目录
[一. 概念铺设](#一. 概念铺设)
[二. 传统操作系统状态](#二. 传统操作系统状态)
[1. 运行](#1. 运行)
[2. 阻塞](#2. 阻塞)
[3. 挂起](#3. 挂起)
[三. linux 中的进程状态](#三. linux 中的进程状态)
[1. 总体介绍](#1. 总体介绍)
[2. R](#2. R)
[3. S](#3. S)
[4. D](#4. D)
[kill -9](#kill -9)
[D vs S](#D vs S)
[5. T](#5. T)
[T vs S](#T vs S)
[6. Z](#6. Z)
[7. D](#7. D)
[8. 孤儿进程](#8. 孤儿进程)
一. 概念铺设
状态是什么?
状态就是一个用来描述该进程当前正在做什么。
传统的操作系统的状态有:
- 运行
- 阻塞
- 挂起
传统操作系统的状态转换图
二. 传统操作系统状态
1. 运行
我们知道,在我们现在的操作系统中有许多进程(我们现在的系统多数都是时间片轮转调用的),但是一般的电脑只有一个 CPU ,而进程要运行必须要使用 CPU ,所以在同一时刻肯定是只有一个进程占用CPU资源,但是在用户的观感上好像我们计算机上的进程都是同时在运行的。
实际上,在操作系统中有一个队列叫做运行队列,而在运行队列中的进程就是运行态,并不是说只有当进程在运行的那一刻才是运行状态
所以运行状态就是在运行队列里面等待的进程就是运行状态,对于时间片轮状的操作系统来说,每一个进程每一次只执行特定长的时间,如果执行没有结束就继续到等待队列的后面继续排队,如果执行完毕,那么就从等待队列中删掉。
2. 阻塞
阻塞也是传统的操作系统书中的一个状态,而阻塞就是当一个进程需要某种外设资源的时候的状态。
比如说,当我们使用C语言进行 scanf 的时候,或者使用 C++ cin 的时候,此时就是阻塞状态,就是该进程需要等待键盘资源输入,或者也有就是向显示器上打印等...
3. 挂起
挂起状态时常不会用到的,挂起状态就是当操作系统内资源不足的时候,当一个进程不运行,但是占着操作系统资源,然后此时操作系统还时资源紧张,那么操作系统会将该进程的代码和数据换到磁盘里面,当该进程运行的时候才会将该进程的代码和数据继续换入到内存中,所以挂起就是一个进程的PCB在内存中,但时它的代码和数据被换到磁盘里面就是挂起状态。
三. linux 中的进程状态
1. 总体介绍
- 运行 (R):进程正在运行或已经准备好运行。
- 休眠 (S):进程处于休眠状态,等待某个事件的发生,如I/O操作或信号。
- 中断 (D):进程处于不可中断的睡眠状态,通常是等待设备或资源,如磁盘I/O。
- 僵尸 (Z):进程已经终止,但是其父进程还未收到终止信号或未进行处理,进程存在但没有参与运行。
- 停止 (T):进程被暂停或停止,通常是由于收到停止信号,如Ctrl+Z。
- 僵尸 (X):进程已经终止,但是其父进程已经终止或退出,僵尸进程被init进程(PID为1)接管。
2. R
在 linux 中的 R 状态就是对应的是运行状态。
那么下面就写一个代码来查看该进程的状态。
cpp
int main()
{
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
我们还是使用 ps 来查看进程的状态。
bash
[lxy@hecs-165234 linux3]$ ps axj | head -1 && ps axj | grep proc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
6468 8145 8145 6468 pts/1 8145 S+ 1000 0:00 ./proc
这里看到,我们查到该进程的状态是 S,为什么呢?因为我们之前说过,我们的进程一般在runQueue里面才算是执行,那么当我们打印的时候我们需要等待外设(显示器),然后由于外设比较慢,所以大多数时间都是再等待,所以我们就看到的是S状态,并不是R。
所以这时候我们可以去掉打印,我们一直死循环,我们就可以看到 R 状态了。
cpp
int main()
{
while(1);
return 0;
}
bash
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
6468 8453 8453 6468 pts/1 8453 R+ 1000 0:02 ./proc
这时候查看到的就是 R 状态,这里为什么是R+ 呢? 这里的+表示在前台运行,也就是在 bash 前端运行。
3. S
s状态其实我们刚才已经看到了,就是打印的时候我们一直看到的是S状态,这是因为我们在等待显示器资源。
4. D
D状态其实也就是睡眠状态,那么和S状态有什么不同呢?
其中S是浅度睡眠,而D是深度睡眠。
浅度睡眠是可以被杀死的,而深度睡眠不能被杀死。
这里穿插一条知识。
kill -9
kill 是信号,kill -9 是一条杀死进程的信号,后面加进程PID 就可以向进程发送该信号,而D状态就是不能被杀死。
D vs S
D状态和S状态的区别?
场景:
当一个进程向磁盘写入很大的数据的时候,该进程在等待,那么这个时候如果操作系统直接杀死该进程,那么向磁盘中写入数据后失败或者成功,磁盘都会向该进程反馈,但是此时操作系统杀死了该进程,如果写入失败,导致磁盘数据被丢失,那么后果还是很严重的,所以在进程向磁盘写入数据的时候,进程等待磁盘返回的时候,此时的进程就是处于D状态也就是胜读睡眠状态,这时候的进程不能被杀死。
由于这个状态不好演示,所以这里也就不演示了。
5. T
T状态就是停止状态,就是该进程停止了,我们也可以通过信号来控制。
bash
[lxy@hecs-165234 linux3]$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) 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
[lxy@hecs-165234 linux3]$ ps axj | grep proc
6468 8501 8501 6468 pts/1 8501 R+ 1000 0:26 ./proc
6180 8503 8502 6180 pts/0 8502 R+ 1000 0:00 grep --color=auto proc
[lxy@hecs-165234 linux3]$ kill -19 8501
kill
kill -l 是查看信号的命令,19 号信号是一个告诉进程停止的信号,所以我们向我们的进程发送19号信号,然后我们在继续查看该进程的状态。
cpp
[lxy@hecs-165234 linux3]$ ps axj | grep proc
6468 8501 8501 6468 pts/1 8504 T 1000 0:54 ./proc
此时就是T状态,那么我们怎么恢复呢? 我们使用18号信号,这里也就不演示了,但是这里说明一下,如果从18号信号恢复过来的话,就变成后台运行了,所以我们 ctrl + c就干不掉了,我们需要使用 kill -9 来杀死它。
T vs S
T和S状态的区别,我们这里感觉T 和 S没有什么区别,但是还是有区别的,S状态就是进程在等待外设资源的一种状态,而T是可能在等待资源,但是也不一定是在等待资源。
6. Z
Z 是僵尸状态。
什么是僵尸状态?
场景:国外发生的一起凶杀案,现在有人看到并且报警,此时警察过来首先先检验受害人是否死亡,或者死亡原因,当检验完毕后,并且警方处理完了所有的事情然后等待受害者家属来为受害者进行后续操作,在受害者家属来之前,受害者就是属于僵尸状态。
所以当一个进程死亡后,如果关心它的进程(也就是父进程)没有来接收子进程的返回的数据等,那么此时的子进程就是僵尸状态。
我们下面让子进程运行5秒后退出,然后父进程持续运行,我们来观察此时的状态。
cpp
int main()
{
pid_t id = fork();
if(id == 0)
{
int count = 5;
while(count--)
{
printf("I am a child... PID: %d, PPID: %d, time: %d\n", getpid(), getppid(), count);
sleep(1);
}
}
else
{
while(1)
{
printf("I am a father... PID: %d, PPID: %d\n", getpid(), getppid());
sleep(1);
}
return 0;;
}
我们此时在写一个监控脚本来看这里的变化。
bash
[lxy@hecs-165234 linux3]$ while : ; do ps axj | head -1 && ps axj | grep proc | grep -v grep; sleep 1; done
这时候我们这个脚本就会查看 proc 的PCB
bash
[lxy@hecs-165234 linux4]$ ./proc
I am a father... PID: 9542, PPID: 6468
I am a child... PID: 9543, PPID: 9542, time: 4
I am a father... PID: 9542, PPID: 6468
I am a child... PID: 9543, PPID: 9542, time: 3
I am a father... PID: 9542, PPID: 6468
I am a child... PID: 9543, PPID: 9542, time: 2
I am a father... PID: 9542, PPID: 6468
I am a child... PID: 9543, PPID: 9542, time: 1
I am a father... PID: 9542, PPID: 6468
I am a child... PID: 9543, PPID: 9542, time: 0
I am a father... PID: 9542, PPID: 6468
I am a father... PID: 9542, PPID: 6468
bash
6468 9542 9542 6468 pts/1 9542 S+ 1000 0:00 ./proc
9542 9543 9542 6468 pts/1 9542 S+ 1000 0:00 ./proc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
6468 9542 9542 6468 pts/1 9542 S+ 1000 0:00 ./proc
9542 9543 9542 6468 pts/1 9542 S+ 1000 0:00 ./proc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
6468 9542 9542 6468 pts/1 9542 S+ 1000 0:00 ./proc
9542 9543 9542 6468 pts/1 9542 S+ 1000 0:00 ./proc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
6468 9542 9542 6468 pts/1 9542 S+ 1000 0:00 ./proc
9542 9543 9542 6468 pts/1 9542 S+ 1000 0:00 ./proc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
6468 9542 9542 6468 pts/1 9542 S+ 1000 0:00 ./proc
9542 9543 9542 6468 pts/1 9542 S+ 1000 0:00 ./proc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
6468 9542 9542 6468 pts/1 9542 S+ 1000 0:00 ./proc
9542 9543 9542 6468 pts/1 9542 Z+ 1000 0:00 [proc] <defunct>
这里看到,此时我们最后子进程退出了,然后父进程还在运行,这是子进程就是Z僵尸状态。
僵尸进程的危害
那么进程一直僵尸会怎么样呢?
- 浪费系统资源:每个进程都需要一定的系统资源,如内存和描述符。当大量的僵尸进程存在时,会占用过多的系统资源,影响系统的正常运行和其他进程的执行效率。
- 产生内核问题:僵尸进程过多可能会导致系统的内核出现问题。尽管现代操作系统可以处理一定数量的僵尸进程,但如果没有及时清理,可能会导致内核资源不足或崩溃。
- 父进程遭受拖累:僵尸进程的父进程可能会受到影响。如果父进程没有正确处理僵尸进程,它们的退出状态将一直保存在内核中,导致父进程的进程表中存在大量的僵尸进程。
虽然僵尸进程本身不会对系统造成直接的危害,但当过多的僵尸进程堆积时,会消耗系统资源并可能导致系统不稳定。因此,及时清理僵尸进程非常重要。一般来说,操作系统的init进程会负责清理僵尸进程,当子进程退出后,init进程会接管并回收僵尸进程的资源。
7. D
D状态就是死亡 dead 状态,也就是退出状态,而在每一个D状态之前都会有一段时间是Z状态的,因为这里的父进程要回收子进程。
父进程的作用
所以这里就体现了为什么要有父进程,因为父进程要对退出的子进程进行回收。
8. 孤儿进程
孤儿进程,见名知意,就是没有父进程的进程,我们下面演示一下。
我们写一个代码,让子进程一直运行,让父进程提前退出,然后我们打开监控脚本看一下。
cpp
int main()
{
pid_t id = fork();
if(id == 0)
{
while(1)
{
printf("I am a child... PID: %d, PPID: %d\n", getpid(), getppid());
sleep(1);
}
}
else
{
int count = 5;
while(count--)
{
printf("I am a father... PID: %d, PPID: %d\n", getpid(), getppid());
sleep(1);
}
}
return 0;;
}
运行,打开监控脚本查看。
bash
[lxy@hecs-165234 linux4]$ ./proc
I am a father... PID: 9780, PPID: 6468
I am a child... PID: 9781, PPID: 9780
I am a father... PID: 9780, PPID: 6468
I am a child... PID: 9781, PPID: 9780
I am a father... PID: 9780, PPID: 6468
I am a child... PID: 9781, PPID: 9780
I am a father... PID: 9780, PPID: 6468
I am a child... PID: 9781, PPID: 9780
I am a father... PID: 9780, PPID: 6468
I am a child... PID: 9781, PPID: 9780
I am a child... PID: 9781, PPID: 1
bash
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
6468 9780 9780 6468 pts/1 9780 S+ 1000 0:00 ./proc
9780 9781 9780 6468 pts/1 9780 S+ 1000 0:00 ./proc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
6468 9780 9780 6468 pts/1 9780 S+ 1000 0:00 ./proc
9780 9781 9780 6468 pts/1 9780 S+ 1000 0:00 ./proc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 9781 9780 6468 pts/1 6468 S 1000 0:00 ./proc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 9781 9780 6468 pts/1 6468 S 1000 0:00 ./proc
这里还是只展示部分。
我们看到我们的父进程退出了,然后子进程的PPID变成了1,那么1是什么呢?
bash
[lxy@hecs-165234 linux3]$ ps axj | head -1 && ps axj | grep 1
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 1 1 1 ? -1 Ss 0 26:38 /usr/lib/systemd/systemd --system --deserialize 23
这里看到我们的1就是system 所以我们的子进程就被系统领养,此时我们的子进程的退出就由操作系统回收了。