深刻理解 Linux 内核学习笔记(一):进程

进程

    进程是任何多通道程序设计的操做系统中的基本概念,进程一般被定义为程序执行时的一个实例,在 Liunx 的源代码中,进程一般被称为 “任务”。shell

 

进程描述符

    进程描述符的做用是为了管理进程,内核必须对每一个进程所作的事情进行清除的描述,例如,内核必须知道进程的优先级、进程状态、为它分配什么样的地址空间、容许访问那些文件等等;数组

    进程描述符是 task_struct 类型结构,它的域包含了与一个进程相关的全部信息。数据结构

 

进程状态

    进程描述符中的状态域描述了进程当前所处的状态:app

    可运行状态(TASK_RUNNING):进程要么在 CPU 上执行,要么准备执行;spa

     可中断的等待状态(TASK_INTERRUPTIBLE):进程被挂起(睡眠),直到一些条件变为                              真(产生一个硬件中断,释放进程正等待的系统资源,或传递一个信号,都能唤醒进程,回到  TASK_RUNNING;操作系统

     不可终端的等待状态(TASK_UNINTERRPTIBLE):与前一个状态相似, 但有一个例外,把信号传递到睡眠的进程不能改变它的状态,用在进程必须等待,不能被中断,知道给定的事件发生;设计

     暂停状态(TASK_STOPPED):进程的执行被暂停,收到 SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU 信号,进人暂停状态,当一个进程被另外一个进程监控时,任何信号均可以把这个进程置于 TASK_STOPPED 状态;指针

     僵死状态(TASK_ZOMBIE):进程的执行被终止,可是父进程尚未发布 wait() 类系统调用以返回有关死进程的信息,发布 wait() 类系统调用前,内核不能丢弃包含在死进程描述符中的数据,由于父进程可能还须要它;队列

 

标识一个进程

    Linux 进程能共享内核大部分数据结构(经过轻量级进程);进程

    Linux 能处理多达 NR_TASKS 个进程,内核在本身的地址空间保存了一个全局静态数组 task,大小为 NR_TASKS,数组中的元素就是进程描述符指针,空指针表示数组项中没有进程描述符。

   

进程描述符的存放

    task 数组仅仅包含进程描述符的指针,而不是描述符自己,由于进程是动态实体,所以,进程描述符被存放在动态内存中,而不是存放永久的分配给内核的内存区。

    内核态的进程访问包含在内核数据段中的栈,内存区中存放进程描述符和内核态的进程栈:

    esp 寄存器是 CPU 栈指针,用来存放栈顶的地址,从用户态切换到内核态之后,进程的内核态堆栈老是空的,所以,esp 寄存器直接指向这个内存区的顶端。

    C 语言中用联合结构表示这个混合结构:

    union task_union {

        struct task_struct task;

        unsigned long stack[2048];

    };

 

current 宏

    进程描述符与内核堆栈之间的配对:内核很容易从 esp 寄存器的值得到当前在 CPU 上正在运行的进程描述符指针。

    假设内存区是 8KB(2^13)长,内核必须让 esp 至少有 13 位有效位,以得到进程描述符的基础址,由 current 宏完成:

    movl  $0xffffe000,    %ecx

    andl  %esp,    %ecx

    movl  %ecx,    p

    执行三条指令后,局部变量 p 包含了在 CPU 上运行的进程描述符指针;

 

进程链表

    为了对给定类型的进程进行有效的搜索,内核创建了几个进程链表,每一个进程链表由指向进程描述符的指针组成;

    一个双向循环链表把全部现有的进程联系起来,称为进程链表(process list),每一个进程的 prev_task 和 next_task 域用来实现链表,链表的头是 init_task 描述符,由 task 数组的第一个元素指向,是进程的祖先,叫作进程 0 或 swapper,init_task 的 rev_task 域指向链表中最后插入的进程描述符。

    SET_LINKS、REMOVE_LINKS 宏用来分别在进程链表中插入和删除一个进程描述符,for_each_task 宏扫描整个进程链表:

    #define for_each_task(p) \

        for (p = &init_task; (p = p->next_task) != &init_task ;)

 

    TASK_RUNNING 状态的进程有独立的双向循环链表:运行队列(runqueue)。

    pidhash 表及链接表:从进程的 PID 导出对应的进程描述符指针。

    task 空闲表项的链表:进程建立或撤销都要更新。

 

 

进程之间的亲属关系

    进程 0 和进程 1 由内核建立,进程 1(init)是全部进程的祖先,一个进程 P 的描述符包含下列域:

     p_opptr  —— 祖先(original parent):p_opptr 指向建立了进程 P 的进程描述符,若是父进程不存在,则指向 1,当一个 shell 用户启动一个后台进程并从 shell 退出时,后台进程变成 init 的子进程;

     p_pptr —— 父进程(parent):p_pptr 指向 P 的当前父进程,值一般与 p_opptr 一致,但偶尔不一样,当另外一个进程发布 parace() 系统调用请求监控 P 时;

     p_cptr —— 子进程(child):p_cptr 指向 P 年龄最小的子进程的描述符,即指向刚刚由 P 建立的进程的进程描述符

     p_ysptr —— 弟进程(younger sibling):p_ysptr 指向在 P 以后由 P 的父进程立刻建立的进程的进程描述符

     p_osptr —— 兄进程(younger sibling):p_ysptr 指向在 P 以前由 P 的父进程立刻建立的进程的进程描述符

 

等待队列

    

    把 TASK_INTERRUPTIBLE 或 TASK_UINTERRUPTIBLE 状态的进程分红不少类,每一类对应一个特定的事件,进程状态提供的信息知足不了快递检索进程,引入了另外的进程链表 —— 等待队列(wait queue)

 

     等待队列对中断处理、进程同步及定时用处很大,进程必须常常等待某些事件的发生,等待队列实如今事件上的条件等待:但愿等待特定事件的进程把本身放进合适的等待队列,并放弃控制权,所以等待队列表示一组睡眠的进程,当某一条件变为真时,由内核唤醒它们;

 

     等待队列中每一个元素都是 wait_queue 类型:

struct  wait_queue {

     struct task_struct  *  task;

     struct wait_queue  *  next;

}

每个等待队列由一个等待队列指针来标识,等待队列指针或者指向链表中第一个元素地址,wait_queue 数据结构的 next 域指向链表中的下一个元素;

 

进程的使用限制

    

    进程与一组使用限制(usage limit)相关联,使用限制指定了进程能使用的系统资源数量:

    RLIMIT_CPU:进程使用 CPU 的最长时间,若是进程超过了这个限制,内核就向它发一个 SIGXCPU 信号,若是进程还不终止,再发一个 SIGKILL 信号;

    RLIMIT_FSIZE:容许文件大小的最大值,若是进程试图把文件的大小托充到大于这个值,内核就给这个进程发 SIGXFSZ 信号;

RLIMIT_DATA、RLIMIT_STACK、RLIMIT_CORE、RLIMIT_RSS、RLIMIT_NPROC、RLIMIT_NOFILE、RLIMIT_MEMLOCK、RLIMIT_MEMLOCK、RLIMIT_AS 等等限制;

     使用限制被存放在进程描述符的 rlim 域:

     struct limit {

          long  rlim_cur;

          long  rlim_max;

}

     rlim_cur 域是资源当前使用限制,例如:current->rlim[RLIMIT_CPU].rlim_cur 表示在 CPU 上正在容许进程所花时间的当前限制;

     rlim_max 域是资源限制所容许的最大值,利用 getrlimit() 和 setrlimit() 系统调用,能够把   rlim_cur 增长到 rlim_max;

 

进程切换、建立进程、撤销进程

    单独博客

相关文章
相关标签/搜索