持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第25天,点击查看活动详情
💦 进程状态
1、Linux 2.6内核源码
后期我们主要也是以 Linux 2.6 为主来学习,因为它匹配的书籍较多。
其中 task_state_array[]
里描述的是 Linux 的进程状态:
2、R (running)
进程是 R 状态,一定在 CPU 上运行 ❓
进程在运行队列中,就叫做 R
状态,也就是说进程想被 CPU 运行,前提条件是你必须处于 R
状态,R
:我准备好了,你可以调度我。
为啥我在跑,但状态却是 S ❓
因为代码大部分时间是在 sleep 的,且每次 1 秒钟,其次 printf 是往显示器上输出的,涉及到 I/O,效率比较低,一定会要求进程去等我们把数据刷新到显示器上。所以综合考量,我们这个程序可能只有万分之一的时间在运行,其它时间都在休眠,站在用户的角度它是 R,但是对于操作系统来说它不一定是 R,它有可能在队列中等待调度。
如果我们就想看下 R 状态呢 ???
循环里啥都不要做。
3、S (sleeping)
休眠状态(浅度休眠,大部分情况)
。这种休眠是可被换醒的,我们可以 Ctrl + C 退出循环,而此时的进程就没了,也就是说它虽然是一种休眠状态,但是它随时可以接收外部的信号,处理外部的请求。
4、D (disk sleep)
休眠状态(深度休眠)
。
此时进程拿着一批数据找到了磁盘说:磁盘,你帮我把数据放在你对应的位置。磁盘说:好嘞,然后磁盘就慢慢地写到对应的位置。此时进程处于等待状态,它在等把数据写完,然后告诉进程写入成功 or 失败。此时操作系统过来说:你没发现现在内存严重不足了吗,我现在要释放一些闲置的内存资源,随后就把进程干掉了。磁盘写失败后,然后跟进程说:不好意思,我写失败了,然而进程已经挂了,此时我们的数据流向就不确定了。
对于上面的场景,这个锅由谁来背 —— 操作系统/内存/磁盘 ❓
于是它们三方开始了辩论:
操作系统说,你在那等,我又不知道你在等啥,系统内存不足了,我就尽我的职责,我的工程师就是这样写我的,杀掉闲置的内存。假如我这次不杀你,那你说下次我再遇到一些该杀死的闲置的内存,我怕我又被责怪,所以没杀,你就认为我不作为 ?操作系统说:我又识别不了哪些进程是重要或不重要的。
磁盘说,我就是一个跑腿的,你们让我干啥就干啥,又不是写入的结果不告诉你,而是你不在了。
进程说,我在那规矩的等着呢,是有人把我杀了,我自己也不想退出。
这里好像谁也没有错,但是确实出现了问题,你难道说错的是用户 —— 内存买小了吗 ?无论是操作系统、内存、磁盘都是为了给用户提供更好的服务。根本原因是操作系统能杀掉此进程,如果让操作系统不能杀掉此进程就可以了。我现在做的事情很重要,即便操作系统再牛,也杀不了我,你系统内存不够了,你想其它办法去,不要来搞我。所以我们针对这种类型的进程我们给出了 D 状态,所以操作系统从此就知道了以后 D 是个大哥,不能搞。
所以对于深度睡眠的进程不可以被杀死,即便是操作系统。通常在访问磁盘这样的 I/O 设备,进行数据拷贝的关键步骤上,是需要将进程设置为 D 的,好比 1 秒钟内,平台有 100 万的用户注册,如果数据丢失,那么带来的损失是巨大的。
对于深度睡眠的进程怎么结束 ???
只能等待 D 状态进程自动醒来,或者关机重启,但有可能会卡住。深度睡眠的进程我们没法演示,万一把自己的机器玩挂了,成本较高。
不管是浅度睡眠还是深度睡眠都是一种等待状态,因为某种条件不满足。
5、T (stopped)
对于一个正在运行的进程,怎么暂停 ❓
使用 kill -l
命令,查看信号,这里更多内容后面我们再学习:
使用 kill -19 27918
命令,给 27918 进程发送第 19 号信号来暂停进程:
使用 kill -18 27918
命令,给 27918 进程发送第 18 号信号来恢复进程:
我们也可以认为 T 是一种等待状态。
6、T (tracing stop)
当你使用 vs of gdb 调试代码,比如你打了一个断点,然后开始调试,此时在断点处停下来的状态就是 t,这里是为了和上面进行区分。这里先不细谈。
7、Z (zomble)
比如你早上去晨跑时,突然看到其他跑友躺地上,你虽然救不了人,也破不了案,但是作为一个热心市民,可以先给 120 打电话,再给 110 打电话。随后警察来了,第一时间肯定不会把这个人抬走,清理现场,如果是这样的话凶手肯定会笑开花,第一时间肯定是先确定人是正常死亡还是非正常死亡,如果是非正常死亡,那么立马封锁现场,拉上警戒线,判断是自杀的还是他杀。随后 120 来了,对人的状态进行判断,如果是正常死亡,就判断是因为疾病,还是年纪大了。最终判断出人是是因为疾病离开的,警察和医生的任务已经完成后,不会就把人放这,直接撤了。而是把人抬走,恢复地方秩序,然后通知家属。所以当一个人死亡时,并不是立马把这个人从世界上抹掉,而是分析这个人身上的退出信息,比如说体态特征、血压等信息来确定具体的退出原因。
同样进程退出,一般不是立马让 OS 回收资源,释放进程所有的资源,作为一个死亡的进程,OS 不会说你已经死了,就赶紧把你拉到火葬场,而是 OS 要知道进程是因为什么原因退出的。创建进程的目的是为了完成某件任务,进程退出了,我得知道他把我任务完成的怎么样了,所以 OS 在进程退出时,要搜集进程退出的相关有效数据,并写进自己的 PCB 内,以供 OS 或父进程来进行读取。只有读取成功之后,该进程才算真正死亡,此时我们称该进程为 死亡状态 X
,。其中我们把一个进程退出,但还没有被读取的那个时间点,我们称该进程为 僵尸状态 Z
。
我作为父进程 fork 创建一个子进程,子进程死亡了,但父进程没通过接口让 OS 回收,此时子进程的状态就是 Z。
僵尸状态演示 ❓
这里我们可以写一个监控脚本 while :; do ps ajx | head -1 && ps ajx | grep mytest; sleep 1; echo "####################"; done
来观测:
8、X (dead)
参考 Z 状态。其次 X 状态我们看不到,因为我们释放了,它是一瞬间的。
💦、补充说明
1、 S and S+
一般在命令行上,如果是一个前台进程,那么它运行时状态后会跟 +。
也就是说前台进程一旦执行,bash 就无法进行命令行解释,ls、top 等命令都无法执行,只有 Ctrl + C 来进行终止:
如果想把一个进程放在后台可以 ./mytest &
:
其中对于后台进程,bash 可以对命令行解释:
但是你会发现 Ctrl + C 无法终止后台进程,只能对该进程发送第 9 号信号来结束进程:
2、 OS 描述的状态 and 具体的 Linux 进程状态
其中新建没有对应的 Linux 进程状态;就绪可对应到 Linux 进程中的 R;运行也可对应到 Linux 进程中的中的 R;退出可对应到 Linux 进程中的 Z/X;阻塞可对应到 Linux 进程中的 S/D/T;
所以 Linux 状态的实现和操作系统的实现是有点差别的。操作系统的所描述的概念是所有操作系统都遵守这样的规则,而 Linux 就是一种具体的操作系统规则。
3、僵尸进程的危害
- 进程的退出状态必须被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于 Z 状态。
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在
task_struct(PCB)
中,换句话说,Z 状态一直不退出,PCB 一直都要维护。 - 那一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费,因为数据结构对象本身就要占用内存,想想 C 中定义一个结构体变量(对象),就是要在内存的某个位置进行开辟空间。
- 内存泄漏。
- 如何避免,后面再谈。
4、孤儿进程
父进程如果提前退出,那么子进程就是 孤儿进程
,那么子进程退出,进入 Z
之后,该怎么处理 ❓
可以看到 5 秒前有 2 个进程,5 秒后父进程死亡了(这里没有被僵尸的原因是父进程也有父进程 23178 -> bash),只有 1 个子进程。这里我们称没有父进程的子进程为孤儿进程
,此时孤儿进程
会被 1号进程
领养,它是 systemd(操作系统),此时操作系统就可以直接对我回收资源。 且进程状态会由前台转换为后台,后台进程可以使用 第 9 号信号
来结束进程。
5、1 号进程
操作系统启动之前是有 0号进程
的,只不过完全启动成功后,0号进程
就被1号进程
取代了,具体的取代方案,后面学习 进程替换
时再谈。可以看到 pid 排名靠前的进程都是由 root 来启动的。注意在 Centos7.6 下,它的 1号进程
叫做systemd
,而 Centos6.5 下,它的 1号进程
叫做initd
。