linux内核进程状态,深入理解 Linux 内核学习笔记(一):进程

进程

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

进程描述符

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

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

linux内核进程状态,深入理解 Linux 内核学习笔记(一):进程_第1张图片

进程状态

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

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

可中断的等待状态(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 数组仅仅包含进程描述符的指针,而不是描述符本身,因为进程是动态实体,因此,进程描述符被存放在动态内存中,而不是存放永久的分配给内核的内存区。

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

linux内核进程状态,深入理解 Linux 内核学习笔记(一):进程_第2张图片

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 的父进程马上创建的进程的进程描述符

linux内核进程状态,深入理解 Linux 内核学习笔记(一):进程_第3张图片

等待队列

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

等待队列对中断处理、进程同步及定时用处很大,进程必须经常等待某些事件的发生,等待队列实现在事件上的条件等待:希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制权,因此等待队列表示一组睡眠的进程,当某一条件变为真时,由内核唤醒它们;

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

struct  wait_queue {

struct task_struct  *  task;

struct wait_queue  *  next;

}

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

linux内核进程状态,深入理解 Linux 内核学习笔记(一):进程_第4张图片

进程的使用限制

进程与一组使用限制(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;

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

单独博客

你可能感兴趣的:(linux内核进程状态)