上节我们总体概括了进程控制块中的内容,下来我们会慢慢的剖析里边的各个域。
这节我们介绍进程控制块中关于进程状态的定义。
linux内核中用一个长整型来标识一个进程的状态,<linux/sched.h>中是这样定义的(3.9版本内核):
struct task_struct {
// 1> 状态信息——描述进程的动态变化,如就绪态、等待态、僵死态等。
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
// 2> 亲属关系——描述进程的亲属关系,如祖父进程、父进程、养父进程、子进程、兄进程、孙进程等。
// 3> 各种标识符——如进程标识符、用户标识符等等用来标识一个进程的数字。
// 4> 进程间通信信息——描述多个进程在同一任务上协作工作,如管道、消息队列、共享内存、套接字等。
// 5> 时间和定时器信息——描述进程在生存周期内使用CPU时间的统计、计费等信息。
// 6> 调度信息——描述进程的优先级、调度策略等信息,如静态优先级、动态优先级、时间片轮转、高优先级以及多级反馈队列等的调度策略。
// 7> 文件系统信息——对进程使用文件情况进行记录,如文件描述符、系统打开文件表、用户打开文件表等。
// 8> 虚拟内存信息——描述每个进程拥有的地址空间,也就是进程编译连接后形成的空间,这里肯定用到前边提到的分页机制。
// 9> 处理器环境信息——描述进程的执行环境(处理器的各种寄存器及堆栈等),这是体现进程动态变化的最主要的场景。
};
同时某一进程的状态的值也在该文件中进行编码:
/*
* Task state bitmask. NOTE! These bits are also
* encoded in fs/proc/array.c: get_task_state().
*
* We have two separate sets of flags: task->state
* is about runnability, while task->exit_state are
* about the task exiting. Confusing, but this way
* modifying one set can't modify the other one by
* mistake.
*/
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* in tsk->exit_state */
#define EXIT_ZOMBIE 16
#define EXIT_DEAD 32
/* in tsk->state again */
#define TASK_DEAD 64
#define TASK_WAKEKILL 128
#define TASK_WAKING 256
#define TASK_PARKED 512
#define TASK_STATE_MAX 1024
就是说每一个进程在某一时刻一定取上述状态中的一个。下图是linux内核为状态转换指定的策略(该图来自陈莉君老师课件):
如图所示,通过fork()创建的进程处于运行状态,其PCB进入运行队列。如果调度程序schedule()运行,则从运行队列中选择一进程投入运行并占用CPU,在进程执行的过程中,因为输入、输出等原因调用interruptible_sleep_on()或者sleep_on(),则进程进入浅度睡眠或者深度睡眠。由于进程进入睡眠状态放弃CPU,因此调度程序重新选择一个程序占用CPU,依次类推。
我们前边说到进程一般有三个状态——运行态、就绪态、和等待态,任何操作系统的实现都肯定实现了这三个状态并且可能还加入自己的一些状态。linux设计者考虑到在任一时刻在CPU上运行的程序最多只有一个(多核结构依然如此,每个核在任意时刻仍只有一个进程在运行),而准备运行的进程可能有多个,于是linux把就绪态和运行态合并为一个状态,叫TASK_RUNNING(运行态),系统把这些运行态的进程放在一个队列中,调度程序从这个队列中选一个进程投入运行。主要的几个状态描述如下:
1> 运行态(TASK_RUNNING)——进程是可执行的;它或者正在执行,或者在运行队列中等待执行。这是进程在用户空间中唯一可能的状态;这种状态也可以应用到内核空间中正在执行的进程。
2> 可中断(TASK_INTERRUPTIBLE)——进程正在睡眠(也就是说被阻塞),等待某些条件的达成,一旦条件成立,内核就把进程状态改为运行态。处于此状态的进程也会因为接收到信号而被提前唤醒并随时准备投入运行。
3> 不可中断(TASK_UNINTERRUPTIBLE)——与前一个状态相似,不过在接收到信号时不会被唤醒。这个状态通常在进程必须在等待时不受干扰或等待事件很快就会发生时出现,较可中断状态来说用的较少。
4> 跟踪(__TASK_TRACED)——被其他进程跟踪的进程,想想我们的调试器吧,它能很好的说明这个状态是怎么回事。
5> 停止(__TASK_STOPPED)——进程停止执行;进程没有投入运行也不能投入运行,当进程接收到如下信号时进入暂停状态。
1)、SIGSTOP——停止进程执行
2)、SIGTSTP——从终端发来信号停止进程
3)、SIGTTIN——来自键盘的中断
4)、SIGTTOU——后台进程请求输出
6> 僵死状态(EXIT_ZOMBIE)——进程执行结束但尚未消亡的一种状态。此时进程已经结束且释放大部分资源,但尚未释放其PCB。
7> 死亡(EXIT_DEAD)——最终状态,进程将被彻底删除,但需要父进程来回收。
内核经常需要调整某个进程的状态。这时最好使用set_task_state(task, state)函数:
set_task_state(task, state);
该函数将制定进程设置为指定的状态。必要的时候,它会设置内存屏障里强制其他处理器作重新排序(一般只有在SMP(对称多处理器系统)中有此必要)。否则,它等价于task->state = state。