• 进程控制——进程等待


    ✅<1>主页::我的代码爱吃辣
    📃<2>知识讲解:Linux——进程等待
    ☂️<3>开发环境:Centos7
    💬<4>前言:之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。父进程通过进程等待的方式,回收子进程资源。

    目录

    一.进程退出场景

    二.进程常见的退出方法

    1. 从main返回

    2. 调用exit

    3.系统调用 _exit 

    4.对比exit()和_exit()

    5.ctrl + c,信号终止

    三.进程等待

     1.进程等待的必要性

     2.进程等待的方法wait方法

     3.进程等待的方法wait方法waitpid方法

    四.获取子进程的status

     1.代码正常终止

     2.代码异常终止


    一.进程退出场景

    1. 代码运行完毕,结果正确
    2. 代码运行完毕,结果不正确
    3. 代码异常终止

     见一见这三种情况:

    测试代码程序结束结果正确:

    正常终止(可以通过 echo $? 查看进程退出码)

    1. #include
    2. int main()
    3. {
    4. printf("***********begin**********\n");
    5. int sum = 0;
    6. for (int i = 1; i <= 100; i++)
    7. sum += i;
    8. printf("***********end**********\n");
    9. if (sum == 5050)
    10. return 0;
    11. else
    12. return 1;
    13. }

    makefile文件:

    1. proc:proc.c
    2. gcc -o $@ $^ -std=c99
    3. .PHONY:clean
    4. clean:
    5. rm -rf proc

    测试结果:

    测试代码代码运行完毕,结果不正确:

    1. #include
    2. int main()
    3. {
    4. printf("***********begin**********\n");
    5. int sum = 0;
    6. for (int i = 1; i < 100; i++)
    7. sum += i;
    8. printf("***********end**********\n");
    9. if (sum == 5050)
    10. return 0;
    11. else
    12. return 1;
    13. }

    测试结果:

     测试代码程序异常终止:

    • 程序异常终止的时候,代码的结果是否正确已经不重要了。
    • 崩溃的本质:进程因为某些原因,导致进程收到了来自操作系统的信号(kill -9)。

    注意:进程的退出码,只有八个比特位表示,如果我们返回的是-1,查询的推出吗是255.

    二.进程常见的退出方法

    1. 从main返回

    我们上述展示的:测试代码程序结束结果正确和测试代码代码运行完毕,结果不正确,都是从main返回的示例。

    2. 调用exit

    测试代码:

    1. #include
    2. #include
    3. int main()
    4. {
    5. printf("***********begin**********\n");
    6. int sum = 0;
    7. for (int i = 1; i < 100; i++)
    8. sum += i;
    9. exit(100);
    10. printf("***********end**********\n");
    11. if (sum == 5050)
    12. return 0;
    13. else
    14. return 1;
    15. }

    测试结果:

    3.系统调用 _exit 

    测试代码:

    1. #include
    2. #include
    3. int main()
    4. {
    5. printf("***********begin**********\n");
    6. int sum = 0;
    7. for (int i = 1; i <= 100; i++)
    8. sum += i;
    9. _exit(1000);
    10. printf("***********end**********\n");
    11. if (sum == 5050)
    12. return -1;
    13. else
    14. return 1;
    15. }

    测试结果:

    4.对比exit()和_exit()

     测试代码:

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. printf("hello world");
    7. exit(0);
    8. return 0;
    9. }

     我们将exit换成_exit:

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. printf("hello world");
    7. _exit(0);
    8. return 0;
    9. }

    测试结果:

    注意:

    1. exit在程序退出时,会刷新程序的缓冲区。
    2. _exit在程序推出时,就是单纯的直接退出,不会做任何处理。

    注意:

    return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。

     5.ctrl + c,信号终止

     通过信号的方式直接终止一个进程。

    ctrl + c  就是使用 kill -9 信号直接终止进程。

    测试代码:

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. while (1)
    7. {
    8. printf("hello world\n");
    9. sleep(1);
    10. }
    11. return 0;
    12. }

    三.进程等待

     1.进程等待的必要性

    1. 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
    2. 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
    3. 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
    4. 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

     2.进程等待的方法wait方法

    1. #include
    2. #include
    3. pid_t wait(int*status);

    返回值:

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

    参数:

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

    见一见僵尸状态:

    测试代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. int id = fork();
    9. if (id == 0) // 子进程
    10. {
    11. int n = 5;
    12. while (n--)
    13. {
    14. printf("我是一个子进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
    15. sleep(1);
    16. }
    17. return 0;
    18. }
    19. else if (id > 0) // 父进程
    20. {
    21. int n = 10;
    22. while (n--)
    23. {
    24. printf("我是一个父进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
    25. sleep(1);
    26. }
    27. }
    28. wait(NULL);
    29. return 0;
    30. }

    测试结果:

     在子进程退出以后,出现了僵尸状态。

     回收僵尸状态:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. int id = fork();
    9. if (id == 0) // 子进程
    10. {
    11. int n = 5;
    12. while (n--)
    13. {
    14. printf("我是一个子进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
    15. sleep(1);
    16. }
    17. return 0;
    18. }
    19. else if (id > 0) // 父进程
    20. {
    21. wait(NULL);//子进程一旦退出就立马回收
    22. int n = 10;
    23. while (n--)
    24. {
    25. printf("我是一个父进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
    26. sleep(1);
    27. }
    28. }
    29. return 0;
    30. }

    测试结果:

    说明:

    1. 子进程一旦退出,其僵尸状态就会被回收。
    2. 子进程没有退出的时候,父进程阻塞无法执行其他代码。

     3.进程等待的方法wait方法waitpid方法

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

    返回值:

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

    参数:
    pid:

    1. Pid=-1,等待任一个子进程。与wait等效。
    2. Pid>0.等待其进程ID与pid相等的子进程。

    status:

    1. WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
    2. WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

    options:

    1. WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
    2. 0:当子进程还没有退出的时候,父进程阻塞在waitpid()函数处,等待子进程退出。

    阻塞时等待任意一个子进程:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. int status;
    9. int id = fork();
    10. if (id == 0) // 子进程
    11. {
    12. int n = 5;
    13. while (n--)
    14. {
    15. printf("我是一个子进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
    16. sleep(1);
    17. }
    18. return 0;
    19. }
    20. else if (id > 0) // 父进程
    21. {
    22. waitpid(-1, &status, 0);
    23. int n = 10;
    24. while (n--)
    25. {
    26. printf("我是一个父进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
    27. sleep(1);
    28. }
    29. }
    30. return 0;
    31. }

    测试结果:

     非阻塞式等待任意一个子进程:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. int status;
    9. int id = fork();
    10. if (id == 0) // 子进程
    11. {
    12. int n = 5;
    13. while (n--)
    14. {
    15. printf("我是一个子进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
    16. sleep(1);
    17. }
    18. return 0;
    19. }
    20. else if (id > 0) // 父进程
    21. {
    22. int n = 10;
    23. while (n--)
    24. {
    25. pid_t pid = waitpid(-1, &status, WNOHANG);
    26. if (pid == 0)
    27. {
    28. printf("子进程还未结束\n");
    29. }
    30. else
    31. {
    32. printf("子进程已经结束!!!\n");
    33. }
    34. printf("我是一个父进程,我的pid:%d,我的ppid:%d\n", getgid(), getppid());
    35. sleep(1);
    36. }
    37. }
    38. return 0;
    39. }

    测试结果:

    说明:子进程还未结束时,父进程仍然可以自己执行,不受任何影响,如果父进程等待到子进程已经退出,就会回收子进程的僵尸状态。

    • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
    • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
    • 如果不存在该子进程,则立即出错返回。

    四.获取子进程的status

    wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
    如果传递NULL,表示不关心子进程的退出状态信息。
    否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
    status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

     注意:

    1. 如果进程正常终止,status 后面七位应该是0,代表没有收到信号,前面八位时退出码。
    2. 如果进程异常终止,前面七位不用处,中间一位core dump 标志位,最后七位时收到的信号。

     1.代码正常终止

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. int status;
    9. pid_t id = fork();
    10. if (id == 0) // 子进程
    11. {
    12. int n = 5;
    13. while (n--)
    14. {
    15. printf("我是一个子进程,我的pid:%d,我的ppid:%d\n", getpid(), getppid());
    16. sleep(1);
    17. }
    18. return 100;
    19. }
    20. else if (id > 0) // 父进程
    21. {
    22. int n = 10;
    23. while (n--)
    24. {
    25. pid_t ret = waitpid(-1, &status, WNOHANG);
    26. if (ret == 0)
    27. {
    28. printf("子进程还未结束\n");
    29. }
    30. else // ret > 0
    31. {
    32. if ((status & 0x7F) == 0) // 后七位结果为0,没有收到信号,正常退出。
    33. {
    34. printf("子进程正常结束!!!子进程pid:%d,退出码:%d\n", ret, (status >> 8) & 0x7F);
    35. }
    36. else
    37. {
    38. printf("子进程异常退出子进程pid:%d,收到信号:%d\n", ret, (status) & 0x3F);
    39. }
    40. sleep(1);
    41. exit(0);
    42. }
    43. sleep(1);
    44. printf("我是一个父进程,我的pid:%d,我的ppid:%d\n", getpid(), getppid());
    45. }
    46. }
    47. return 0;
    48. }

    测试结果:

     2.代码异常终止

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. int status;
    9. pid_t id = fork();
    10. if (id == 0) // 子进程
    11. {
    12. int n = 15;
    13. while (n--)
    14. {
    15. printf("我是一个子进程,我的pid:%d,我的ppid:%d\n", getpid(), getppid());
    16. sleep(1);
    17. }
    18. return 100;
    19. }
    20. else if (id > 0) // 父进程
    21. {
    22. int n = 10;
    23. while (n--)
    24. {
    25. pid_t ret = waitpid(-1, &status, WNOHANG);
    26. if (ret == 0)
    27. {
    28. printf("子进程还未结束\n");
    29. }
    30. else // ret > 0
    31. {
    32. if ((status & 0x7F) == 0) // 后七位结果为0,没有收到信号,正常退出。
    33. {
    34. printf("子进程正常结束!!!子进程pid:%d,退出码:%d\n", ret, (status >> 8) & 0x7F);
    35. }
    36. else
    37. {
    38. printf("子进程异常退出子进程pid:%d,收到信号:%d\n", ret, (status) & 0x3F);
    39. }
    40. sleep(1);
    41. exit(0);
    42. }
    43. sleep(1);
    44. printf("我是一个父进程,我的pid:%d,我的ppid:%d\n", getpid(), getppid());
    45. }
    46. }
    47. return 0;
    48. }

     注意:如果我们在提取退出码时,不想使用位运算,我们可以直接使用:

    1. WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
    2. WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

  • 相关阅读:
    Java项目:文具学习用品商城系统(java+SSM+JSP+jQuery+Mysql)
    从零开始Blazor Server(4)--登录系统
    高德地图车机版导航
    ROS1 and ROS2一键安装
    java基于微信小程序的校园二手闲置商品交易系统 uniapp 小程序
    一分钟教会你使用Docker Desktop搭建RocketMQ--巨简单
    Verilog数字系统教程学习——Verilog语法的基本概念
    安卓游戏开发之图形渲染技术优劣分析
    新学期,新FLAG | 要以码为梦而非夜郎自大
    LeetCode.1260. 二维网格迁移____原地暴力 / 降维+循环数组直接定位
  • 原文地址:https://blog.csdn.net/qq_63943454/article/details/132889742