目录
- 前言
- 1. T && t 状态
- 2. X 与 Z 状态
- 3. 孤儿进程
前言
继上一篇文章 进程状态(二)----- linux 中具体的进程状态(上) 介绍了 linux 系统中具体的 R、S、D 状态,而这篇文章继续介绍 linux 系统中剩下的三种状态,T && t 状态,X 与 Z 状态,以及孤儿进程的由来。
/*
* 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 */
};
1. T && t 状态
T 和 t 状态,我们放在一块讲,因为在如今的 linux 系统中,这两个基本已经没有什么区别了。所谓 T 状态,就是 stop,就是让进程停下来。
当我们进程跑起来的时候,我们可以通过如下指令,将进程暂停一下。
[outlier@localhost process2]$ kill -l1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX 其中的 19 信号就是将进程暂停,18 信号恢复进程(恢复之后的进程会变为后台进程,需要通过 kill 指令杀掉进程才能够关闭)
大家会不会有一个疑问就是,一个进程处于暂停状态 和 一个进程处于 sleep 状态,有什么区别吗?在最直观的理解上,这两种进程都是不被 cpu 执行的进程。所以为什么进程要暂停下来呢?它是不是也像 sleep 一样,在等待某种资源的就绪呢?
面对这类问题,我们可以这样去解读,有时候我们就是想要暂停一下这个进程,等待其它事件的发生,而非等待某种资源的就绪,我们可以将其设置为 T 状态,而 T 状态下,进程的代码就完全暂停了,一般而言也不接受其它信号了,并且 T 状态在某种程度上也算是一种阻塞状态。与 sleep 不同的是,sleep 一定是在等待某种资源,但是 stop 状态不一定是在等待资源,也可以是人为控制下的暂停。
那么为什么我们要暂停一个进程呢? ---- gdb 调试的时候,是不是就是 gdb 帮助我们暂停进程的?
借助 gdb,进程运行到断点处停下来了,此时的进程状态从 sleep 变为了 stop(只不过是 小 t 的暂停状态)。所以凭什么你运行到断点处,你的代码就很乖的停下来了,这一切不都是 gdb 这个调试工具帮你做的吗,而这种场景就是暂停进程的应用场景之一。、
2. X 与 Z 状态
X(dead) 状态,字面意思直接拿捏,它就是死亡状态!一个进程处于死亡状态,是什么含义?不就是这个进程因为各种原因然后停止运行了,而这个状态只是一个返回状态,也无法在任务列表里看到这个状态。
但是进程在进入 dead 状态之前,背后可是存在着一系列的事情,简单说,那就是,进程因为某种原因挂掉了,它并不会马上进入 X 状态,而是先进入 Z 状态,即僵尸状态。
那么背后是怎么一个事呢?讲个故事。
有一天早晨,张三和往日一样进行着晨跑。途中,有一个看起来较为专业的哥们,从张三的身边一闪而过,张三一开始也没多想,还是按照自己的节奏跑着。不久后,张三突然发现前面有个人 “躺平” 了,正好是刚刚一闪而过那哥们。张三哪见过如此场面,直接先后拨打了120,110。等待救护车来临之后,做了急救措施,最终宣判那哥们 dead 了,而此时帽子叔叔也已经赶到。所以既然现在人也已经彻底躺平了,于是救护人员也就离开。此刻,这个哥们已经彻底躺平是事实,但是帽子叔叔并没有宣布其躺平的消息,也没有立刻通知哥们的家属对其进行回收,而是叫了两法医对那哥们进行鉴定(判断是自杀?ta 杀?意外躺平?),毕竟好端端的一个人,突然在大庭广众之下彻底躺平,总得给社会一个交代吧?直到法医鉴定完毕,确定那哥们的躺平原因之后,帽子叔叔才正式通知其家属来认领并回收那哥们。
而在张三看到那哥们躺平开始,一直到法医鉴定躺平原因这段期间内,那哥们就一直需要在那躺平着,此时这个人的状态就称为 僵尸状态!当帽子叔叔那边鉴定完这哥们躺平的原因之后,准备通知家属进入到回收阶段了,那么这个人的状态就称为死亡状态。
所以进程也如此,一个进程退出了,并不是立刻进入 dead 状态,而是操作系统会将该进程的退出信息维持一段时间,以让关心这件事的主体得知该进程退出的原因等信息。所以对一个已经退出的进程,操作系统在维护其退出信息的这段时间内,这个进程的各种资源还没有得到释放的,我们就把这种进程称为 僵尸进程!
而一个进程退出之后,那么最关心这个进程的肯定是他的父进程。假设今天有一对父子进程,子进程退出了,但是它的父进程迟迟没有来 “关心” 它,自顾自的运行着,那么操作系统就必须把子进程的状态一直维持着,直到父进程前来回收子进程。所以再次强调,我们把这种已经死掉的进程,但是又需要有父进程前来回收的这种进程的状态,称为Z 状态,即僵尸状态。 到这里,讲了这么多,那总得见一见僵尸进程吧?
int main()
{pid_t pid = fork();if(pid == 0){// 子进程int cnt = 5;while(cnt){cout << "I am a child process, pid: " << getpid() << ", ppid: " << getppid() << ", cnt: " << cnt << endl;cnt--;sleep(1);}exit(0);}else{// 父进程while(1){cout << "I am a parent process, pid: " << getpid() << ", ppid: " << getppid() << endl;sleep(1);}}
}
在这次测试中,我们先退出了子进程,而在退出前,父子进程绝大部分时间都处于 sleep 状态,当子进程退出后,由于父进程并没有对子进程进行回收,因此子进程所呈现出来的状态并不是 dead 状态,而是 zombie 僵尸状态。
所以一个进程退出之后,没有及时被其父进程主动回收,这个进程资源就会一直被维持在内存当中,而这种进程既然已经变成 Z 状态了,那么它就一定不会被操作系统调度了,更不会被运行。所以这种问题就会导致 内存泄漏!而为什么在我们关闭我们的 test 进程之后,其创建出来的子进程变为 Z 状态(这现在我可以理解,因为父进程没有主动回收),那为什么其父进程却并没有呈现 Z 状态呢?而是直接退出了,难道是父进程的父进程对其进行了回收吗?
3. 孤儿进程
没错,在运行 test 这个程序之后,bash 这个进程会自动为 test 创建出进程,而 bash 作为 test 的父进程,当 test 这个进程退出时,bash 就有对它进行回收的义务!而事实就是如此,子进程是 test 这个进程创建出来的,但当子进程退出时,由于父进程并没有对其进行关心,因此该子进程的退出信息就要一直被维护下去(退出信息也是数据,保存在 task_sturct 中,也相当于在维护 PCB)。
那相反过来呢?如果是父进程退出了,子进程还在继续运行会是怎么样的现象?父进程提前退出,那子进程退出后变为 Z 状态,那是如何处理的呢?
int main()
{pid_t pid = fork();if(pid == 0){// 子进程int cnt = 500;while(cnt){cout << "I am a child process, pid: " << getpid() << ", ppid: " << getppid() << ", cnt: " << cnt << "\n";cnt--;sleep(1);}exit(0);}else{// 父进程int cnt = 5;while(cnt){cout << "I am a parent process, pid: " << getpid() << ", ppid: " << getppid() << ", cnt: " << cnt << "\n";cnt--;sleep(1);}exit(0);}
}
当父进程先退出了,留下子进程进行在跑的现象是,父进程退出那一刻,就立马被它的父进程(即 bash 回收了),所以也就检测不到父进程的进程信息了,但是子进程的 ppid 不再是一开始的父进程,而是变为了 1,无脑猜测,这个 pid 为 1的进程,就是我们的操作系统(也即 init 进程)!而我们把上述这种情况,父进程先退出,子进程就称之为“孤儿进程”!
所以现在也就可以回答一开始提出的问题了。父进程先退出,子进程就会被 1号 init 进程领养,所以当子进程退出,变为 Z 状态,自然就是由 1 号进程对其进行回收。而为什么要领养这个孤儿进程?如果不领养孤儿进程,那么当这些进程退出时,没有进程可以为其回收,释放系统资源,当这些进程越来越多,也就代表着系统中被占用的无用资源越来越多!所以必须要对孤儿进程进行领养,然后在其退出时将其资源释放回收。
到这里,linux 中常见的几个进程状态就已经交代完毕了,但是关于进程这个概念远远还没有结束。截止目前,我们知道了进程属性中的标识符(即 pid),知道了进程的状态(R, S, D, T, t, X, Z)。但是进程中还有一个 优先级 的概念,同样是进程中相当重要的组成信息,也会在之后的文章中进行介绍。
如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!
感谢各位观看!