为什么要有进程等待? 我们知道,当子进程退出时,如果父进程不对其进行操作,子进程会变为僵尸进程。
而且当子进程结束时,我们如何知道子进程的最后执行情况呢?
父进程通过进程等待,可以:
wait函数
pid_t wait(int *status);
waitpid函数
pid_t waitpid(pid_t pid, int *status, int options);
参数:
该函数会暂停当前进程的执行,直到指定的子进程结束。
返回值是结束子进程的进程ID,或者出错时的返回值。
主要的区别和用法总结如下:
status是int型的整数,用于存储子进程的退出状态信息。其低7位(0-6位)第7位(第7位),其他位(8位及以上),分别存放不同信息。
进程正常终止情况下:
被信号终止情况下:
我们用位图来解释status

我们通过下面一段代码 获取子进程status
// 程序获取进程status
int main() {
pid_t id = fork();
if (id == -1) {
// 创建子进程失败
perror("fork");
exit(EXIT_FAILURE);
} else if (id == 0) {
// 子进程
printf("子进程正在运行\n");
exit(10); // 子进程退出状态设为 10
} else {
// 父进程
int status;
pid_t iid = waitpid(id, &status, 0);
printf("After the wait function\n");
if(iid == id)
{
printf("wait success, pid: %d, status: %d\n", iid, status);
}
sleep(10);
}
return 0;
}
代码执行结果如下:

为什么我们给出退出码exit(10)而最后status值为2560?
根据上面status的存储分析:如果子进程正常退出,status 的值将会是子进程退出码的左移 8 位。
即前八位存储的是退出状态, 0000 1010 0000 0000,当我们返回其十进制后,即为2560。
意义
进程的阻塞等待和非阻塞等待是指在等待某个事件完成时,进程所采取的不同等待方式。
1. 阻塞等待(Blocking Wait)
2. 非阻塞等待(Non-blocking Wait)
代码实现
1.5.1 进程阻塞等待
下面是代码实现:
int main()
{
pid_t id = fork();
if(id < 0) {
// fork error
perror("fork error");
exit(EXIT_FAILURE);
}
else if(id == 0) {
// 子进程
std::cout << "子进程正在运行, pid: " << getpid() << std::endl;
exit(10);
}
else{
// 父进程
int status;
pid_t rid = waitpid(id, &status, 0); // 阻塞等待,options设为0
std::cout << "after wait" <<std::endl;
if(rid == id && WIFEXITED(status))
{
std::cout << "wait success, pid: " << rid << ", status: " << status << std::endl;
}
else
{
std::cout << "子进程等待失败, return." << std::endl;
return 1;
}
sleep(10);
}
return 0;
}
代码执行结果如下:

1.5.2 进程非阻塞等待
代码实现如下:
int main()
{
pid_t id = fork();
if(id < 0){
// error
perror("fork erorr");
exit(EXIT_FAILURE);
}else if(id == 0){
printf("子进程正在运行,pid:%d \n", getpid());
exit(10);
}else{
int status;
pid_t rid;
do{ // 有子进程退出时 rid返回其pid
pid_t rid = waitpid(id, &status, WNOHANG);
if(rid == 0)
{
printf("child process is running\n");
}
sleep(5);
}while(rid == 0);
if(rid == id && WIFEXITED(status))
{
printf("wait success, child return code is :%d.\n",WEXITSTATUS(status));
}else{
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}
代码执行结果:

首先,我们看下面一段代码:
#include
#include
int main() {
std::cout << "This is the original program." << std::endl;
// 执行程序替换,将当前进程替换为/bin/ls(列出当前目录内容)
char *args[] = {"/bin/ls", "-l", NULL};
execv("/bin/ls", args);
// 如果execv执行成功,下面的代码不会被执行
std::cerr << "Failed to execute /bin/ls!" << std::endl;
return 1;
}
代码的执行结果如下:
我们可以看到,程序运行后,执行了 ls -l 操作,而 Failed to execute /bin/ls! 并没有被打印到下面。
而ls本身也是一个程序,可以理解为,进程 被替换为了 ls 程序

根据上面的图,我们分析进程替换原理,进程替换可以理解为下面的步骤:
像上面代码中所使用的execv函数,叫做程序替换函数:
下面列举这些程序替换函数,定义如下:
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *filename, char *const argv[], char *const envp[]);
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
这些函数都是程序替换函数,它们的区别在于传递参数的方式和环境变量的处理:
execv("/bin/ls", argv);execvp("ls", argv);execve("/bin/ls", argv, envp);execl("/bin/ls", "ls", "-l", NULL);execlp("ls", "ls", "-l", NULL);execle("/bin/ls", "ls", "-l", NULL, envp);