Hello,大家好,本文我们所要介绍的是有关Linux下的进程状态
task_ struct内容分类
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
其实那么多的状态,真正主要的也就那么几个,所以接下去我会中重点讲解以下几种进程的状态
首先我们要谈到的是【运行状态】,这个状态是最普遍的
因为每个进程是需要去竞争CPU资源的,但是呢CPU不可能同时给这么多进程分配资源
head
所指向就是第一个进程所对应的【task_struct】,队尾指针tail
所指向就是最后一个所对应的【task_struct】。所以我们要运行某一个进程只需要将 head 所指向的那个进程放到CPU上去运行即可提问:一个进程只要把自己放到CPU上开始运行了,是不是一直要到执行完毕,才把自己放下来?
所以呢我们不要拿自己的时间感受去衡量CPU,其运行一遍速度是非常快的,你根本感受不到这种进程切换的效果
在介绍完【运行状态】后,我们再来讲讲【阻塞状态】
type
代表这是一个什么类型的硬件、status
代表这个硬件当前在操作系统中所处的状态是什么样的,wait_queue
代表的则是等待队列的指针wait_queue
那有同学可可能会疑惑这为什么叫做【等待队列】呢?明显只有一个进程鸭
最后我们再来讲讲一种状态叫做【挂起状态】
要怎么去省呢?
那有的同学说:为什么要这样去做呢?这样做有什么意义?
在介绍完操作系统学科下的三种最主要进程状态后,我们对进程的状态有了基本的概念,接下去就让我们正式地来学习一下Linux系统下7种进程状态
先来小结并回顾一下上面所学:
以下就是关于进程的所有状态
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】
printf
打印循环语句 1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main(void)
5 {
6 while(1);
7 {
8 printf("hello bit\n");
9 }
10
11 return 0;
12 }
S+
,不过呢读者想看到的应该是R
才对printf
打印语句了,而且直接使用一个while(1)
去做循环 1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main(void)
5 {
6 while(1);
7 //{
8 // printf("hello bit\n");
9 //}
10
11 return 0;
12 }
R+
,这才是我们想要的【进程状态】
那有读者就要问了:为什么把printf
打印语句给去掉之后就变成这样了呢?
printf
打印语句它是属于IO流的一种,第一次因为是循环的缘故,它一直在等IO设备就绪,所以其进程状态就一直为S+
,对应的即是在操作系统中的【阻塞状态】;但是当我们去掉printf
这种IO流之后呢,它就是在纯纯运行,没有IO,那也就变成了R
状态这里再补充说明一下这个
S
和R
后面的+
R+
代表的就是这个进程是在前台运行的,所以我们在输入任何指令后不会对其造成 任何的影响./myproc
的后面加上了一个&
;那么其状态变成了R
,此代表的意思就是这个进程它是运行在了【后台】的接下去我们再来介绍一下Linux下的睡眠状态S
1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main(void)
5 {
6 while(1)
7 {
8 sleep(1);
9 printf("hello bit\n");
10 }
11
12 return 0;
13 }
S+
睡眠状态
那有同学就很疑惑,这个进程不是在运行吗?为什么不是R
状态呢?
S+
这个睡眠状态。其实呢,这和外设与CPU的速度之差是有关系的,CPU那个运行速度是极其快速的S+
;因为有99.99%
的时间都在进行等待,而只有0.01%
的时间在运行,它们之间简直可以说是数量级别的差别 1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main(void)
5 {
6 int a = 0;
7 printf("Enter# ");
8 scanf("%d", &a);
9
10 printf("echo : %d\n", a);
11 return 0;
12 }
S+
的状态,因为【shell】此时正在等待用户的输入,这个就对应到了我们上面所讲到的 阻塞状态bash
,也可以算是一种【阻塞状态】,一直等待着我们去输入命令行,一旦有了的话它就进行读取除了【浅度睡眠】之外呢,还有一种叫做【深度睡眠】,它们俩呢,都是 阻塞状态
kill -9 8664
向这个进程发送【9号信号】,那么这个进程就被杀死了,你也可以认为被唤醒了好,接下去呢我就通过一个故事来描述一下这个【深度睡眠】到底是怎样一种状态
所以呢,同学们,我们要明白这么一个道理:操作系统认为当前系统能行的话就直接让用户去用就可以了,如果扛不住了就置换页表,实在不行了就去会杀进程
那因为随着这份重要数据的丢失呢,就引发了一场官司
以下呢,是三个人的辩词
【操作系统】:
操作系统听了这番说辞后心里想了想,确实是。于是呢这个时候便把茅头指向了丢掉数据的磁盘✒ 发问道:你为什么要丢数据呢?
【磁盘】:
操作系统一听:诶,这货说的好像确实没什么问题。那就就把视角转向了受害人即【进程】这一方。反正操作系统没错、磁盘没错,那就是你的问题喽!
【进程】:
那这个时候法官一想,它们三个说的似乎都挺有道理,难道是我错了吗?
那我们在上面有提到过处于【浅度睡眠】的进程,是可以被
kill
掉的,那么我们就要让这个进程的状态变为【深度睡眠】才对,即[D]
D
后,当它在等待这个磁盘写入的时候,操作系统路过,看到这个进程没有跑起来空等着,于是在想把他杀掉的时候就被这块 《秒死金牌》给吓到了R
了那这个时候就又有同学问了:D状态这么强大吗,那如果一个操作系统里有很多的D状态,这怎么办呢?
不过呢这个[D]
就没办法在这里给读者演示了,因为D状态的进程只有处于高IO的情况才可以演示。有兴趣的可以去研究一下Linux下的命令dd
好,接下去呢我们来讲讲【停止状态T】
kill -l
kill -19 PID
kill -18 PID
所以呢,如果我们要将一个进程给终止的话,发送19号信号即可,要让其继续启动起来,则发起18号信号
那我现在要问了,这个T停止状态和S睡眠状态有什么不同呢?
接下去呢,我们再来说说进程的跟踪状态t,还记得我们在基础篇中所学习的 GDB调试 吗?
S
睡眠状态。接下去呢我进行了【gdb】调试,在行内打上断点后,输入r
运行起来后,我们再去查看这个进程的状态时,就发现其状态变成了t
,原因就在于这个进程在调试的时候停了下来对于【死亡状态X】来说呢,这个状态只是一个返回状态,你不会在任务列表里看到这个状态
kill -9 PID
接下去我们来介绍一种状态叫做【僵死状态Z】,对于这个状态,我们要涉及到两个特殊的进程叫做 僵尸进程 与 孤儿进程
首先我们要来介绍的是僵尸进程,这里呢通过一个故事来进行引入
110
和120
的电话,然后守在他的身边等待救援来到,周边的人看到也都纷纷围了过来,其中不免有一些人会紧急救援。不过等了一会这个人就没了呼吸好,我们回归正题,来说说这个【僵尸进程】
Z状态
,即[僵死状态]
那我现在想问了:有一个进程暂时退出了,它要将它的状态暂时维持一段时间,问题是它维持给谁看呢?
fork
出来,此时呢它突然挂掉了,那么此时父进程就必须去关心一下对应的子进程退出时的原因wait()
系统调用的时候再去做一定的介绍就上面这样生冷的文字来叙述还不太行,接下去我们通过实际的案例来观察一下
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 int main(void)
6 {
7 pid_t id = fork();
8 if(id == 0)
9 {
10 // child
11 int cnt = 5;
12 while(cnt)
13 {
14 printf("I am child, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
15 sleep(1);
16
17 cnt--;
18 }
19 exit(0);
20 }
21 else
22 {
23 // father
24 while(1)
25 {
26 printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
27 sleep(1);
28 }
29 }
30 return 0;
31 }
5486
,其父进程为5485
,一开始它们均为S睡眠状态
,但是呢到了后面子进程的状态就变成了Z僵死状态
。也就意味着此时子进程已经死亡了,但是呢父进程还不知道
这个来看,它表示【失效的、不再存在的】所以我们总结一下:
进程一般退出的时候,如果父进程没有主动回收子进程信息,子进程会一直让自己处于Z状态,进程的相关资源尤其是 task_struct
结构体不能被释放
那对于上面的这种子进程,我们就将其称作为是【僵尸进程】,不过呢这种进程是存在一定危害的!
那此时我想问:这个父进程突然之间退出了,但是呢它的父进程并不知晓,那为何这个父进程没有处于【僵尸状态Z
】呢?
bash
的子进程,当其一退出之后,bash
就将其回收了。可是这个子进程为什么也没了呢?这个的话我们就要聊聊【孤儿进程】了接下去我们来将上面测试僵尸进程的代码做个修改,让父进程先于子进程退出
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 int main(void)
6 {
7 pid_t id = fork();
8 if(id == 0)
9 {
10 // child
11 int cnt = 500;
12 while(cnt)
13 {
14 printf("I am child, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
15 sleep(1);
16
17 cnt--;
18 }
19 exit(0);
20 }
21 else
22 {
23 int cnt = 5;
24 // father
25 while(cnt--)
26 {
27 printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
28 sleep(1);
29 }
30 }
31 return 0;
32 }
PPID
即父进程的PID
,它由原先的【19956】变成了【1】,这是为什么呢?PID
的值为1的进程,我们一般将其称作为是【系统进程】。我们可以使用下面这句指令去查找一下ps ajx | head -1 && ps ajx | grep systemd
PID
即为1那为何会出现上面这种现象呢?是这个子进程突然换父进程了吗?
但是这个孤儿进程为什么要被领养呢?
那其实我们就可以解释上面的事情了
bash
给回收了,那么对于父进程的子进程来说,我们刚才谈到了,如果一个进程的父进程先于子进程结束的话,那么在这一刻这个子进程立马变成了【孤儿进程】,被系统领养并回收了,所以我们就看不到了最后来总结一下本文所学习的内容
运行状态R
阻塞状态R
挂起状态R
运行状态R
sleep()
函数可以很好地使一个进程处于浅度睡眠状态S
深度睡眠状态D
停止状态T
继续发送【18】号信号的话则可以使其重新启动进程跟踪状态t
kill -9 PID
就可以杀掉一个进程,使其处于死亡状态X
僵死状态Z
,变为【僵尸进程】;若是在父子进程中父进程先于子进程退出的话,那么这个子进程就会变成【孤儿进程】以上就是本文要介绍的所有内容,感谢您的阅读