前面我们说过,对于一个被执行的程序,操作系统会为该程序创建一个进程或一个任务。为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。这时有必要看一看kernel里面是怎么给我们定义的。
相信大家的英文水平都不差,我就不做翻译了,简单老说就是包含了五种状态:
事件 | 说明 |
---|---|
R状态 | 即运行状态,但它并不意味着进程一定是在运行中,它表明进程要么是在运行中,要么是在运行队列中 |
S状态 | 即睡眠状态,意味着进程在等待某件事件的完成(有时候也叫做可中断睡眠) |
D状态 | 即磁盘休眠状态,有时候也叫做不可中断睡眠状态,这个状态的进程通常会等待IO的结束 |
T状态 | 即停止状态,可以通过发送SIGSTOP信号使进程停止(T)。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行 |
X状态 | 即死亡状态(dead),这个状态只是一个返回状态,在任务列表中是看不到的 |
下面演示一下R状态和T状态:
1 #include <iostream>
2 #include <unistd.h>
3 using namespace std;
4
5
6 int main()
7 {
8 while(1)
9 {
10 // sleep(2);
11 cout<<"this is proc"<<endl;
12 }
13 return 0;
14 }
当我们在bash下键入kill -19 + PID 会出现下面现象:
可以看到再键入上述指令之后,我们的程序就变成了T状态,要想变回原来的状态,我们可以键入
kill -SIGCONT + PID
这条指令就可以恢复;
看看效果:
可以看出来我们的程序已经恢复正常。
接下来是重点—僵尸进程(zombie)
在内核当中我们发现还有一种状态就是zombie状态,简称僵尸状态。
僵尸状态是一个比较特殊的状态。如果子进程退出父进程没有退出,那么子进程会变成僵尸进程;如果父进程退出但子进程没退出会造成孤儿进程,孤儿进程最后会被一号进程收养,即 init 进程;僵尸进程会以终止状态保持在进程当中,并且会一直等待父进程读取退出状态码。
看个例子:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 //僵尸状态
6 int main()
7 {
8 pid_t pid = fork();
9 if(pid < 0 )
10 {
11 perror("fork");
12 return 1;
13 }
14 else if(pid == 0)
15 {
16 printf("this is child , pid is %d\n",getpid());
17 sleep(5);
18 exit(EXIT_SUCCESS);
19 }
20 else{
21 printf("this is parent , pid is %d\n",getpid());
22 sleep(30);
23 }
24 return 0;
25 }
再来看看孤儿进程:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 //模拟孤儿进程
6 int main()
7 {
8 pid_t pid = fork();
9 if(pid<0)
10 {
11 perror("fork");
12 return 1;
13 }
14 else if(pid==0)
15 {
16 printf("child is running...pid is %d\n",getpid());
17 sleep(10);
18 // exit(EXIT_SUCCESS);
19 }
20 else{
21 printf("parent is running... pid is %d\n",getpid());
22 sleep(2);
23 exit(EXIT_SUCCESS);
24 }
25 return 0;
26 }
僵尸进程危害
- 进程退出状态必须被维持下去,因为他要告诉父进程,父进程交给他办的事办的怎么样了,如果父进程一直不读取,那么子进程就会一直处于Z状态。
- 维护退出状态本身就需要数据来维护,也属于进程基本信息,保存在task_struct(即PCB)中,如果子进程一直不退出,PCB就会一直维护。
- 如果父进程创建了很多子进程,就是不回收,那么最后会造成内存泄漏