文章目录
- 进程状态
- 进程排队
- 进程的状态(运行,阻塞,挂起)
- 进程的七个状态
- 孤儿进程
进程状态
进程 = task_struct + 可执行程序
进程不是一 直在运行的,可能在等待软硬件资源,比如scanf后,程序停止运行了,在等待用户输入。进程放在CPU上,也不是一直在运行的。时间片:进程在CPU上的时间,过了就会被拿下。
进程排队
先简单看看进程排队:
所谓的进程排队,一定是在等待某种资源,排队是进程的PCB(task_struct)在排队,而不是进程的可执行程序在排队。
一个task_struct(进程的PCB)可以被连入多种数据结构中, 在Linux内核中,每一个进程的PCB不是连入到单链表中的,而是连入到双链表的(队列可以用链表实现),这个结构和书本上学习的双链表结构一样的但用法不同而已,请看图解:
图一:
注释:如果像下图这样管理PCB的话(也就是每一个进程PCB中包含两个指针,但是这样只能连入一到一个数据结构中,如果想要连入到多种数据结构中,必须加指针,浪费空间,并且难维护),所以这种结构是不合适的。
图二:
注解:每一个进程的PCB中,包含特殊的结构体,然后将这个结构体指针放入双链表中进行管理,这样,就可以通过这个结构体指针找到进程的PCB,这个结构体指针也可以连入多种数据结构中。(利用struct listnode在对应进程的PCB中偏移量计算PCB的地址:(task_struct*)(&n-&((task_struct)0-> n)。如果PCB中存在很多个特殊的结构体,就可以将PCB连入到很多不用的数据结构中进行管理。
进程的状态(运行,阻塞,挂起)
所谓的状态,其实就是一个整型变量,在task_struct里面的一个整型变量:
状态决定了什么?
状态决定了下一步的后续动作。
一个CPU一个运行队列。
如简图:
-
运行状态:一个进程的PCB在CPU的运行队列上,不一定正在运行,把该进程的状态成为运行状态;
-
阻塞状态 :
每一个设备为了操作系统能够管理。都会被先描述,后组织。将设备用一个结构体描述,结构体里面可能会包含设备类型,状态,以及当前设备的等待队列等。
当一个进程在等待软硬件资源的时候,如果资源没有就绪,例如scanf时,操作系统会将该进程PCB从与逆行队列拿下来,状态由运行改为阻塞,连入到等待的资源提供的的等待队列中,等待资源;
- 其中,状态的变迁引起的是PCB会被OS变迁到不同的队列中。
- 挂起状态:
前提:当计算机资源已经很吃紧了;
阻塞挂起:已经很吃紧了,但是有一些进程暂时不会被调度,操作系统会将该进程的代码与数据写入到外设(磁盘)中去,也就是腾空间。(磁盘有一个固定的区域,叫做swap分区,是用来给操作系统在资源紧张的时候,用来和磁盘进行数据的唤入与唤出)
注意:进程的PCB不会被唤入(将数据拷贝到外设)唤出(将数据从外设唤入到内存)。
创建一个进程的时候,先创建进程的对应的内核数据结构(PCB),再将代码与数据加载到内存(可以一部分一部分的加载,按需加载) 。
进程的七个状态
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运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(阻塞状态)。crtl+c可以终止进程。
- D磁盘休眠状态(Disk sleep),有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。(阻塞状态)crtl+c不可以终止进程。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
- t(停止状态):在调试的时候可以看见。
- Z(zombine):僵尸状态。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
Linux进程状态转换图:
测试:使用指令
# ps ajx | head -1 && ps ajx | grep myprocess
测试代码1:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 while(1)
8 {
9 printf("i am a proces id:%d\n",getpid());
10 sleep(1);
11 }
12 return 0;
13 }
运行结果:
- 问题:为什么我的代码在运行,但是确是S状态呢?
答:因为CPU执行你的代码非常快,剩下的还执行了sleep函数,导致该进程大多时间处于sleep状态,只有很少量的时间被调度了一次,导致很难观察到运行状态。
测试代码2:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 while(1)
8 {
9 printf("i am a proces id:%d\n",getpid());
10 //sleep(1);
11 }
12 return 0;
13 }
运行结果:
- 问题:为什么我的代码将sleep注释掉了再运行,但是确还是S状态呢?
代码中使用的printf函数,是要访问外设的,但是要访问这个设备,这个设备的资源不一定准备就绪了,但是CPU的执行速度很快,但是硬件的速度很慢,导致大部分时间也是在等待状态的。
测试代码3:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 while(1)
8 {
9 //printf("i am a proces id:%d\n",getpid());
10 //sleep(1);
11 }
12 return 0;
13 }
运行结果:
- 为什么状态后有+呢,比如:S+
后面带有+,说明是前台进程,没有代表为后台进程:
例如:
指令(前台进程变为后台进程)
# ./myprocess &
可以将前台进程变为后台进程,后台进程使用键盘ctrl+c不能杀掉,只能用命令kill -9 杀掉;
例如:
测试代码:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 while(1)
8 {
9 printf("i am a proces id:%d\n",getpid());
10 sleep(1);
11 }
12 return 0;
13 }
T状态的测试:
测试代码:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 while(1)
8 {
9 printf("i am a proces id:%d\n",getpid());
10 sleep(1);
11 }
12 return 0;
13 }
运行结果:
T状态:让进程处于暂停状态。
t状态的测试:
当我们调试代码的时候,在每次单步执行的时候,程序会处于t状态(停止状态);
示例:
Z状态,僵尸状态:当一个进程退出后,但是该进程的状态需要自己维持住,供上层读取。也就是进程从退出到资源被清理的这个时间段,进程所处于的状态。
- 当一个进程退出过后,代码和数据可以被直接释放,因为已经没有用了,但是,当前进程所对应的PCB,还不能被释放,里面还有记录进程的退出信息,是为了让系统或者其他进程能够读取到该进程的的退出信息进而执行下一步动作。只有该数据被拿走过后,这个进程才是真正的死亡状态。PCB才会被清理释放。
- 如果父进程不读取子进程的退出信息,僵尸状态的进程,会一直存在,task_struct对象也会一直存在,都是要暂用内存的,会有内存泄漏。在Linux系统中,所有的进程退出后都是Z状态。只有被别人读取完成后,才会变为X状态。
测试代码:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 pid_t id = fork();
8 if(id==0)
9 {
10 int cnt = 5;
11 while(cnt)
12 {
13 printf("i is child id:%d pid:%d\n",getpid(),getppid());
14 sleep(1);
15 cnt--;
16 }
17 exit(0); //让子进程直接退出
18 }
19 //father
20 while(1)
21 {
22 printf("i is father id:%d pid:%d\n",getpid(),getppid());
23 sleep(1);
24 }
25 return 0;
26 }
结果:5秒过后子进程状态变为Z(僵尸状态):因为进程退出后,退出信息你没有被读取;怎么读取后续文章再给出。
孤儿进程
如果一个进程的父进程已经退出,但是子进程还没有退出,这个子进程就被叫做孤儿进程。
这是孤儿进程会变为后台进程,只有不能ctrl+c杀掉,可以通过kill命令。
这个进程会被1号进程(操作系统)所领养;如果不被领养,就变成没有主的进程,当程序退出时,就会一直为僵尸进程,就会一直暂用资源(空间),所以孤儿进程会被1号进程领养,这样该进程的资源还可以被操作系统所回收。
测试代码:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 pid_t id = fork();
8 if(id==0)
9 {
10 int cnt = 50;
11 while(cnt)
12 {
13 printf("i is child id:%d pid:%d\n",getpid(),getppi d());
14 sleep(1);
15 cnt--;
16 }
17 exit(0); //让子进程直接退出
18 }
19 //father
20 int cnt = 5;
21 while(cnt)
22 {
23 printf("i is father id:%d pid:%d\n",getpid(),getppid ());
24 sleep(1);
25 cnt--;
26 }
27 printf("父进程退出\n");
28 return 0;
29 }