进城描述符及基本数据结构
内核把各个进程存储在一个双向链表(图3-1)之中。在该链表里,每个进程由进程描述符(process descriptor)代表。进程描述符与进程一一对应。一个进程描述符实际上就是一个数据结构,其原型存储在<linux/sched.h>中,声明为:struct task_struct{};其包含了足以描述特定进程的全部信息,例如:打开文件, 进程地址空间, 等待信号队列和进程状态等等。
进程描述符构成的双向链表因为内核会为每个进程同时在内核空间和用户空间分配一定的内存。通常在内核空间分配的大小为两个页大小(4KB ~ 8KB),且是固定不变的。而上述的 task_struct 结构则存储在每个进程的内核栈的末端。提供该结构的好处是便于寻址,操作系统不必使用额外的寄存器去存储进城描述符的内存地址,而简单地使用栈指针即可。关于task_struct的详解内容,可阅读Linux进程描述符task_struct结构体详解--Linux进程的管理与调度(一)这篇博客。
除了该数据结构以外,Linux系统还在<asm/thread_info>中定义了一个新的数据结构:struct thread_info,用于进程管理。当进城运行时,这个数据结构存储在内核栈的下方,代码如下:
struct thread_info;那么在源代码中,内核是如何表示内核栈的呢?在<linux/sched.h> 定义了一个联合:
union thread_union;这个联合就是代码级的内核栈表示方式。值得注意的是,因为联合的特性,thread_info 与 stack 两个成员分配在同一个内存区段中,之间并无明显的界限。通常来说,内核栈中的数据不会太多,但是如果用户的行为或是某些恶意行为,导致对进程内核栈的利用率过高,最终将会覆盖thread_info中的数据,将导致不可预知的后果。以下图片仅供参考。
内核栈布局Q: 那么 task_struct 存在于内存中的什么地方呢?在内核栈还是用户地址空间?
进程描述符内容
PID
操作系统为每个进程都分配了一个独一无二的进程ID,类似于人类的身份证。在内核中这个ID被称为PID,数据类型为pid_t,通常情况下等同于int。但为了兼容以往的Unix和Linux版本,PID的最大值为32768,相当于short int。也就是说系统中的进程数最多为32768。当然你也可以通过修改/proc/sys/kernel/pid_max来修改这个值,不过也许会带来兼容性问题。
Current
有时候内核需要快速地寻找到当前进程的task_struct结构,已完成许多工作。所以提供了一个宏(macro)叫做current,这个宏在任何运行Linux的体系上都应被实现。
在x86机器上,current宏通过计算栈指针的13个标志位去获得thread_info结构。进行实际寻找工作的是current_thread_info()函数:
movl $-8192,%eax
andl %esp, %eax
转而再得到task_struct:
current_thread_info()->task;
进程状态
Linux为每个进程提供了五种状态,也就是说进程必须为五种状态之一。
TASK_RUNNING: 在这种状态下的进程是可运行的,不是正在运行中,就是处于进程调度的运行队列里等待调度。如果一个进程在用户空间中运行,那么该状态是它唯一可能处于的状态。当然,对于在内核空间运行的进程,也可能处于该状态。
TASK_INTERRUPTIBLE: 进程处于睡眠中,也可以说正在阻塞(Block),并且是可唤醒的,当其接收到一个恰到的信号就回切换到TASK_RUNNING状态。
TASK_UNINTERRUPTIBLE: 与TASK_INTERRUPTIBLE类似,但区别在于不可被信号唤醒。该状态被应用于一个进程试图不被中断地等待某一事件的发生。 ???
TASK_ZOMBLE: 进程已经终止,但其父进程没有调用wait4()系统调用去获取其相关信息。所以内核将保留其进程标识符与内核空间中,直到父进程做出一定动作。这也是我们俗称的僵尸进程。
TASK_STOPPED: 进程的执行停止了,既不执行也不适合去执行。(???) 当进程接收到SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOUT信号时就会陷入这状态。(??? 回去查阅一下信号章节)
进程状态间转换
网友评论