当前位置:   article > 正文

进程终止与进程等待_c++ fork

c++ fork

fork 函数

fork 函数是 Linux 中一个非常重要的函数,它的作用是从已存在的进程中创建一个新进程。这个新进程就是当前进程的子进程。

fork() 函数使用方法:它在头文件 #include <unistd.h> 中,函数原型为

pid_t fork(void);

用一个 pid_t 类型的变量来接收 fork() 函数的返回值。当创建进程成功时,fork() 函数会给子进程返回 0,给父进程返回新创建的子进程的 id;当创建失败时,fork() 函数会返回 -1(给父进程返回,因为子进程根本没创建出来)

当创建子进程成功后,操作系统会做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

当一个进程调用fork之后,就有两个二进制代码相同的进程。但它们都运行到相同的地方后,每个进程都会可以开始它们自己的旅程。可以使用下面的代码测试(Linux系统):

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. int main(void)
  5. {
  6. pid_t pid = fork();
  7. printf("Before: pid is %d\n", getpid());
  8. if (pid == -1) perror("fork()"), exit(1);
  9. if (pid == 0)
  10. {
  11. printf("child:pid is %d, fork return %d\n", getpid(), pid);
  12. }
  13. else
  14. {
  15. printf("After:pid is %d, fork return %d\n", getpid(), pid);
  16. }
  17. sleep(1);
  18. return 0;
  19. }

运行结果:

fork()函数 内核示意图

fork() 函数的常规用法及错误原因

常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数 

错误原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

C/C++中捕捉错误的方式(代码运行完毕,结果错误)

errno 是C语言调用C函数时的错误码,当调用函数出错时,它会在内部给出错误码(数字),但这个数字往往我们不知道是什么错误,那我们就要用 strerror 函数来解析这个错误码。strerror 函数可以将错误码以字符串的形式描述起来。

代码异常终止

当代码异常终止时,一般操作系统会给这个进程发信号,让这个进程以某种错误而终止,其表现就是这个进程被操作系统杀掉!它的内部逻辑类似于手动调用 kill 指令。

kill -n id

n 为选项,表示各种信号;id 为被发送信号进程的 id

使用如下指令可以查看 Linux 中全部的信号

kill -l

进程终止

进程退出有三种场景:

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

这三种情况可以用2个数字组合完全覆盖!

当进程正常终止时:可以通过 echo $? 查看进程退出码。

进程正常终止有三种情况:1. 从main返回;2. 调用exit;3. 调用_exit

异常退出:ctrl + c,信号终止

exit() 函数

  1. #include <unistd.h>
  2. void exit(int status);

status 定义了进程的终止状态,也就是进程的结果正确还是不正确。status 是一个整形值,父进程可以通过 wait函数来获取该值,得到子进程的最终执行结果!

_exit() 函数

  1. #include <unistd.h>
  2. void _exit(int status);

_exit() 函数与 exit() 函数的作用可以说是一模一样,只是在细节上有所差异:在终止进程时,exit() 函数会自动刷新缓冲区,而_exit() 函数不会!

其实相当于 exit()函数 是先调用了 _exit() 函数,如图

由此可以得出一个结论:我们所认识的缓冲区,并不在操作系统内部!否则 exit() 和 exit() 都应该能刷新缓冲区。

进程等待

什么是进程等待?

通过 wait/waitpid 的方式,让父进程对子进程进行资源回收的等待过程。那为什么要进行等待呢?

第一,可以解决子进程僵尸问题带来的内存泄漏问题(进程僵尸只有父进程回收才能解决,且不能被杀掉,所以这是目前必须使用进程等待解决的问题);第二,父进程创建子进程的目的,就是让子进程来完成任务,而父进程需要知道子进程任务到底完成的如何,就必须通过等待的方式来获取子进程退出的信息(两个数字:退出码和信号)这个退出信息也许并不是必须的,但是系统需要提供这样的基础功能!

如何进行等待

