Linux系统应用编程 --- 进程原语(三)

简介: Linux系统应用编程 --- 进程原语(三)

wait & waitpdi

1. 僵尸进程和孤儿进程的概念

僵尸进程

(1)子进程先于父进程结束。子进程结束后父进程此时并不一定立即就能帮子进程“收尸”,在这一段(子进程已经结束且父进程尚未帮其收尸)子进程就被称为僵尸进程。

(2)子进程除 task_struct 和栈外其余内存空间皆已清理

(3)父进程可以使用 wait 或 waitpid 以显式回收子进程的剩余待回收内存资源并且获取子进程退出状态。【父进程帮子进程收尸是要调用函数的】

(4)父进程也可以不使用 wait 或者 waitpid 回收子进程,此时父进程结束时一样会回收子进程的剩余待回收内存资源。(这样设计是为了防止父进程忘记显式调用 wait/waitpid 来回收子进程从而造成内存泄漏)

 

孤儿进程

(1)父进程先于子进程结束,子进程成为一个孤儿进程。

(2)linux 系统规定:所有的孤儿进程都自动成为一个特殊进程(进程 1,也就是 init 进程)的子进程。

2. wai & waitpid函数原型

wait是早起从unix继承过来的函数,是一个阻塞函数,比喻父进程调用wait之后,就会阻塞等待回收子进程,如果没有子进程结束,就一直阻塞,显然降低系统性能。

为了补救,后面又有了waitpid函数。

1. #include <sys/types.h>
2. 
3. #include <sys/wait.h>
4. 
5. pid_t wait(int *status);
6. 
7. pid_t waitpid(pid_t pid, int *status, int options);

wait返回值:成功回收子进程,返回终止的子进程的pid,错误返回-1

waitpid函数参数解释:

1) 参数1 pid

参数值

含义

pid<-1

等待进程组号为pid绝对值的任何子进程。

pid=-1

等待任何子进程,此时的waitpid()函数就退化成了普通的wait()函数。

pid=0

等待进程组号与目前进程相同的任何子进程,也就是说任何和调用waitpid()函数的进程在同一个进程组的进程。

pid>0

等待进程号为pid的子进程。

2)int *status

这个参数将保存子进程的状态信息,有了这个信息父进程就可以了解子进程为什么会退出,是正常退出还是出了什么错误。如果status不是空指针,则状态信息将被写入寄存器指向的位置。当然,如果不关心子进程为什么推出的话,也可以传入空指针。

Linux提供了一些非常有用的宏来帮助解析这个状态信息,这些宏都定义在sys/wait.h头文件中。主要有以下几个:

说明

WIFEXITED(status)

如果子进程正常结束,它就返回真;否则返回假。

WEXITSTATUS(status)

如果WIFEXITED(status)为真,则可以用该宏取得子进程exit()返回的结束代码。

WIFSIGNALED(status)

如果子进程因为一个未捕获的信号而终止,它就返回真;否则返回假。

WTERMSIG(status)

如果WIFSIGNALED(status)为真,则可以用该宏获得导致子进程终止的信号代码。

WSTOPSIG(status)

如果当前子进程被暂停了,则返回真;否则返回假。

WIFSTOPPED(status)

如果WIFSTOPPED(status)为真,则可以使用该宏获得导致子进程暂停的信号代码。

3) int options

参数options提供了一些另外的选项来控制waitpid()函数的行为。如果不想使用这些选项,则可以把这个参数设为0。主要使用的有以下两个选项:

参数

说明

WNOHANG

如果pid指定的子进程没有结束,则waitpid()函数立即返回0,而不是阻塞在这个函数上等待;如果结束了,则返回该子进程的进程号。

WUNTRACED

如果子进程进入暂停状态,则马上返回。

这些参数可以用“|”运算符连接起来使用。

如果waitpid()函数执行成功,则返回子进程的进程号;如果有错误发生,则返回-1,并且将失败的原因存放在errno变量中。

失败的原因主要有:没有子进程(errno设置为ECHILD),调用被某个信号中断(errno设置为EINTR)或选项参数无效(errno设置为EINVAL)

如果像这样调用waitpid函数:waitpid(-1, status, 0),这此时waitpid()函数就完全退化成了wait()函数。

3. 从code理解wait & waitpid函数的作用

code 1 :

1. #include <stdio.h>
2. #include <unistd.h>
3. #include <stdlib.h>
4. #include <sys/types.h>
5. #include <wait.h>
6. 
7. int main(void)
8. {
9.        pid_t pid, pid_c;
10. //调用一次返回2次,在父进程返回子进程的PID,在子进程中返回0
11. 
12.        int n = 10;
13.        pid = fork();
14. 
15. if(pid > 0)
16.        {/*in parent*/
17.               while(1)
18.               {
19.                      printf("I am parent pid = %d\n", getpid());
20.                      pid_c = wait(NULL);            //wait是一个阻塞函数,等待回收子进程资源
21. 
22. //如果没有子进程,wait返回-1
23.                      printf("wait for child %d.\n", pid_c);
24.                      sleep(1);
25.               }
26.        }
27. 
28. else if(pid == 0)
29.        {/*in child*/
30. //printf("I am child %d\n", n++);
31.               printf("I am child pid = %d.\n", getpid());
32. //printf("my parent pid = %d.\n", getppid());
33.               sleep(10);
34.        }
35. 
36. else
37.        {
38.               perror("fork");
39. exit(0);
40.        }
41. 
42. return 0;
43. }

执行结果如下:

分析:

刚进来的时候打印一次父子进程的ID号,然后子进程slepp 10s,父进程执行wait等待回收子进程,阻塞在这里,直到子进程结束,此时返回子进程的Pid号。子进程结束之后,wait的返回值就是-1.

