孤儿进程
孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程就变成init进程,称为init进程领养孤儿进程。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 pid_t pid = fork();
8 if(pid == 0){
9 while(1)
10 {
11 printf("I'm child, my parent is %d\n",getppid());
12 sleep(1);
13 }
14 }else if(pid > 0){
15 printf("I'm parent, my pid is %d\n",getpid());
16 sleep(9);
17 printf("I'm parent,going to die\n");
18 }else{
19 perror("fork error!\n"); return 1;
20 }
21 return 0;
22 }
运行结果:
I'm parent, my pid is 12589
I'm child, my parent is 12589
I'm child, my parent is 12589
I'm child, my parent is 12589
I'm parent,going to die # 父进程结束 子进程的父进程变为1(init进程)
I'm child, my parent is 1
I'm child, my parent is 1
I'm child, my parent is 1
I'm child, my parent is 1
僵尸进程
僵尸进程: 子进程被终止,父进程尚未回收,子进程残留资源PCB存放于内核中,就变成僵尸(Zombie)进程。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 int main(void)
6 {
7 pid_t pid = fork();
8
9 if (pid == 0) {
10 printf("---child, my parent= %d\n", getppid());
11 sleep(10);
12 printf("-------------child die--------------\n");
13 } else if (pid > 0) {
14 while (1) {
15 printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);
16 sleep(1);
17 }
18 } else {
19 perror("fork error!");
20 return 1;
21 }
22 return 0;
23 }
执行结果:
I am parent, pid = 12652, myson = 12653
---child, my parent= 12652
I am parent, pid = 12652, myson = 12653
I am parent, pid = 12652, myson = 12653
-------------child die--------------
I am parent, pid = 12652, myson = 12653
I am parent, pid = 12652, myson = 12653

特别注意,僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经被终止,当父进程执行结束后才会彻底清除。
回收子进程
为什么要回收子进程:一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。比如我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
wait函数说明
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待直到子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。
#include <sys/wait.h>
pid_t wait(int *wstatus);
返回值:
- 成功:清理掉的子进程ID;
- 失败:-1 (没有子进程)
因为是阻塞等待所以子进程正在执行的话这里是没有返回的。
参数wstatus:
该参数是传出参数,用来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:
WIFEXITED(status)为非0 → 进程正常结束
WEXITSTATUS(status)如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)WIFSIGNALED(status)为非0 → 进程异常终止
WTERMSIG(status)如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。WIFSTOPPED(status)为非0 → 进程处于暂停状态(此宏作为了解)
WSTOPSIG(status)如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(status)为真 → 进程暂停后已经继续运行
wait函数使用
例子说明:创建一个子进程,调用wait然后观察子进程是正常退出还是异常退出,且具体退出的原因是什么。
1 #include <stdio.h>
2 #include <sys/wait.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5
6 int main(void)
7 {
8 pid_t pid = fork();
9 int status;
10 if (pid == 0) {
11 printf("---child, my parent= %d\n", getppid());
12 sleep(20);
13 printf("-------------child die--------------\n");
14 return 8; // or exit(9)
15 } else if (pid > 0) {
16 pid_t rpid = wait(&status);
17 if(rpid > 0){
18 if(WIFEXITED(status)){
19 printf("Exit normally with %d\n",WEXITSTATUS(status));
20 }
21 if(WIFSIGNALED(status)){
22 printf("Abnormal exit with %d\n",WTERMSIG(status));
23 }
24 }else{
25 printf("No son was created by parent\n");
26 }
27 while (1) {
28 printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);
29 sleep(3);
30 }
31 } else {
32 perror("fork error!");
33 return 1;
34 }
35
36 return 0;
37 }
正常退出结果:
---child, my parent= 20432
-------------child die--------------
Exit normally with 8 # 正常退出,退出值为8
I am parent, pid = 20432, myson = 20433
I am parent, pid = 20432, myson = 20433
异常退出结果:
I am parent, pid = 20497, myson = 20498
---child, my parent= 20497
Abnormal exit with 15 # 异常退出,被15号信号终止
I am parent, pid = 20497, myson = 20498
异常退出一般是通过传递信号终止,上面的异常退出时的信号是15号信号。
查看系统中还有哪些信号:
root@OJO:~/day03/createprocess# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
如果要手动结束某个进程可用名:kill -信号值 进程id
比如要接收pid = 103456的进程:kill -9 103456
waitpid函数说明
作用同wait一样,此外还可指定清楚特定的pid进程,也可以不阻塞等待子进程退出。
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
参数:
pid:
>0:回收指定ID的子进程-1:回收任意子进程(相当于wait)0:回收和当前调用waitpid一个组的所有子进程<-1:回收指定进程组内的任意子进程
wstatus:
- 通wait函数的功能一样
options:
- 如果设置为0的话,成功:返回清理掉的子进程ID;失败:-1(无子进程)
- 如果设置为
WNOHANG的话,则waitpid不会阻塞等待子进程退出,而是轮训的检查子进程是否退出,子进程正在运行的话返回0,子进程退出返回子进程的pid.
注意: 一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。
waitpid函数使用
例子说明:创建多个子进程然后调用waitpid回收这些子进程,然后观察是否有僵尸进程。
1 #include <stdio.h>
2 #include <sys/wait.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5
6 int main(void)
7 {
8 int i = 0;
9 printf("I'm parent, my pid = %d\n",getpid());
10 for(;i < 5; i++){
11 pid_t pid = fork();
12 int status;
13 if (pid == 0) {
14 printf("---child %d, my parent= %d, my pid = %d\n", i, getppid(), getpid());
15 printf("-----------%dth child die----------\n", i);
16 return 8; // or exit(9)
17 } else if (pid > 0) {
18 sleep(1);
19 pid_t rpid = waitpid(-1, &status, WNOHANG);
20 if(rpid > 0){
21 if(WIFEXITED(status)){
22 printf("Exit normally with %d, pid = %d\n",WEXITSTATUS(status), rpid);
23 }
24 if(WIFSIGNALED(status)){
25 printf("Abnormal exit with %d, pid = %d\n",WTERMSIG(status), rpid);
26 }
27 }else{
28 printf("Son doing something\n");
29 }
30 } else {
31 perror("fork error!");
32 return 1;
33 }
34 }
35 sleep(70);
36 return 0;
37 }
执行结果:
I'm parent, my pid = 20763
---child 0, my parent= 20763, my pid = 20764
-----------0th child die----------
Exit normally with 8, pid = 20764
---child 1, my parent= 20763, my pid = 20765
-----------1th child die----------
Exit normally with 8, pid = 20765
---child 2, my parent= 20763, my pid = 20766
-----------2th child die----------
Exit normally with 8, pid = 20766
---child 3, my parent= 20763, my pid = 20767
-----------3th child die----------
Exit normally with 8, pid = 20767
---child 4, my parent= 20763, my pid = 20768
-----------4th child die----------
Exit normally with 8, pid = 20768

也没有僵尸进程,因为每次循环都会创建一个子进程也会回收一个子进程。
总结
在多进程中,父进程创建一个子进程,当子进程结束后会有残留的pcb在系统中还未清理干净,所以就需要父进程手动清理,如果父进程没有清理的话待父进程退出后就会交由操作系统负责清理。清理残留的pcb就需要调用wait或waitpid函数来清理。