文章目录
- 进程状态
- 实验观察进程的各个状态
- 动态监视进程的脚本
- 代码样本1:观察S(浅度睡眠)状态
- 代码样本2:观察R(运行)状态
- 发送`kill -19 `信号 观察T(暂停)状态:
- 调试代码,观察t(追踪)状态
- X(死亡)状态
- D(深度睡眠)状态
- Z状态(僵尸进程)
- 孤儿进程
- 进程的五态模型
进程状态
进程状态的概念
进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件的变化而转换。
而在task_struct里,其实也就是用一些变量来表示进程状态,其中state表示的就是进程的当前状态,state定义在 Linux 源文件 include/linux/sched.h 头文件中.-1 就表示不能运行,0表示运行,大于0表示停止。
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
int exit_state;
unsigned int flags;
观察kernel源代码里是怎么描述进程状态的
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列
里。
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态
Z僵尸状态(zombie):当进程退出且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程
实验观察进程的各个状态
动态监视进程的脚本
为了方便实时观察进程的状态,我们可以新开一个窗口使用一个动态监视的脚本来观察:
while :; do ps ajx|head -1 && ps ajx|grep test | grep -v grep;sleep 1; done
ps ajx|head -1
的意思就是打印进程信息的头部状态栏属性
ps ajx|grep test | grep -v grep
显式包含test的进程的信息,并过滤掉grep指令本身这个进程
while :;do 指令 ; sleep 1 done
循换while中的指令,每过一秒执行一次
代码样本1:观察S(浅度睡眠)状态
运行程序并执行脚本观察
以上结果很奇怪,在右边的窗口中,test程序一直在printf打印,可是为什么进程的状态显示是S休眠状态呢?(S+中的’+‘表示该进程在前台运行,运行时加一个&就可以使程序在后台运行)
原因是CPU的处理速度太快了,每次程序在申请打印资源的时候都需要等待。相对cpu来说,等待这个过程的时间是漫长的。而等待成功后向屏幕打印这个动作几乎是瞬间的,马上就又要去等待。所以在我们的监测脚本看来,好像一直处于休眠状态,其本质原因在于CPU的运算速度比显示器这个外设的运算速度要快得多。所以我们大概率只能看到休眠状态。
由此我们也能理解,休眠状态其实就是在等待“资源”就绪。
此外,我们能用ctrl+c能够强行终止一个在休眠的进程,这样的休眠状态也叫可中断休眠(浅度睡眠)
代码样本2:观察R(运行)状态
运行程序并执行脚本观察,R状态
果然,将打印代码注释掉之后,我们就能看到进程的运行状态R了
发送kill -19
信号 观察T(暂停)状态:
用kill -l
查看信号
发送信号暂停某个进程,继续运行代码样本2:
格式是 kill + 信号编号+进程pid
再发送kill -18 26187唤醒被暂停的进程,于是test进程的状态由T变成了R
T状态时,程序处于暂停状态,此时信息没有任何更新,是完全的暂停。处于S状态的进程会有一些数据更新,比如睡眠了多少秒等。
那么t状态和T状态有区别吗?
答案是有的。
调试代码,观察t(追踪)状态
t状态表示跟踪状态,这个状态有点特殊,常在调试中遇见:
当我们打好断点进行调试,程序在遇到断点处就会暂停变为追踪状态。此时的进程在等待gdb进程对他进行操作。
和T状态不同的是,t状态不能被kill -18 信号唤醒, 只能等到调试进程通过ptrace系统调用执行PTRACE_CONT、PTRACE_DETACH等操作 (通过ptrace系统调用的参数指定操作), 或调试进程退出,被调试的进程才能恢复R状态。
X(死亡)状态
表示一个进程死亡,CPU会进行资源的回收。由于这个状态比较瞬时,难以观察到。
D(深度睡眠)状态
D(disk sleep),深度睡眠状态,又叫不可中断睡眠状态。跟S状态类似,都是在休眠等待”资源“,但是D状态不能被中断。也就意味着用kill发送中断信号对它也没用。一旦进入了D状态,就只能被资源唤醒(或者重启、断电)。
Z状态(僵尸进程)
Z状态又叫僵尸状态。表示一个终止了但是还未被回收的进程的状态。
当一个进程由于某种原因终止时,内核斌并不是立即把他从系统中清除。相反,进程被保持在一种已终止的状态中,知道被父进程回收。当父进程回收已终止的子进程时,内核就会把子进程的退出状态传递给父进程,然后抛弃已经终止的进程,这个时候子进程就不存在了。
为什么已经终止的子进程被称为僵尸进程?
在民间的传说中,僵尸是活着的尸体,一种半生半死的实体。而僵尸进程已经终止了,内核还在保留着他的退出信息知道父进程回收为止,与僵尸类似。
给出以下代码观察僵尸状态
运行脚本监测
观察代码我们可以知道,子进程被创建后休眠五秒后马上就return;此时的子进程已经被终止,但是并没有马上释放所有资源,而是等待父进程的回收。父进程这个时候陷入了较长的休眠状态,并没有马上回收子进程。于是导致了子进程称为了僵尸进程。如果没有父进程去处理这些退出信息,僵尸进程就会一直存在,其部分未释放的资源也一直存在。也就造成了内存泄漏。
孤儿进程
与僵尸进程相反,如果父进程先终止,那该父进程的子进程就会成为孤儿进程。
成为孤儿进程后,内核会安排init进程成为它的孤儿进程的养父。init进程的PID为1,是在系统启动的时候由内核创建的,他不会终止,是所有进程的祖先。如果父进程还没来得及回收僵尸子进程就终止了,init进程也会去回收它们。不过长时间运行的程序,比如shell或者服务器,总是应该回收它们的僵尸子进程。即使僵尸子进程没有运行,它们也消耗内存资源。
观察以下代码
当父进程终止,子进程成为孤儿进程,被init领养
进程的五态模型
上面的七种进程状态可以总结为三种:运行、阻塞、挂起。
是不是非常熟悉呢,这就是我们教科书上经常看到描述进程状态的三种状态。
其中运行态R分为两个部分:
一个是真正在CPU中执行的进程状态(运行态)
一个是在CPU的运行队列中但是还没有进入CPU的进程状态(就绪态)
这两种状态都可以被称为运行态。
挂起状态通常发生在内存不足的情况下,系统会将暂时用不到的一部分进程资源换出到磁盘里面,此时进程状态为S,等内存不在那么紧张的时候再唤醒该进程,并重新将资源加载到内存里面。整个过程比较消耗时间,是一种用时间换空间的做法。