Haohao Notes

DREAM OF TECHNICAL ACHIEVEMENT

0%

Linux系统编程-进程回收

孤儿进程

父进程先于子进程结束,则子进程称为孤儿进程,子进程的父进程成为 init 进程,称 init 进程领养进程孤儿进程,init 进程称为孤儿院。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>

int main(void)
{
pid_t pid;

pid = fork();

// 创建失败
if( pid == -1 )
{
perror("fork");
exit(1);
}
// 父进程
else if( pid > 0)
{
printf("This is the parent process. My PID is %d.\n",getpid());

sleep(4);

printf("--------------parent going to die------------------\n");
}
// 子进程
else if(pid == 0)
{
while(1)
{
printf("This is the child process. My PID is: %d. My PPID is: %d.\n",getpid(),getppid());

sleep(1);
}
}

return 0;
}

运行结果:

1
2
3
4
5
6
7
8
9
10
tanhao@MacBook-Pro test % gcc pro.c 
tanhao@MacBook-Pro test % ./a.out
This is the parent process. My PID is 46491.
This is the child process. My PID is: 46492. My PPID is: 46491.
This is the child process. My PID is: 46492. My PPID is: 46491.
This is the child process. My PID is: 46492. My PPID is: 46491.
--------------parent going to die------------------
tanhao@MacBook-Pro test % This is the child process. My PID is: 46492. My PPID is: 1.
This is the child process. My PID is: 46492. My PPID is: 1.
This is the child process. My PID is: 46492. My PPID is: 1.

从上面运行结果可以看出来父进程退出后子进程自动被init进程领养ppid为1

此时程序中的打印信息会一直进行打印,即使你按下 ctrl+c 也不会退出正在执行的程序 ./a.out;此时需要使用 ps -ef ,查看正在打印的程序信息;然后使用 kill -9 + 进程ID(./a.out 的进程ID)

僵尸进程

进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程。

注意:僵尸进程是不能使用 kill 命令清除掉的。因为 kill 命令只是用来终止进程的,而僵尸进程已经终止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
int i = 100;
pid_t pid=fork();
if(pid < 0)
{
perror("fork failed.");
exit(1);
}
if(pid > 0)
{
printf("This is the parent process. My PID is %d.\n", getpid());
for(; i > 0; i--)
{
sleep(1);
}
}
else if(pid == 0)
{
printf("This is the child process. My PID is: %d. My PPID is: %d.\n", getpid(), getppid());
}
return 0;
}

运行结果:

1
2
3
tanhao@MacBook-Pro test % ./a.out  
This is the parent process. My PID is 46503.
This is the child process. My PID is: 46504. My PPID is: 46503.

1
2
3
4
tanhao@MacBook-Pro test % ps aux|grep "a.out"
tanhao 46506 0.0 0.0 4277536 708 s005 S+ 10:32下午 0:00.00 grep a.out
tanhao 46504 0.0 0.0 0 0 s000 Z+ 10:32下午 0:00.00 (a.out)
tanhao 46503 0.0 0.0 4268180 696 s000 S+ 10:32下午 0:00.00 ./a.out

这里的Z+ 就是僵尸进程(zoom)

wait 函数

一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的 PCB 还保留着,内核在其中保留了一些信息。如果是正常终止,则保存退出状态;如果是异常终,则保存着导致该进程终止的信号是哪一个。这个进程的父进程可以调用 wait 或 waitpid 获取这些信息 ,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在 Shell 中用特殊变量 $? 查看,因为 Shell 是它的父进程,当它终止时 Shell 调用 wait 或者 waitpid 得到它的退出状态同时彻底清除掉这个进程。

父进程调用 wait 函数可以回收子进程终止信息。该函数有三个功能:

  1. 阻塞等待子进程退出
  2. 回收子进程残留资源
  3. 获取子进程结束状态

pid_t wait(int *status); 成功:返回清理掉的子进程 ID; 失败:-1(没有子进程)

当进程终止时,操作系统的隐式回收机制会:

  1. 关闭所有文件描述符
  2. 释放用户空间分配的内存。内核的 PCB 仍存在。其中保存该进程的退出状态。(正常终止—->退出值;异常终止——->终止信号)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<stdio.h>

int main(void)
{
pid_t pid,wpid;

int status;

pid = fork();

if( pid == -1 )
{
perror("fork");
exit(1);
}
else if( pid == 0)
{

execl("ls","ls",NULL);
printf("I am child,my parent pid = %d,going to sleep 10s\n",getppid());

sleep(20);
printf("-------------child die------------------\n");
// exit(76);
return 100;
}

else
{
// 回收子进程,避免出现僵尸进程

wpid = wait(&status);

if( wpid == -1 )
{
perror("wait error:");
exit(1);
}

// 正常退出
if( WIFEXITED(status) )
{
printf("child exit with %d\n",WEXITSTATUS(status));

}

// 异常退出
if(WIFSIGNALED(status))
{
printf("child exit with %d\n",WTERMSIG(status));
}
while(1)
{
printf("I am parent,my pid = %d,my son = %d\n",getpid(),pid);

sleep(1);
}
}
return 0;
}

运行结果

1
2
3
4
5
6
7
8
9
tanhao@MacBook-Pro test % gcc pro.c
tanhao@MacBook-Pro test % ./a.out
I am child,my parent pid = 46553,going to sleep 10s
-------------child die------------------
child exit with 100
I am parent,my pid = 46553,my son = 46555
I am parent,my pid = 46553,my son = 46555
I am parent,my pid = 46553,my son = 46555
I am parent,my pid = 46553,my son = 46555

