Linux:进程等待
Linux:进程等待,wait和waitpid的介绍
·
一、为什么需要进程等待
- 子进程退出,父进程如果不管不顾,就可能造成“僵尸进程”的问题,进而造成内存泄漏。另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的 kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
- 最后,父进程派给子进程的任务完成的如何,我们需要知道。如:子进程运行完成,结果对还是不对,或者是否正常退出。
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
二、进程等待的方法
wait函数
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL
验证:父进程等待可以解决子进程僵尸问题
1 #include <stdio.h>
2 #include <string.h>
3 #include <errno.h>
4 #include <unistd.h>
5 #include <stdlib.h>
6 #include <sys/types.h>
7 #include <sys/wait.h>
8
9 int main()
10 {
11 pid_t id = fork();
12 if(id == 0)
13 {// child
14 int cnt = 5;
15 while(cnt--)
16 {
17 printf("我是子进程: pid: %d\n", getpid());
18 sleep(1);
19 }
20 exit(0); // 终止子进程
21 }
22 else if(id > 0)
23 {// parent
24 // while(1)
25 // {
26 // printf("我是父进程: pid: %d\n", getpid());
27 // sleep(1);
28 // }
29 // 回收子进程,等待僵尸
30 //pid_t rid = wait(NULL);
31 sleep(10);
32 pid_t rid = wait(NULL);
33 if(rid == id)
34 {
35 printf("pid: %d, wait success!\n",\
36 getpid());
37 }
38 sleep(5);
39 exit(0);
40 }
41 }
解释一下代码的逻辑:创建子进程后,父进程休眠10秒,子进程5秒后终止进程,变成僵尸进程,再过5秒后,父进程醒来,用wait函数回收子进程,子进程消失,再过5秒后,父进程也终止。
运行结果:
// 创建父子进程
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
32134 32135 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
30068 32139 32138 30068 pts/2 32138 S+ 1000 0:00 grep --color=auto myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
32134 32135 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
30068 32144 32143 30068 pts/2 32143 S+ 1000 0:00 grep --color=auto myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
32134 32135 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
30068 32149 32148 30068 pts/2 32148 S+ 1000 0:00 grep --color=auto myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
32134 32135 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
30068 32154 32153 30068 pts/2 32153 S+ 1000 0:00 grep --color=auto myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
32134 32135 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
30068 32159 32158 30068 pts/2 32158 S+ 1000 0:00 grep --color=auto myprocess
// 子进程变成僵尸状态,父进程还在sleep
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
32134 32135 32134 29186 pts/1 32134 Z+ 1000 0:00 [myprocess] <defunct>
30068 32164 32163 30068 pts/2 32163 S+ 1000 0:00 grep --color=auto myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
32134 32135 32134 29186 pts/1 32134 Z+ 1000 0:00 [myprocess] <defunct>
30068 32169 32168 30068 pts/2 32168 S+ 1000 0:00 grep --color=auto myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
32134 32135 32134 29186 pts/1 32134 Z+ 1000 0:00 [myprocess] <defunct>
30068 32174 32173 30068 pts/2 32173 S+ 1000 0:00 grep --color=auto myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
32134 32135 32134 29186 pts/1 32134 Z+ 1000 0:00 [myprocess] <defunct>
30068 32179 32178 30068 pts/2 32178 S+ 1000 0:00 grep --color=auto myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
32134 32135 32134 29186 pts/1 32134 Z+ 1000 0:00 [myprocess] <defunct>
30068 32184 32183 30068 pts/2 32183 S+ 1000 0:00 grep --color=auto myprocess
// 父进程醒来,子进程被回收
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
30068 32189 32188 30068 pts/2 32188 S+ 1000 0:00 grep --color=auto myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
30068 32194 32193 30068 pts/2 32193 S+ 1000 0:00 grep --color=auto myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
30068 32199 32198 30068 pts/2 32198 S+ 1000 0:00 grep --color=auto myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
30068 32204 32203 30068 pts/2 32203 S+ 1000 0:00 grep --color=auto myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29186 32134 32134 29186 pts/1 32134 S+ 1000 0:00 ./myprocess
30068 32209 32208 30068 pts/2 32208 S+ 1000 0:00 grep --color=auto myprocess
// 父进程结束
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
30068 32214 32213 30068 pts/2 32213 S+ 1000 0:00 grep --color=auto myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
30068 32220 32219 30068 pts/2 32219 S+ 1000 0:00 grep --color=auto myprocess
waitpid函数
#include <sys/wait.h>
#include <sys/types.h>
pid_t waitpid(pid_t pid, int *status, int options);
1.参数
pid :pid = -1,等待任意一个子进程。与wait等效
pid > 0,等待其他进程ID与pid相等的子进程
statue:输出型参数,返回子进程的status
这里介绍两个宏:
- WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(进程是否正常退出)。
- WEXITSTATUS(status):若WIFEXITED非零(进程正常退出),提取子进程的退出码。(查看进程的退出码)。
options:模认是0,表示阻塞等待
options可以填的参数:WNOHANG:
- 各部分含义:“Wait” 对应 Linux 中进程等待相关函数(
waitpid())的核心功能,即父进程等待子进程状态变化;“No Hang” 表示 “不挂起、不阻塞”,形象体现该选项的核心作用。 - 该宏常用在
waitpid()系统调用的options参数中,用于实现非阻塞等待。当设置此选项后,若没有子进程退出,waitpid()不会让父进程阻塞等待,而是立即返回 0,方便父进程同时处理其他任务。
WNOHANG:可以理解为:若:pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。
若:正常结束,则返回该子进程的ID。
2. 返回值
- 成功:返回状态发生变化的子进程 PID(若
options含WNOHANG且无子进程状态变化,返回0)。 - 失败:返回
-1(如无对应子进程、被信号中断等),并设置errno标识错误原因。
把waitpid的pid 设置为 -1,options设置为 0 就等同于wait函数
三、获取子进程status
- wait 和 waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
- 如果传递NULL,表示不关心子进程的退出状态信息。
- 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
- status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16 比特位):
代码:
下面代码中,子进程退出码是1,程序正常运行结束。
1 #include <stdio.h>
2 #include <string.h>
3 #include <errno.h>
4 #include <unistd.h>
5 #include <stdlib.h>
6 #include <sys/types.h>
7 #include <sys/wait.h>
8
9 int main()
10 {
11 pid_t id = fork();
12 if(id == 0)
13 {// child
14 int cnt = 5;
15 while(cnt--)
16 {
17 printf("我是子进程: pid: %d\n", getpid());
18 sleep(1);
19 }
20 exit(1); // 终止子进程
21 }
22 else if(id > 0)
23 {// parent
24 // while(1)
25 // {
26 // printf("我是父进程: pid: %d\n", getpid());
27 // sleep(1);
28 // }
29 // 回收子进程,等待僵尸
30 //pid_t rid = wait(NULL);
31 sleep(10);
32 //pid_t rid = wait(NULL);
33 int status = 0;
34 pid_t rid = waitpid(id,&status, 0);
35
36 if(rid == id)
37 {
38 printf("pid: %d, wait success!,statue:%d\n",\
39 getpid(), status);
40 }
41 sleep(5);
42 // exit(0);
43 }
44 }

