文章目录
- 一.一套普适性的进程状态理论
- 1.运行
- 2.阻塞
- 3.挂起
- 二.一套具体的Linux进程状态
- 1.R-运行
- 2.S-睡眠
- 3.T-暂停
- 5.t-被追踪
- 三.僵尸进程和孤儿进程
- 1.僵尸进程
- 2.孤儿进程
一.一套普适性的进程状态理论
1.运行
由于CPU数量相对于进程数量来说少之又少,所以CPU维护了一个运行队列,方便管理大量等待CPU资源的进程.
同时由于CPU的处理速度很快,位于运行队列中的每一个进程都必须随时准备被运行, 所以只要位于运行队列中的进程都是处于运行状态.
值得注意的是处于运行状态的进程不一定是正在被CPU运行,但处于运行状态的进程一定是在CPU的运行队列中.
2.阻塞
我们知道磁盘等外设资源的读写速度相对于CPU相差甚远,所以当有些进程进程需要访问外设,比如数据打印到显示器,当CPU知道他们需要访问外设,CPU为了整体效率,肯定不能停下等,等待进程访问外设完后继续为其服务.
所以操作系统会把需要访问外设 (比如磁盘) 的进程从运行队列拿到内存中—磁盘所维护的等待队列中进行等待磁盘资源就绪.那么位于等待队列中的进程所处的状态就被称为阻塞状态.
值得注意的是:
-
实际上进程所处的状态取决于其所处的是何种队列
-
并非进程位于队列中,而是进程所对应的PCB位于队列中排队
-
进程所处的状态都在task_struct(PCB)中以数字含义的形式记录着
3.挂起
当大量要访问外设的进程, 都在等待外设资源就绪之时,他们是不会被立即调度, 未来可能还要等待很长的时间.
然而进程PCB和对应代码和数据都是要占用内存资源
所以操作系统就会自主地把一些等待外设资源就绪的进程所对应的代码和数据从内存换出到磁盘上,但是PCB仍然作为进程存在的标志,依旧保留在内存—-磁盘所维护的等待队列中
从而为内存节省了空间.
值得注意的是:
- 毕竟PCB依旧位于等待队列中, 所以挂起也是阻塞状态的一种
- 挂起是操作系统自主的行为,用户并不关心
二.一套具体的Linux进程状态
一套普适性的进程状态理论讲完了,让我们看看一套具体的Linux进程状态都有哪些?
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.R-运行
写一段纯计算的代码:
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 int a=0;
6 while(1)
7 {
8 a++;
9 }
10 return 0;
11 }
进程转态查看:R+
2.S-睡眠
写一段访问外设的代码/睡眠X秒的代码:
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 int a=0;
6 while(1)
7 {
8 a++;
//和上面代码区别在下面一行:打印到显示器
9 printf("a:%d\n",a);
10 }
11 return 0;
12 }
进程转态查看:S+
3.T-暂停
首先介绍一下kill:
kill -l
9号信号:SIGKILL杀进程信号
19号信号:SIGSTOP暂停信号
18号信号:SIGCONT继续信号
接着演示两种如何出现暂停状态:
从R+ => T =>R:
从S+ => T =>S:
恢复成后台进程进程,ctrl+C无法终止:
状态S+和S的区别
S+状态的进程是前台进程, 在该状态下,CTRL+C可以终止进程,shell命令不可被正常执行.
S 状态的进程是后台进程, 在该状态下,CTRL+C不可以终止进程,shell命令可以被正常执行.
4.D-深度睡眠
</给大家讲个三个人的故事了解一下即可:
我们知道, 一般用户数据都永久保存在磁盘上,因为用户数据很重要,所以磁盘的地位也很重要! 有一天一个进程A抱着一堆数据来找磁盘,让磁盘保存.
磁盘
:你把用户重要数据
从内存拿过来,但是我保存需要时间,你得等一段时间哦!
进程A把用户数据交给磁盘,并在内存中苦等
…同时内存空间不够了
操作系统
:内存空间不够,我快顶不住了,进程挂起也解决不了问题,我得杀进程
来解决问题!
操作系统看到占着茅坑不拉屎的进程A,便在不知情的情况杀掉了进程A…同时磁盘保存数据失败,来向进程A反馈,但进程A已然死亡.
磁盘
:进程A,我保存数据失败了,用户数据还给你.
但内存中并无进程A的回应, 于是磁盘将数据丢掉,继续为其他进程服务…同时用户发现数据丢失,前来问责
.
磁盘
:我只是听话照做,数据本来就可能会保存失败,罪责与我无关.
进程A
: 我也是工具人,我办事情到一半就被干掉了,我还没找操作系统算账呐.
操作系统
:用户你给我的权力啊,内存压力大,挂起都不顶用,再说我杀掉进程A我也不是针对他,我不知道他拿着重要数据嘛.
用户听了三者都无罪,于是用户定了一个新规…
用户
:我给一些重要的进程一块免死金牌
,他特殊,你再怎么样也别杀他.
磁盘,进程A,操作系统都一致称好…
了解D深度睡眠状态:
深度睡眠状态一般在企业内部做高IO或者高并发用的多,处于深度睡眠状态的进程无法被操作系统杀死,只能通过断电重启或者进程自己醒来。
5.t-被追踪
我们在使用gdb调试代码的时候,进程会处于一种被追踪状态,等待开发人员查看上下文数据,这也就是为什么我们能够调试代码的原因。
ps:
- S和D,T和t其实都是阻塞状态的一种,这就是具体的一款操作系统和抽象的操作系统理论之间的差别
- X-死亡状态,死亡的进程直接被父进程回收,Linux下没法演示,但好理解。
- D-僵尸状态/将死状态,将于<三>大概了解,后进程控制深入理解
三.僵尸进程和孤儿进程
1.僵尸进程
僵尸进程:子进程退出,其代码和数据被回收,但是PCB中保存着退出码等退出信息,PCB没有被立即回收,得等待父进程读取退出信息完再彻底死亡,子进程就是僵尸进程.
僵尸:Zombie
已故的:defunct
Linux下查看僵尸进程:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 int main()
5 {
6 pid_t pid=fork();
7 if(pid<0)
8 {
9 perror("Error\n");
10 return 0;
11 }
12 else if(pid==0)
13 {
14 printf("I am child,PID:%d,PPID:%d\n",getpid(),getppid());
15 sleep(5);
16 exit(1);
17 }
18 else
19 {
20 while(1)
21 {
22
23 printf("I am parent,PID:%d,PPID:%d\n",getpid(),getppid());
24 sleep(1);
25 }
26 }
27 return 0;
28 }
2.孤儿进程
但是如果父进程先于子进程退出,子进程退出时,谁来完成子进程的退出信息读取和资源回收呐?
孤儿进程: 父进程退出, 子进程被操作系统领养,操作系统完成子进程退出信息读取和资源回收,子进程就是孤儿进程.
Linux下查看孤儿进程:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 int main()
5 {
6 pid_t pid=fork();
7 if(pid<0)
8 {
9 perror("Error\n");
10 return 0;
11 }
12 else if(pid==0)
13 {
14 while(1)
15 {
16 printf("I am child,PID:%d,PPID:%d\n",getpid(),getppid());
17 sleep(1);
18 }
19 }
20 else
21 {
22 printf("I am parent,PID:%d,PPID:%d\n",getpid(),getppid());
23 sleep(5);
24 exit(1);
25 }
26 return 0;
27 }
父进程退出,父进程的退出信息读取和资源回收工作由bash完成
因为子进程退出的时候也有退出信息读取和资源回收工作,所以要被领养
事实证明,子进程被操作系统领养,变成S状态,前台进程变成后台进程。