精华内容
下载资源
问答
  • 深入理解Linux内核架构

    万次阅读 2016-12-30 21:44:36
    命名空间,可以简单理解为划分多个地区,在每个地区单独命名。 命名空间的作用: (1)只使用一个内核,在一台计算机上运作,所有全局资源都通过命名空间抽象起来,这使得可以将一组进程放置到容器中,个容器彼此...

    2.1 进程优先级

     粗略分,实时进程和非实时进程。
    
     实时进程:(1)硬实时进程,必须在可保证的时间范围内得到处理。
                     (2)软实时,仍然需要尽快处理,晚一点也可以接受。
    
     普通进程,非实时的,根据重要性来划分,交互式的要尽快响应,冗长的编译或计算优先级可以低一点。
    
    
    
     调度器的两次进化
     (1) O(1)调度器,可以在常数时间内完成工作,不依赖系统运行的进程数目。
    
     (2)完全公平调度器,试图尽可能模仿理想情况下的公平调度,还能处理更一般行的调度实体。
    

    2.2 进程的生命周期

    进程的状态:
    (1)运行:该进程正在执行。
    (2)等待:进程可运行,但没得到许可,当前cpu给了另外的进程,调度器下次可以选择当前进程。
    (3)睡眠:进程正在睡眠,无法运行,等待外部事件,调度器下次切换任务时不可选该进程。

    进程的状态切换
    这里写图片描述

    图 2-2的每个路径的描述
    ①进程必须等待事件,则从“运行”改变为“睡眠”。
    ②调度器从进程收回CPU资源,则从“运行”变为“等待”。
    ③“睡眠”状态,不能直接变为“运行”,在进程等待的时间发生后,先变回“等待”。
    ④在调度器授予CPU时间之前,进程一直保持“等待”,分配CPU时间后,状态才能改为“运行”。
    ⑤程序执行终止后,就从“运行”变为“终止”。
    
    
    僵尸进程:进程已经终止了,但是进程表终仍然有对应的表项。
    产生的原因:进程由另外的进程或者用户杀死,父进程,在子进程终止时,没有调用wait释放子进程保留的资源。
    

    linux进程管理中的两种状态:用户状态和核心态。
    用户态进程:权限受限,只能访问自身数据,不会干扰其他程序,如果要访问系统数据或者功能,必须切换到核心态。
    核心态进程:有无限的权限。

    从用户态切换到核心态的方法:
    (1)系统调用,这个是由用户程序调用。
    (2)中断,这个是自动触发的。

    内核抢占是调度,抢占规则:
    (1)普通进程总是可被抢占的,甚至是由其他进程抢占。
    (2)系统处于核心态, 正在处理系统调用,其他进程是无法抢占CPU的,调度器必须等到系统调用结束。
    (3)中断可以暂停处于用户态和核心态的进程,优先级最高。

    2.3 进程表示

    核心数据结构tack_struct,相当庞大,可以分为如下部分
    (1)状态和执行信息,如待决信号,pid,父进程指针。
    (2)已分配的虚拟内存信息。
    (3)进程身份凭据,用户ID,组ID及权限。
    (4)使用的文件,包括程代码的二进制文件。
    (5)线程信息,记录该进程特定于CPU的运行时间数据。
    (6)进程间通信相关的信息。
    (7)信号处理程序。

    linux资源限制机制,对进程使用系统资源施加某些限制,用rlim数组。
    struct rlimit{
    unsigned long rlim_cur;
    unsigned long rlim_max;
    }

     rlim_cur,进程当前资源限制,也称软限制。
     rlim_max,最大容许值,也称硬限制。
    
     getrlimits和setrlimit分别用来读写,当前限制。可以查看进程的限制,cat /proc/pid/limits
    

    2.3.1 进程类型
    典型的unix进程包括:二进制代码组成的应用程序、单线程、分配给应用程序的一组资源。
    进程的产生方式:
    (1)fork,生成当前进程的一个相同副本,称子进程,原进程的所有资源都以适当的方式复制到子进程。
    原来的进程有两个独立的实例,包括同一组打开文件、同样的工作目录、内存中的同样数据。

     (2)exec,从一个可执行的二进制文件加载另外一个程序,来代替当前运行的进程。exec不创建新进程,所以
    要先fork复制一个旧程序,在exec创建另外一个应用程序。
    
     (3)clone,原理基本和fork相同,新进程不是独立于父进程的,而是可以与父进程共享某些制定需要的资源。
          一般用于实现线程。
    

    2.3.2 命名空间

    传统的全局管理资源,例如系统所有进程,通过pid标识,所以内核管理一个全局的pid列表。
    

    用户id的管理方式类似,全局id使得内核有选择允许货拒绝某些特权。如果想让某个用户为root,其他用户又不想受到干涉,那就太难了,这样他们每个都独立一个主机才行。这种做法存在局限性。

    命名空间,可以简单理解为划分多个地区,在每个地区单独命名。

    命名空间的作用:
    (1)只使用一个内核,在一台计算机上运作,所有全局资源都通过命名空间抽象起来,这使得可以将一组进程放置到容器中,个容器彼此隔离。
    (2)隔离使得容器的成员与其他容器毫无关系。
    (3)可以通过允许容器进行一定的共享,来降低容器之间的分隔。

    本质上,命名空间建立了系统的不同视图,每一项全局资源,都必须包含到容器的数据结构中,
    只有资源和包涵资源的命名空间,构成的二元组才是全局唯一的。
    这里写图片描述

    命名空间的创建方法:
    (1)fork或clone创建进程时,有选项可以控制是与父进程共享命名空间,还是建立新的命名空间。
    (2)unshare系统调用,将进程的某些部分从进程分离,其中包括命名空间。

    子进程从父进程命名空间分离后,从子进程看,改变全局属性不会传播到父进程命名空间,而父进程的修改也不会传播到子进程,文件系统的另当别论。

    命名空间如何实现?
    (1)每个子系统的命名空间结构将此前所有的全局组件包装到命名空间中。
    (2)将给定进程关联到所属命名空间的机制。

    每个内核子系统的全局属性都封装到命名空间,用一个数据接口,将所有通过命名空间形式提供的对象集中起来,就是struct nsproxy。

    这里写图片描述

     从struct task_struct {
    ……
        struct nsproxy *nsproxy;
    ……
    }可知,多个进程可以共享一个命名空间。
    

    2.3.3 进程ID号

    在命名空间中用于唯一标识一个进程的,是进程ID,简称pid,fork货clone产生的每个进程都由内核分配一个新的pid值。
    

    1、进程id有很多类型:
    (1)进程里面的线程组id。
    (2)进程组组长id。
    (3)几个进程组合成一个会话,所以每个进程有个会话id。

    全局ID和局部ID的区分:在建立一个新的命名空间时,该命名空间中的所有pid对父命名空间时可见的,但子命名空间无法看到父命名空间的皮带。这表示某些进程有多个pid,凡可以看到该进程的命名空间都会为其分配一个pid。

    全局ID:对每个ID类型,都有一个给定的全局ID,保证在整个系统中的唯一性。
    局部ID:对每个ID类型,它们在所属的命名空间内部有效,但类型相同、值也相同的ID可能出现在不同的命名空间中。

    2、管理pid

    pid分配器,用于加速新Id的分配。这里的id是广义上的,包括组id,组长id等。

    先看看pid命名空间的表示方式
    struct pid_namespace {
    ……
    struct task_struck *child_reaper;
    ……
    int level;
    struct pid_namespace *parent;
    };

    这里有2个关键的地方:
    (1)每个pid命名空间都有个进程,如上述的child_reaper就用于指向这个进程,该进程的作用相当于init进程,目的是对孤儿进程调用wait4,命名空间局部的init变体也需要完成该工作。

    (2)parent指向父命名空间,层次表示当前命名空间在命名空间的层次结构中的深度。初始命名空间的深度为0。

    pid管理围绕struct pid(内核对pid的内部表示)和struct upid(特定的命名空间中的可见信息)展开。

    struct upid{
    int nr;//id的数值
    struct pid_namespace *ns;//该id所属的命名空间指针
    struct hlist_node pid_chain;//将所有的upid链接在散列表的链表。
    };

    struct pid
    {
    atomic_t count;
    struct hlist_head tasks[PIDTYPE_MAX];//每个项对应于一个id类型,作为一个散列表头。
    int level;
    };

    对tasks中的每项,因为一个ID可能用于几个进程(如图2-5),所有共享同一给定ID的task_struct实例都吐过该
    列表链接起来。

    enum pid_type
    {
    PIDTYPE_PID,
    PIDTYPE_PGID,
    PIDTYPE_SID,
    PIDTYPE_MAX
    };
    这里写图片描述

    用函数可以操作和扫描上述复杂结构:
    (1)给出局部数字id和对应的命名空间,查找此二元组的task_struct;
    (2)给出task_struct、id类型、命名空间,取得命名空间局部的数字ID;

    3、生成唯一的pid
    具体做法:为跟踪已经分配和仍然可用的pid,内核使用一个大的位图,其中每个pid由一个比特标识,pid的值可以通过对应比特在位图的位置来计算。其他的id都可以派生于pid。

    2.3.4 进程关系

    一张图说明,父子关系,兄弟关系。
    

    这里写图片描述

    struct task_struct{
    ……
    struct list_head children;
    struct list_head sibling;
    ……
    }
    

    2.4 进程管理相关的系统调用

    2.4.1 进程复制
    进程复制有3个方法:
    (1)fork,建立一个父进程的完整副本,然后作为子进程执行。linux使用写实复制。
    
    (2)vfork,不创建父进程的副本,父子进程之间共享数据,一个进程修改,另外的会知道。
    

    主要用于vfork后,调用execve加载新程序,子进程开始或退出前,父进程处于堵塞状态。

    (3)clone产生线程,可以对父子进程之间的共享、复制进行精确控制。
    
    
    1、写时复制
     诞生的原因:复制父进程副本使用内存多,耗费时间长,很多情况下不需要复制。
     作用是可以节省时间、内存空间。
     在父、子进程确实要写入,会产生缺页异常,然后再由内核分配内存空间。
    
    2、执行系统调用
     fork  —— sys_fork
    vfork  —— sys_vfork
    clone — — sys_clone
    几个系统调用对应的入口函数,最终这些入口函数,调用体系结构无关的do_fork,通过clone_flags这个标志集合区分不同入口。
    
    
    3、do_fork的实现
    do_fork
        |—>copy_process
        |—>确定pid
        |—>初始化vfork的完成程序(在设置了CLONE_VFORK的情况下)和ptrace标志。
        |—>wake_up_new_task
        |—>是否设置了CLONE_VFORK标志?—>wait_for_completion
    
    
       子进程生产成功后,需要执行一下操作:
    (1)如果有CLONE_PID标志,fork操作先要创建新的pid命名空间。然后在新命名空间获取pid。否则直接获取局部pid。
    (2)如果用了ptrace监控,创建进程后,就向它发送SIGSTOP信号,让调试器检查数据。
    (3)子进程使用wake_up_new_task唤醒,也就是将它的task_struct添加到调度器队列,让她有机会执行。
    (4)如果启用vfork机制,就要启用子进程的完成机制,子进程的task_struct的vfork_done成员即用于该墓地,父进程用wait_for_completion函数在该变量中进入睡眠,直至子进程退出。子进程终止时,内核调用complete(vfork_done)唤醒因该变量睡眠的进程。
    
    4、复制进程
    该过程主要受到标志的控制,来进行操作,内容较多,但比较简单。
    
    5、创建线程时的特别问题
    也是一些标志控制问题。
    
    2.4.2 内核线程
    
    内核线程的定义:直接由内核本身启动的进程,实际上是将内核函数委托给独立的进程,与系统其他进程并行执行,经常被称为内核守护进程。如,内存与块设备同步进程、系统事务日志进程。
    
    内核线程,主要两类,要么就是启动后,一直等待内核让它执行某一些操作,要么就是周期性运行,检查特定资源,是否符合预设限制,然后根据检查结果执行操作。
    
    内核线程的特别之处:
    

    (1)它们在cpu管态执行,而不是用户态。
    (2)它们只能访问内核部分的虚拟地址空间,不能访问用户空间。由于这个原因,内核上下文切换时,不需要倒换用户层的虚拟地址,等到下次执行的是与切换前不一样的进程才需要切换。

    内核线程的实现:
    (1)将一个函数传递给kernel_thread,内核调用daemonize,从内核释放父进程的所有资源。
    (2)daemonize阻塞信号的接收。
    (3)将init作为守护进程的父进程。

    更简单的方法是kernel_create。

    2.4.3 启动新程序

    1、ececve的实现
    该系统调用的入口点是体系结构相关的sys_execve,最后将工作委托给do_execve。
    do_execve的主要工作:
    (1)打开要执行的文件。
    (2)bprm_init,申请进程空间并初始化。
    (3)prepare_binprm,提供父进程相关的值,也是用于初始化。
    (4)search_binary_handler,查找一种适当的二进制格式,用于所要执行的特定文件。
    如释放原进程的所有资源,将应用程序映射到虚拟地址空间,参数和环境也映射到虚拟地址空间,
    设置进程的指令指针和其他特定于体系结构的寄存器。
    
    2、解释二进制格式
    解释数据结构体 linux_binfmt,每种二进制格式,都要用register_binfmt向内核注册。
    
    主要接口:
    (1)load_binary加载程序。
    (2)load_shlib价值普通程序。
    (3)core_dump用于在程序错误的情况下输出内存转储,用于调试分析。
    

    2.4.4 进程的退出
    进程调用exit退出,内核能有机会讲资源释放回系统。

    2.5 调度器的实现

    2.5.1 概观

    调度器的任务是在程序之间共享cpu时间,该任务分为调度策略和上下文切换两部分。
    
    当前的调度器只考虑进程的等待时间,即进程在就绪队列中已经等待的时间,对cpu时间需求最严格的进程被调用,这样进程的不公平等待不会积累,不公平会均匀分布到系统的所有进程。
    
    组成结构:所有可运行的进程都按等待时间在一个红黑树中排序,等待cpu时间最长的进程是在最左侧,调度器下次会考虑该进程,等待时间稍短的进程在该树上从左至右排序。时间复杂度时O(log n)。
    
    虚拟时钟:该时钟的时间流逝速度慢于实际的时钟,精确的速度依赖于当前等待调度器挑选的进程数目。如4个进程,在就绪队列实际等待20秒,相当于虚拟时间5秒。
    
    就绪队列的虚拟时间由fair_clock确定,进程的等待时间保存在wait_runtime,在红黑树排序时,食用
    

    fair_clock-wait_runtime,当然是用绝对值了。另外,程序运行时,将从wait_runtime减去已经运行的时间,这样按时间排序时,它就会往右移动一点了。当前还会受到进程的优先级,和是否频繁切换因素影响。

    2.5.2 数据结构

    调度子系统的概观
    

    这里写图片描述

    激活调度的方法:
    (1)直接的,进程打算睡眠或其他因素放弃cpu。
    (2)周期性的,以固定频率运行,检查是否有必要进行进程切换。
    用这两种方法 ,分别是上面的通用调度器、核心调度器。

    调度类:用于判断后面调用哪个进程,内核支持不同的调度策略(完全公平、实时调度、空闲时调度空闲进程),调度类能以模块化实现这些策略。调度器被调用时,它会查询调度器类,确定接下来运行哪个进程。也就是调度器类。

    进程切换:在选中将运行的程序后,要执行底层任务切换。

    注意:每个进程都属于某一调度类,每个调度类负责管理所属的进程,调度器本身不涉及进程管理,工作都委托给调度类。

    1、task_struct的成员
    struct task_struct{
    ……
    int prio;
    int normal_prio;
    int static_prio;
    unsigned int rt_priority;
    const struct sched_class *sched_class;
    unsigned int policy;
    cpumask_t cpus_allowed;
    unsigned int time_slice;
    ……
    };

    动态优先级:
    (1)主要有两类prio和normal_prio。
    (2)其中normal_prio表示基于进程的静态优先级和调度策略计算的优先级,因此即使普通进程和实时进程有相同静态优先级,其普通优先级也是不同的。
    (3)调度器的优先级保存在prio。

    注意,进程分支时,子进程会继承普通优先级。

    rt_priority表示实时进程的优先级,该值不会代替前面prio、normal_prio等值,最低的优先级为0,最高为99。

    sched_class表示进程所属的调度类。

    调度器不限于调度进程,还可以调度更大的实体,如进程组调度,这里将cpu时间在进程组之间分配,接下来再在组内分配。这里调度器不直接操作进程,而是处理可调度实体,由sched_entity表示。

    policy保存进程的调度策略,如完全公平的调度。

    cpu_allowed是个位域,在多处理器时,用于限制进程可在哪个cpu运行。

    run_list和time_slice时循环实时调度器所需要的,但不用于完全公平调度器。

    2、调度器类

    对每个调度类,都必须提供sched_class的一个实例,调度器请求的操作都有指针表示,调度器不要了解不同调度类的内部工作。

    调度类之间的层次优先级:实时进程 > 完全公平调度 > 空闲进程,sched_class的next,就是按这个顺序链接起来的,这个层次结构在编译时已经建立,不能在运行时增加新调度类的机制。

    3、就绪队列

    就绪队列:核心调度器用于管理活动进程的主要数据结构,每个活动进程只出现在一个就绪队列。另外,进程不是就绪队列的成员直接管理的,进程由调度类管理,就绪队列潜入了特定于调度类的子就绪队列。

    就绪队列的核心成员及解释:
    这里写图片描述

    nr_running,指定了队列上可运行的进程数目,不考虑优先级和调度类。
    load 就绪队列当前负荷的度量。
    cpu_load 用于跟踪此前的负荷状态。
    cfs和rt是嵌入的子就绪队列,分别用于完全公平调度器和实时调度器。
    curr 指向当前运行的进程的task_struct实例。
    idle 指向空闲进程的task_struct实例。
    clock和prev_raw_clock用于实现就绪队列自身的时钟,每次调用周期性调度器时,都会更新clock的值。
    

    4、调度实体

    load 指定了权重,决定了实体占队列总负荷的比例。
    run_node 标准的树结点,实体可以在红黑树上排序。
    on_rq 表示该实体当前是否在就绪队列上接受调度。
    sum_exec_runtime用于记录进程运行时,消耗的cpu时间,用于完全公平调度。
    在进程被撤销cpu时,当前sum_exec_runtime的值保存到prev_exec_runtime中。
    

    2.5.3 处理优先级

    1、优先级的内核表示
    进程的nice值在-20和+19之间(包含),值越低优先级越高。
    内核用0至139(包含),表示内部优先级,值越低优先级越高。0-99用于实时进程,nice值[-20,19]映射到范围[100,139],实时进程优先级总是比普通进程优先级高。
    
    2、计算优先级
    进程的优先级计算,要考虑static_prio、normal_prio、prio三种优先级。
    调用相关函数计算结果,
    

    这里写图片描述

    3、计算负荷权重
    进程的重要性由进程优先级和task_struct->se.load的负荷权重。进程每降低一个nice值,则多获得10%的cpu时间,每升高一个nice值,就放弃10%的cpu时间,也就是优先级加1,权重就减少。进程加入到就绪队列时,就绪队列的负荷权重也会增加。
    
     2.5.4 核心调度器
    
    1、周期性调度器
    (1)在scheduler_tick中实现,系统活动时,内核以频率hz自动调用该函数。
    (2)没进程等待时,供电不足情况下,可以关闭周期性调度器。
    (3)主要任务是管理内核中与系统和每个进程的调度相关的统计量,另外就是激活负责当前进程的调度类的周期性调度方法。
    
    __update_rq_clock 更新rq的时钟时间戳。
    update_cup_load,更新rq->cup_load数组。
    
    2、主调度器
    将当前cpu分配给另一个进程,要调用主调度器函数schedule,从该系统调用返回后也要检查当前进程是否设置了重调度标志TIF_NEED_RESCHEDULE,如果有,内核会调用schedule。
    

    __sched前缀的用处:有该前缀的函数,都是可能调用schedule的函数,包括schedule自身。该前缀目的是将相关代码的函数编译后,放到目标文件的特定段中,.sched.text中。该信息使内核在现实栈转储或类似信息时,忽略所有与调度有关的调用。由于调度器函数调用不是普通代码流程的部分,所以这种情况下是无意义的。

    asmlinkage void __sched schedule( void );该函数的过程:
    (1)将就绪队列的当前活动进程指针保存在prev中,prev = rq->curr;
    (2)更新就绪队列的时钟,清除当前进程task_struct的重调度标志TIF_NEED_RESCHED。
    (3)判断当前进程是否在可中断睡眠状态,而且现在接收到信号,那么它将再次提升为可运行。否则,用
    deactivate_task讲进程停止。
    (4)再用put_prev_task通知调度类,但前进程要被另一进程代替。pick_next_task,选择下一个要执行的进程。
    (5)只有1个进程,是不要切换的,还让它留在cpu。要是能选择另外的进程,就用context_switch进行上下文切换。
    (6)当前进程,被重新调度回来时,检测是否要重新调度,如果要,就又重复前面(1)至(5)的步骤了。
    
    
    3、与fork的交互
    
    用fork或其变体新建进程时,调度器用sched_fork函数挂钩到该进程。在用wake_up_new_task唤醒进程时,内核调用调度类的task_new将新进程加入相应类的就绪队列。
    
    单处理器,sched_fork执行如下:
    (1)初始化新进程与调度相关的字段。
    (2)建立数据结构。
    (3)确定进程的动态优先级。
    
    4、上下文切换
    context_switch要进行如下操作:
    (1)prepare_task_switch,执行特定于体系结构的代码,为切换做准备。
    (2)switch_mm更换task_struct->mm描述的内存管理上下文。
    (3)switch_to切换处理器寄存器和内核栈。
    (4)切换前,用户空间进程的寄存器进入和心态时保存在内核栈上,在上下文切换时,内核栈的值自动回复寄存器数据,再返回用户空间。
    
    展开全文
  • 深入理解linux内核架构First and foremost, I have to thank the thousands of programmers who have created the Linux kernel over the years — most of them commercially based, but some also just for their ...
  • https://blog.csdn.net/xueli1991/article/details/56013275
    展开全文
  • proc文件系统属于虚拟文件系统,即该文件系统的数据,由内核动态生成,并不会存放在持久存储数据中。 proc文件系统,使得内核可以生成与系统状态和配置有关的信息。该信息可以由用户和系统程序从普通文件读取,而...

    proc文件系统

    1. proc简介(Process Data System, 进程数据系统)

    proc文件系统属于虚拟文件系统,即该文件系统的数据,由内核动态生成,并不会存放在持久存储数据中。

    proc文件系统,使得内核可以生成与系统状态和配置有关的信息。该信息可以由用户和系统程序从普通文件读取,而无需专门的工具与内核通信。比如可以通过cat status打印进程的状态信息,如下:

    daniel@daniel-HP-ProBook-4411s:/proc/self$ cat status
    Name:	bash
    State:	S (sleeping)
    Tgid:	5797
    Pid:	5797
    PPid:	4378
    TracerPid:	0
    Uid:	1000	1000	1000	1000
    Gid:	1000	1000	1000	1000
    FDSize:	256
    ......

    从内核开发趋势来看,正在远离用proc文件系统提供的信息,而倾向于采用特定与问题的虚拟文件系统来导出数据。一个很好的例子就是USB文件系统,将与USB子系统有关的许多状态信息导出到用户空间,而没有给proc增加新的负担。但这并不意味这,proc文件系统变的多余,当今,/proc依旧重要,不仅在安装新的发布版时,而且也用于支持(自动化的)系统管理。

    proc文件系统主要有如下几个主要功能:

    a. 用于查看进程的数据信息,别忘了proc的全称为process data system

    b. 查看与特定的内核子系统无关的一般信息,如/proc/iomem, /proc/ioports, /proc/interrupts

    c. 查看网络信息,/proc/net子目录提供了内核的各种网络选项的有关数据。

    d. 查看和修改系统控制参数,由/proc/sys子目录提供。例,可以通过cat /proc/sys/vm/swappiness 查看交换算法在换出页时的积极程度。

    2. proc的数据结构

    Ext2一样,proc大量使用了VFS数据结构,因为作为一种文件系统,它必须集成到内核的VFS抽象层中。但,毕竟proc只是用于获取内核的数据为主要目的,所以在其设计的过程中,遵循简单实用的特性,较Ext2简单。


    1-1 底层为procVFS结构

    1-1中绿色方框的部分为proc实现的部分,proc中文件的大小为0,所以inode中的i_mapping并没有使用,值为NULLproc_dir_entry用于表示一个proc文件系统中的目录,通过proc_mkdir创建,目录之间的树形结构通过proc_dir_entry中的*next, *parent, *subdir维护。1-1中所涉及的结构体请参考书中的第8, 10章和Linux内核源代码。

    以下以查询一个目录为例(主要涉及path_lookup函数,查找过程与VFS与Ext2文件系统一致),得益于VFS文件系统,proc文件系统只需要自定义实现lookup接口。因为查找过程将在一定时间到达real_lookup,该函数将通过调用dentry->i_op->lookup实现。

    dentry->i_op->lookup,对于/proc文件,为proc_root_lookup;对于/proc目录一下的非PID目录,为proc_lookup;对于/proc下的PID目录,为proc_tgid_base_operations

    // proc_root_lookup函数的主要功能
    static struct dentry *proc_root_lookup(struct inode * dir, struct dentry * dentry, 
      struct nameidata *nd)
    {
      	if (!proc_lookup(dir, dentry, nd)) {//顺序扫描指定路径的各个分量
      		return NULL;
      	}
      	
      //需要扫描的目录为一个pid目录,而该pid目录的proc_dir_entry还没有创建。
      	return proc_pid_lookup(dir, dentry, nd); // 查找指定pid目录下的目录或文件
      }
    }

    // proc_lookup 函数,PDE主要是由container_of机制实现的
    struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry, 
      struct nameidata *nd)
    {
    	return proc_lookup_de(PDE(dir), dir, dentry);
    }

    /*
     * Don't create negative dentries here, return -ENOENT by hand
     * instead.
     */
    struct dentry *proc_lookup_de(struct proc_dir_entry *de, struct inode *dir, 
      struct dentry *dentry)
    {
    	struct inode *inode = NULL;
    	int error = -ENOENT;
    
    	spin_lock(&proc_subdir_lock);
    	for (de = de->subdir; de ; de = de->next) { //遍历de下面的所有路径分量
    		if (de->namelen != dentry->d_name.len)
    			continue;
    		if (!memcmp(dentry->d_name.name, de->name, de->namelen)) {
    			unsigned int ino;
    
    			ino = de->low_ino;
    			de_get(de);
    			spin_unlock(&proc_subdir_lock);
    			error = -EINVAL;
    			inode = proc_get_inode(dir->i_sb, ino, de); // 查找成功,创建proc_dir_entry *de对应的proc_inode, 并返回&(proc_inode.vfs_inode)。注意inode的inode_operations初始化为de->proc_iops。
    			goto out_unlock;
    		}
    	}
    	spin_unlock(&proc_subdir_lock);
    out_unlock:
    
    	if (inode) {
    		dentry->d_op = &proc_dentry_operations;
    		d_add(dentry, inode); // 将新初始化的dentry加入dentry缓存
    		return NULL;
    	}
    	if (de)
    		de_put(de);
    	return ERR_PTR(error);
    }

    struct dentry *proc_pid_lookup(struct inode *dir, struct dentry * dentry, 
      struct nameidata *nd)
    {
    	struct dentry *result = ERR_PTR(-ENOENT);
    	struct task_struct *task;
    	unsigned tgid;
    	struct pid_namespace *ns;
    
    	result = proc_base_lookup(dir, dentry); // if (dentry->d_name== self) 创建self的inode
    	if (!IS_ERR(result) || PTR_ERR(result) != -ENOENT)
    		goto out;
    
    	tgid = name_to_int(dentry); //将字符串转换为unsigned int
    	if (tgid == ~0U)
    		goto out;
    
    	ns = dentry->d_sb->s_fs_info;
    	rcu_read_lock();
    	task = find_task_by_pid_ns(tgid, ns); // 查找对应pid对应的task_struct
    	if (task)
    		get_task_struct(task); // 使用计数加1 
    	rcu_read_unlock();
    	if (!task)
    		goto out;
    
    	result = proc_pid_instantiate(dir, dentry, task, NULL);// 为该pid目录创建inode和dentry,注意inode的inode_operations初始化为proc_tgid_base_inode_operations。
    	put_task_struct(task);
    out:
    	return result;
    }


    展开全文
  • 深入LINUX内核架构(德)莫尔勒: 链接:https://pan.baidu.com/s/1g9klBGkKqtRfnT2uAmGlpw 提取码:n83g 深入理解LINUX网络技术内幕_高清版带书签: 链接:https://pan.baidu.com/s/1gVlqKFHTeNVj4U8PT3B5Aw 提取码:l1...

    深入LINUX内核架构(德)莫尔勒:
    链接:https://pan.baidu.com/s/1g9klBGkKqtRfnT2uAmGlpw
    提取码:n83g

    深入理解LINUX网络技术内幕_高清版带书签:
    链接:https://pan.baidu.com/s/1gVlqKFHTeNVj4U8PT3B5Aw
    提取码:l1o1

    展开全文
  • 深入Linux 内核架构

    2019-04-01 21:46:28
    作者:Wolfgang Mauerer 翻译:郭旭 高清电子扫面版,可以pdf文字搜索,方便查询所需要的信息。 本人又使用了Adobe Acrobat 优化了网页浏览速度 欢迎大家下载,祝大家学习愉快,开卷有益
  • 深入理解Linux内核》.pdf
  • 深入理解linux内核第三版 中文
  • Linux内核层还提供网络防火墙的框架netfilter,基于netfilter框架编写网络过滤程序是 Linux 环境下内核层网络处理的常用方法。 视频教程:内核网络协议栈架构详解 1、Linux内核源代码结构 Linux 的内核源代码可以从 ...
  • 深入理解 Linux 内核》就是一本辅助学习 Linux 内核的经典书籍。有的初学者,在没有人指导的情况下,就钻入 Linux Kernel代码的海洋中埋头苦学,结果学了半天仍然是一头雾水。当然了,在大师指导下学习就不一样了...
  • 本书很多观点分新颖,言简意赅,《深入理解Linux内核》名声不错,但过于教科书化,而且只讲数据结构不讲算法,不太直观。《Linux内核设计与实现》讲的过于笼统,内容组织也不合理。 强烈推荐此书。 本书缺点: (1) ...
  • 本书很多观点分新颖,言简意赅,《深入理解Linux内核》名声不错,但过于教科书化,而且只讲数据结构不讲算法,不太直观。《Linux内核设计与实现》讲的过于笼统,内容组织也不合理。 强烈推荐此书。 本书缺点: (1) ...
  • 深入理解Linux内核》英文版,电子书
  • 深入理解Linux内核(高清版).PDF 电子书 (共八部分)part1 中国电力出版社
  • 深入理解LINUX内核》内存管理

    千次阅读 2016-12-25 12:05:46
    深入理解LINUX内核》内存管理。
  • 深入Linux内核架构》进程管理和调度,基础介绍。
  • 深入理解 Linux 内核》就是一本辅助学习 Linux 内核的经典书籍。有的初学者,在没有人指导的情况下,就钻入 Linux Kernel代码的海洋中埋头苦学,结果学了半天仍然是一头雾水。当然了,在大师指导下学习就不一样了...
  • 深入理解Linux内核进程上下文切换

    千次阅读 多人点赞 2020-09-28 08:39:19
    作者简介韩传华,就职于南京大鱼半导体有限公司,主要从事linux相关系统软件开发工作,负责Soc芯片BringUp及系统软件开发,乐于分享喜欢学习,喜欢专研Linux内核源代码。我都知道...
  • 深入理解linux内核中文版(第二版)!和第三版差不多,第三版目前没有中文版的电子书!对于了解linux内核必不可少的一本书!
  • 深入Linux内核架构第二章学习笔记

    千次阅读 2010-09-03 11:11:00
    深入linux内核架构是本根据Linux源代码讲述内核的书,比深入理解linux内核更加贴近代码,讲的更深入浅出一些。本书第二章主要讲述Linux进程的调度,linux的进程都可以描述为task_struct的结构,这个结构包含进程运行...
  • 深入理解Linux内核-系统调用篇 就职于国内一家半导体公司,担任高级系统软...
  • 深入理解 Linux 内核

    千次阅读 2010-07-11 16:56:00
    成为一名精通 Linux 程序设计的高级程序员一直是不少朋友孜孜以求的目标。根据中华英才网统计数据,北京地区 Linux 程序员月薪平均为 Windows 程序员的 1.8 倍、Java 程序员的 2.6 倍, Linux 程序员年终奖金平均为 ...
  • 关于学习Linux的经典书籍 (深入理解Linux内核、Linux设备驱动程序等) 分类: 嵌入式linux c 编程 嵌入式arm篇 嵌入式linux kernel篇 嵌入式linux driver篇 嵌入式Bootloader篇 linux 操作系统篇 Qt ...
  • 深入理解linux内核,中文第三版,可随便复制粘贴,包含所有20章的内容
  • 几年前,当我们拿到Linux 内核代码开始研究时,可以说茫然无措。其规模之大,叫“覆 压三百余里,隔离天日”似乎不为过;其关系错综复杂,叫"廊腰线回,檐牙高啄,各 抱地势,勾心斗角”也非言过其实。阿房宫在规模...
  • 本书讨论了Linux内核的概念、结构和实现。主要内容包括多任务、调度和进程管理,物理内存的管理以及内核与相关硬件的交互,用户空间的进程如何访问虚拟内存,如何编写设备驱动程序,模块机制以及虚拟文件系统,Ext...
  • 本节讲的是“linux分段”,上一节是“硬件分段”,硬件分段主要讲80x86如何从硬件层提供分段支持,而本节讲的linux如何使用分段来实现操作系统,当然基于硬件分段也可以实现windows操作系统,但是linux和windows使用...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 24,323
精华内容 9,729
关键字:

深入理解linux内核架构

linux 订阅