文章目录
- 一、进程的结果
- 二、进程等待
一、进程的结果
在现实生活中找别人帮忙办事,别人同意帮忙之后,会反馈给自己的结果无非就是三种:
- 别人把事办完了,结果是自己想要的
- 别人把事办完了,由于办事的方法错误,导致结果并不是自己想要的
- 别人办事时遇到阻碍了,没办法继续完成这件事情
同样的,进程创建出来也是用来帮忙做事的,进程运行完成后,进程的结果有三种情况:
- 进程运行正常,结果正确
- 进程运行正常,结果错误
- 进程运行异常
对于进程运行正常而言,我们可以通过进程的退出码,来判断进程的运行结果是否正常
进程可以通过 main 函数中 return int 或者 任意代码处调用的 exit(int) 函数 正常退出,其中 return 返回的值和 exit 函数的参数,即为进程的退出码
echo $? 可以查看最近一次运行的程序的退出码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
// 计算
printf("begin ...\n");
int sum = 0;
for (int i = 1; i <= 100; ++i) sum += i;
sleep(1);
printf("end, result: %d\n", sum);
exit(11); // 或者 return 11 表示运行正常,结果错误
// return 0; // 或者 exit(0) 表示运行正常,结果正确
}
进程的退出码为 0 表示结果正确
进程的退出码非 0 表示结果错误
打印错误码对应的错误信息
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
for (int i = 0; i < 255; ++i)
printf("errno %d : %s\n", i, strerror(i));
return 0;
}
这里只截取了一部分,感兴趣的读者可以自行运行查看
系统调用 _exit(int) 也可以结束进程,与库函数 exit(int) 的关系是:库函数 exit(int) 会先进行关闭文件流,冲刷缓冲区等操作,然后再调用 _exit(int) 结束进程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
printf("hello world");
// 系统调用 _exit(int) 不会关闭文件流,冲刷缓冲区等,直接结束进程
_exit(0);
// 库函数 exit(int) 会关闭文件流,冲刷缓冲区等,在结束进程
// exit(0);
}
exit(int) 函数会冲刷缓冲区,所以打印了 hello world
_exit(int) 函数直接结束进程,不会打印 hello world
对于进程运行异常,是由于进程收到了操作系统的信号,参考
二、进程等待
当子进程先于父进程退出时,子进程会处于僵尸状态,为了回收子进程的资源,防止内存泄漏,以及可以选择性的接收子进程的结果,父进程需要等待子进程
系统调用 wait / waitpid,头文件 sys/types.h 和 sys/wait.h
- pid_t wait(int* status),阻塞式等待任意一个子进程(如果没有子进程退出,父进程会阻塞在 wait 函数)
返回值:等待成功返回子进程的 pid,出错返回 -1,并且 errno 被设置为相应的出错信息
参数:status 为输出型参数,用于存储子进程的退出码,如果不关心子进程的退出码,可以设置为 NULL
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// 创建子进程
pid_t id = fork();
assert(id != -1);
if (id == 0)
{
// 子进程
int cnt = 5;
while (cnt)
{
printf("我是子进程,我的 pid 是: %d,我的 ppid 是: %d\n我还剩余 %d s\n", getpid(), getppid(), cnt--);
sleep(1);
}
exit(0);
}
// slepp(10); 模拟父进程的任务
// 等待子进程
printf("我是父进程,我的 pid 是: %d, 开始等待子进程 %d ...\n", getpid(), id);
pid_t cid = wait(NULL);
assert(cid != -1);
printf("我是父进程,我的 pid 是: %d, 等待子进程 %d 成功\n", getpid(), cid);
return 0;
}
父进程阻塞在 wait 函数处
加上 sleep(10) 之后,子进程会先进入僵尸状态,父进程调用 wait 回收资源后,子进程的僵尸状态随之消失
- pid_t waitpid(pid_t pid, int* status, int options)
返回值:等待成功返回子进程的 pid,出错返回 -1
参数:
- pid 如果设置为 -1,表示等待任意一个子进程,pid 如果大于 0,则表示等待指定 pid 的子进程
- status 为输出型参数,用于存储子进程的退出码,如果不关心子进程的退出码,可以设置为 NULL
- options 如果设置为 0,表示阻塞式等待,如果设置为 WNOHANG,则表示非阻塞等待(调用时子进程还未退出,waitpid 函数会返回 0)
进程的结果有三种情况,整数 status 是采用如下位图的方式来存储进程的结果
-
正常终止:0 ~ 7 位为 0,8 ~ 15 位表示退出码
-
异常终止:0 ~ 6 位表示收到的信号,第 7 位表示是否为核心转储,8 ~ 15 位没有使用
-
宏 WIFEXITED(status),用于判断 status 是否收到信号,即子进程是否运行正常
-
宏 WEXITSTATUS(status),当子进程运行正常时,用于获取子进程的退出码
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
assert(id != -1);
if (id == 0)
{
// 子进程
int cnt = 5;
while (cnt)
{
printf("我是子进程,我的 pid 是: %d,我的 ppid 是: %d,我还剩余 %ds\n", getpid(), getppid(), cnt--);
sleep(1);
}
exit(0);
}
// 父进程
int status = 0;
while (1)
{
pid_t ret_id = waitpid(id, &status, WNOHANG);
assert(ret_id != -1);
if (ret_id > 0)
{
// 等待成功
printf("我是父进程,我的 pid 是: %d,等待子进程 %d 成功\n", getpid(), ret_id);
// 判断是否是信号导致
if (WIFEXITED(status))
printf("子进程 %d 运行正常,退出码: %d\n", ret_id, WEXITSTATUS(status));
else
printf("子进程 %d 运行异常", ret_id);
break;
}
// 父进程的任务
printf("我是父进程,我的 pid 是: %d, 正在执行任务中 ...\n", getpid());
sleep(1);
}
return 0;
}
父进程非阻塞等待,子进程进入僵尸状态,父进程调用 waitpid 回收资源后,子进程的僵尸状态随之消失
wait / waitpid 是如何得到子进程的结果呢?其实就是通过子进程的 task_struct 中的属性得到的