僵尸进程对内存的影响:子进程如果没有回收,子进程占了一个PCB,浪费内存,造成内存泄露。

wait函数特点:

1. wait是一个阻塞函数,等待回收子进程资源,如果没有子进程,则返回-1.

2. wait函数回收子进程的PCB,避免浪费内存。既然会浪费内存,那么操作系统在设计的时候,为什么不把所有的僵尸进程都直接销毁?

因为操作系统需要知道每个进程是怎么销毁的,怎么获取一个进程的退出码,通过wait传参。

 

code 2 :

1. #include <stdio.h>
2. #include <sys/types.h>
3. #include <unistd.h>
4. #include <wait.h>
5. 
6. 
7. int main(void)
8. {
9. 
10.        pid_t pid;
11. 
12.        int n = 3;
13. 
14.        while(n--)
15.        {
16.               pid = fork();
17. if(pid == 0) break;
18.        }
19. 
20. if(pid == 0)
21.        {
22. //while(1)
23.               {
24.                      printf("I am child %d.\n", getpid());
25.                      sleep(3);
26.               }
27.        }
28. 
29. else if(pid > 0)
30.        {
31. 
32.               pid_t pid_c;
33.               while(1){
34. 
35. //pid_c = wait(NULL);
36.                      printf("I am parent.\n");
37.                      pid_c = waitpid(0, NULL, WNOHANG);
38. 
39. if(pid_c == -1)     
40. continue;
41. else
42.                             printf("wait for child %d.\n", pid_c);
43. if(pid_c > 0){
44.                             n++;     
45.                      }
46. if(n == 2)
47.                             break;                  
48.                      sleep(1);
49.               }
50.        }
51. 
52. return 0;
53. }

一个父进程fork了三个子进程,通过ps – ajx查看

可以看到三个子进程的pid号依次递增,,父进程与三个子进程同属于一个进程组。(父进程创建子进程之后,子进程跟父进程默认是一个组)

如何kill掉一个进程组,命令【kill -9 -14943】

注释掉while(1),执行之后可以看到,通过WNOHANG确实将waitpid设置成了非阻塞的。

相关文章
|
8月前
|
监控 关系型数据库 MySQL
在CentOS系统中,如何统计哪个进程打开了文件描述符?
利用上述方法,你可以有效地监控和统计CentOS系统中的进程打开的文件描述符数量,以帮助排查错误或优化系统配置。通过组合使用各种工具和命令,可以获得对系统状态和行为的深入了解,进而做出相应的调整和
360 5
|
11月前
|
安全 应用服务中间件 网络安全
在Linux环境部署Flask应用并启用SSL/TLS安全协议
至此,你的Flask应用应该能够通过安全的HTTPS协议提供服务了。记得定期更新SSL证书,Certbot可以帮你自动更新证书。可以设定cronjob以实现这一点。
774 10
|
11月前
|
Java Linux 网络安全
Linux云端服务器上部署Spring Boot应用的教程。
此流程涉及Linux命令行操作、系统服务管理及网络安全知识,需要管理员权限以进行配置和服务管理。务必在一个测试环境中验证所有步骤,确保一切配置正确无误后,再将应用部署到生产环境中。也可以使用如Ansible、Chef等配置管理工具来自动化部署过程,提升效率和可靠性。
1038 13
|
10月前
|
存储 数据采集 监控
ubuntu(linux)系统主要应用于哪些工业场景中?研维三防ubuntu系统的手持工业三防平板电脑在哪些行业中有实际应用
Ubuntu 系统凭借其独特的优势,在众多工业场景中得到了广泛应用,为工业数字化、智能化发展提供了有力支持。而研维三防基于 Ubuntu 定制系统的手持工业三防平板电脑,更是将 Ubuntu 系统的优势与工业级的性能、坚固耐用性完美结合,在电力、物流、制造等多个行业中展现出强大的应用价值,助力企业提高生产效率、优化管理流程、提升产品质量,成为推动工业现代化发展的重要力量。随着技术的不断进步与创新,相信 Ubuntu 系统以及研维三防这类工业级设备将在更多的工业领域中发挥更大的作用,为工业发展带来更多的机遇与变革。
|
安全 算法 Ubuntu
Linux(openssl)环境:编程控制让证书自签的技巧。
总结:在Linux环境中,OpenSSL是一个非常实用的工具,可以帮助我们轻松地生成自签名证书。通过上述三个简单步骤,即可为内部网络、测试环境或开发环境创建自签名证书。但在公共访问场景下,建议购买经过权威认证机构签发的证书,以避免安全警告。
658 13
|
弹性计算 运维 监控
基于进程热点分析与系统资源优化的智能运维实践
智能服务器管理平台提供直观的可视化界面,助力高效操作系统管理。核心功能包括运维监控、智能助手和扩展插件管理,支持系统健康监控、故障诊断等,确保集群稳定运行。首次使用需激活服务并安装管控组件。平台还提供进程热点追踪、性能观测与优化建议,帮助开发人员快速识别和解决性能瓶颈。定期分析和多维度监控可提前预警潜在问题,保障系统长期稳定运行。
622 17
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
JavaScript Ubuntu Linux
如何在阿里云的linux上搭建Node.js编程环境?
本指南介绍如何在阿里云Linux服务器(Ubuntu/CentOS)上搭建Node.js环境,包含两种安装方式:包管理器快速安装和NVM多版本管理。同时覆盖全局npm工具配置、应用部署示例(如Express服务)、PM2持久化运行、阿里云安全组设置及外部访问验证等步骤,助你完成开发与生产环境的搭建。
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
308 26
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
332 17