在 Xshell 终端中输入如下指令,查看 waitpid 和 wait 的使用方式

wiat 和 waitpid 方法

man waitpid

wait 函数:

返回值:成功返回被等待进程 pid,失败则返回 -1。

参数:输出型参数,获取子进程退出状态,若不关心则可以设置成为NULL

waitpid 函数

返回值:当正常返回的时候waitpid返回收集到的子进程的进程ID;如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:pid:Pid=-1,等待任一个子进程。与wait等效。Pid>0,等待其进程ID与pid相等的子进程;status:WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

options:

WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

那么父进程是如何得知子进程的退出信息呢?

子进程在退出的时候,要修改状态 Z,并将自己的退出信息和退出码写入 pcd 中,父进程通过读取子进程的 pcd 获取这些信息。注意:不能使用全局变量获取子进程的信息,因为进程之间具有独立性,各有各的进程地址空间,子进程对数据的修改不改变父进程中的数据!

获取子进程的 status

当需要获取子进程的 status时,通常采用位图的思想,使用位运算!

如图,经过位运算得到需要的数字:

是否收到信号的判定方法:exit sig 是否等于0,等于0说明没收到信号,不等于0说明收到了异常的信号;当一个进程异常了(收到信号),exit code 就变得没有意义了。

进程的阻塞等待和非阻塞等待

设 wait/waitpid 的返回值为 rid 

  • rid > 0 :等待成功
  • rid == 0:等待成功,但对方(子进程)还没有退出
  • rid < 0:等待失败 

对于 waitpid 方法而言,它的第三个参数 options 可以控制父进程是阻塞等待还是非阻塞等待。

阻塞等待:子进程不退出,父进程就一直等待子进程,waitpid 不返回。这种情况在计算机中叫 宕(dang)机或应用夯(hang)住了。这种等待的缺点就是父进程在等待的过程中,什么事都做不了!

进程的阻塞式等待代码

  1. int main()
  2. {
  3. pid_t pid;
  4. pid = fork();
  5. if (pid < 0) {
  6. printf("%s fork error\n", __FUNCTION__);
  7. return 1;
  8. }
  9. else if (pid == 0) { //child
  10. printf("child is run, pid is : %d\n", getpid());
  11. sleep(5);
  12. exit(257);
  13. }
  14. else {
  15. int status = 0;
  16. pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S
  17. printf("this is test for wait\n");
  18. if (WIFEXITED(status) && ret == pid) {
  19. printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));
  20. }
  21. else {
  22. printf("wait child failed, return.\n");
  23. return 1;
  24. }
  25. }
  26. return 0;
  27. }

非阻塞等待:如果子进程的退出条件不满足,wait/waitpid 不会阻塞,而是立即返回!所以非阻塞等待一般要重复读多次调用,这种一般叫做:非阻塞轮询方案进行进程等待 这样做的好处是:在子进程没有退出的情况下,父进程可以在等待的过程中,顺便做一些占用时间比较少的事情。

进程的非阻塞式等待代码

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <sys/wait.h>
  5. int main()
  6. {
  7. pid_t pid;
  8. pid = fork();
  9. if (pid < 0) {
  10. printf("%s fork error\n", __FUNCTION__);
  11. return 1;
  12. }
  13. else if (pid == 0) { //child
  14. printf("child is run, pid is : %d\n", getpid());
  15. sleep(5);
  16. exit(1);
  17. }
  18. else {
  19. int status = 0;
  20. pid_t ret = 0;
  21. do
  22. {
  23. ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
  24. if (ret == 0) {
  25. printf("child is running\n");
  26. }
  27. sleep(1);
  28. } while (ret == 0);
  29. if (WIFEXITED(status) && ret == pid) {
  30. printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));
  31. }
  32. else {
  33. printf("wait child failed, return.\n");
  34. return 1;
  35. }
  36. }
  37. return 0;
  38. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/882955
推荐阅读
相关标签
  

闽ICP备14008679号