由上面运行结果我们可以看出,wait是阻塞等待子进程退出.子进程退出后,才执行printf("I am parent,my pid = %d,my son = %d\n",getpid(),pid);

回收结果

回收结果可使用wait或者waitpid 函数传出参数 status 来保存进程的退出状态,借助宏函数来进一步判断进程终止的具体原因,宏函数可分为如下六种

  • WIFEXITED(status)如果子进程正常结束则为非0值。
  • WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。
  • WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真
  • WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。
  • WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
  • WSTOPSIG(status)取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才使用此宏。

waitpid 函数

作用同 wait ,但可指定 pid 进程清理,可以不阻塞。

pid_t waitpid(pid_t pid, int *status, int options);

函数说明

waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。

如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。

子进程的结束状态值会由参数status返回,而子进程的进程识别码也会一快返回。

如果不在意结束状态值,则参数status可以设成NULL。

参数pid

为欲等待的子进程识别码,其他数值意义如下:

  • pid < -1 等待进程组识别码为pid绝对值的任何子进程。
  • pid = -1 等待任何子进程,相当于wait()。
  • pid = 0 等待进程组识别码与目前进程相同的任何子进程。
  • pid > 0 等待任何子进程识别码为pid的子进程。

参数option

  • 可以为0 或下面的OR 组合

    • WNOHANG 如果没有任何已经结束的子进程则马上返回,不予以等待。

    • WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。子进程的结束状态返回后存于status.

返回值

如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。

注意

一次 wait 或者 waitpid 调用只能清理一个子进程,清理若干个子进程应该使用循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<stdio.h>

int main(int argc,char *argv[]) {
int n = 5, i; // 默认创建 5 个子进程
pid_t p, q;
pid_t wid;
if (argc == 2) {
n = atoi(argv[1]);
}

for (i = 0; i < n; i++) // 出口1 父进程专用出口
{
p = fork();
if (p == 0) {
break; // 出口 2 子进程出口,i 不自增
} else if (i == 3) {
q = p;
}
}
// 等子进程创建完毕后开始运行
if (n == i) {
sleep(n);
printf("I am parent ,pid = %d, gpid = %d\n", getpid(), getppid());
// while(wait(NULL));
// wait(NULL);
// waitpid(q,NULL,0); // 回收 4 儿子的资源, 此时 waitpid 的作用等价于 wait 函数 阻塞版
// while(waitpid(-1,NULL,0)); // 回收所有子进程 == wait(NULL);
do {
wid = waitpid(-1, NULL, WNOHANG);
printf("recyc wid = %d\n", wid);
if (wid > 0) {
n--;
}
// if wid == 0 说明子进程正在运行
sleep(1);

} while (n > 0);

printf("wait finish\n");
while (1);
} else {
sleep(i);
printf("I am %dth child,pid = %d,gpid = %d\n", i + 1, getpid(), getppid());
}
return 0;
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
tanhao@MacBook-Pro test % ./a.out  
I am 1th child,pid = 46663,gpid = 46662
I am 2th child,pid = 46664,gpid = 46662
I am 3th child,pid = 46665,gpid = 46662
I am 4th child,pid = 46666,gpid = 46662
I am 5th child,pid = 46667,gpid = 46662
I am parent ,pid = 46662, gpid = 24409
wid = 46667
wid = 46666
wid = 46665
wid = 46664
wid = 46663
wait finish
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/*
需求:
父进程 fork 3 个子进程,三个子进程 一个调用 ps 命令,一个调用自定义程序1(正常),一个调用自定义程序1(会出错误)。
父进程会使用 waitpid 对子进程进行回收。
*/


#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<stdio.h>

int main(int argc,char *argv[])
{
int n=3,i;
pid_t pid;
pid_t wid;
for( i=0;i<n;i++ )
{
pid=fork();
if( pid == -1)
{
perror("fork");
exit(1);
}
if( pid == 0 )
{
printf("I am %d child,pid = %d,ppid = %d\n\n",i+1,getpid(),getppid());
break;
}
}

// 对第一个子进程执行 ps 操作
if( i== 0 )
{
sleep(1);
printf("I am %d child,pid = %d,ppid = %d\n\n",i+1,getpid(),getppid());

execlp("ps","ps","-ef",NULL);


}
// 对第二子进程 执行自定义程序1 (正常)
else if( i==1 )
{
sleep(2);
printf("I am %d child,pid = %d,ppid = %d\n\n",i+1,getpid(),getppid());

execl("main","main",NULL);

}


// 对第三子进程 执行自定义程序2 (错误)
else if( i==2 )
{
sleep(5);
printf("I am %d child,pid = %d,ppid = %d\n\n",i+1,getpid(),getppid());

execl("abnor","abnor",NULL);

}

// 父进程回收子进程资源

else
{
// 注意:这里不能使用阻塞版本的 waitpid,而要使用非阻塞版的 waitpid ,不然程序组塞住了。

do
{
wid=waitpid(-1,NULL,WNOHANG);
if( wid > 0 )
{
i--;
}
sleep(1);
}while( i > 0 );

printf(" wait finnaly!\n");

sleep(10);
}

return 0;
}

这个运行结果有点多,这里就不贴了自己可以试试