获得到的status是256。
这里的status一共有36个bit位,其中高16位不用,只使用低16位,其中,低8位表示进程退出时的信号编号,次8位表示进程退出时的退出码(位图结构)
这里的1是在第9位,所以是2^8 == 256。
退出码:
int exit_code = (status >> 8) & 0xFF; //只保留最低8位,其他位置清零
信号编号:
int exit_sig = status & 0x7F //0111 1111 只保留最低7位,其他位置清零
所以可以改进上面的代码:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{// child
int cnt = 5;
while(1)
{
printf("我是子进程: pid: %d\n", getpid());
sleep(1);
}
exit(1); // 终止子进程
}
else if(id > 0)
{// parent
// while(1)
// {
// printf("我是父进程: pid: %d\n", getpid());
// sleep(1);
// }
// 回收子进程,等待僵尸
//pid_t rid = wait(NULL);
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid == id)
{
//改进的地方:
int exit_code = ((status >> 8)&0xFF);
int exit_sig = status&0x7F; // 0111 1111
printf("pid: %d, wait success!, status: %d, exit_code: %d, exit_sig: %d\n",\
getpid(), status, exit_code, exit_sig);
}
}
}
运行结果:

四、一次性创建多个子进程
先看一个错误案例:
5 int main(int argc, char *argv[])
36 {
37 if(argc != 2)
38 {
39 std::cout << "Usage: " << argv[0] << " process_num" << std::endl;
40 exit(USAGE_ERR);
41 }
42 int num = std::stoi(argv[1]);
43 for(int i = 0; i < num; i++)
44 {
45 pid_t id = fork();
46 if(id == 0)
47 {
48 // child
49 Task();
50 exit(0);
51 }
52 // father
53 int status = 0;
54 pid_t rid = waitpid(id, &status, 0);
55 if(rid > 0)
56 {
57 printf("我是子进程:%d Exit, exit code:%d\n", rid, WEXITSTATUS(status));
58 }
59 }
60
61
62
63
64 return 0;
65 }
这样写非常奇怪,完全不符合预期。
正确写法:
int main(int argc, char *argv[])
36 {
37 if(argc != 2)
38 {
39 std::cout << "Usage: " << argv[0] << " process_num" << std::endl;
40 exit(USAGE_ERR);
41 }
42 int num = std::stoi(argv[1]);
43 std::vector<pid_t> subs;
44 // 创建多个子进程
45 for(int i = 0; i < num; i++)
46 {
47 pid_t id = fork();
48 if(id == 0)
49 {
50 // child
51 Task();
52 exit(0);
53 }
54 subs.push_back(id);
55 }
56
57 // 父进程
58 // 等待多进程
59 for(auto &pid : subs)
60 {
61 // father
62 int status = 0;
63 pid_t rid = waitpid(id, &status, 0);
64 if(rid > 0)
65 {
66 printf("我是子进程:%d Exit, exit code:%d\n", rid, WEXITSTATUS(status));
67 }
68 }
69
70
71
72
73 return 0;
74 }
优化一下,封装成函数:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/wait.h>
4 #include <sys/types.h>
5 #include <stdlib.h>
6 #include <iostream>
7 #include <vector>
8
9 enum
10 {
11 OK,
12 USAGE_ERR
13 };
14
15 void Task()
16 {
17 int cnt = 5;
18 while(cnt--)
19 {
20 printf("我是一个子进程, 我在完成Task任务, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt);
21 sleep(1);
22 }
23 }
24
25 void Hello()
26 {
27 int cnt = 5;
28 while(cnt--)
29 {
30 printf("我是一个子进程, 我在完成Hello任务, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt);
31 sleep(1);
32 }
33 }
34 typedef void (*callback_t) ();
35 // 一般而言:
36 // 参数:
37 // 输入: const &
38 // 输出:*
39 // 输入输出: &
40 void CreateChildProcess(int num, std::vector<pid_t> *subs, callback_t cb)
41 {
42 for(int i = 0; i < num; i++)
43 {
44 pid_t id = fork();
45 if(id == 0)
46 {
47 //child
48 cb();
49 exit(0);
50 }
51 // 父进程
52 subs->push_back(id);
53 }
54 }
55
56 void WaitAllChild(const std::vector<pid_t> &subs)
57 {
58 for(const auto &pid : subs)
59 {
60 int status = 0;
61 pid_t rid = waitpid(pid, &status, 0);
62 if(rid > 0)
63 {
64 printf("子进程: %d Exit, exit code: %d\n", rid, WEXITSTATUS(status));
65 }
66 }
67 }
68
69 // 启停多进程的方案
70 // ./myproc 5 --- 我要创建五个子进程
71 int main(int argc, char *argv[])
72 {
73 if(argc != 2)
74 {
75 std::cout << "Usage: " << argv[0] << " process_num" << std::endl;
76 exit(USAGE_ERR);
77 }
78 int num = std::stoi(argv[1]);
79 std::vector<pid_t> subs;
80 //std::vector<callback_t> cbs;
81 // 创建多进程
82 CreateChildProcess(num, &subs, Hello);
83 // 父进程
84 WaitAllChild(subs);
85
86 return OK;
87 }
运行事例:

更多推荐




所有评论(0)