【linux】进程【4】


一.进程等待

1.1 为什么要进行进程等待

1) 防止内存泄漏
进程退出,如果父进程不管子进程,子进程就会处于僵尸状态,长时间的僵尸状态会导致内存泄漏。因为没人去回收这个子进程,但是子进程却需要占用资源进行维护。(虽然说如果最后该子进程的父进程也挂掉了之后,就会被操作系统回收。但是大多数情况下这个进程一旦被运行起来,一般都会成为常驻进程,一直在后台运行的。)

2) 得知子进程的状态
父进程创建了子进程,是要让子进程办事的,那么子进程把任务完成的怎么样,父进程需要关心的
。并且子进程把任务完成的怎么样,是用之前进程终止的三种情况来标定的。那么子进程如果可以返回上述相关结果的状态,并且让父进程获取到,那么父进程就可以得知子进程把事情办得怎么样了。

因此,父进程了解子进程的状态,回收僵尸状态,都需要通过进程等待来完成。怎么完成呢?就是通过进程等待。进程等待通过让父进程回收子进程,释放子进程相关资源(数据代码和相关的数据结构),并且获得子进程的退出结果,进而指导接下来的工作。

1.2 获取子进程status

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。 如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程,
如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中, 这是一个整数值(int),
指出了子进程是正常退出还是被非正常结束的,以及正常结束时的返回值,或被哪一个信号结束的等信息。

在这里插入图片描述

子进程的退出原因,通常保存在stasus中。虽然status是一个4字节的int,但是实际上仅仅使用了一个字节去保存退出原因,也就是说其余三个字节是没有的,这同样意味着,退出原因最大不能超过255。

在这里插入图片描述
我们想要能看到准确的退出信号就需要:(status>>8)&0xFF

在这里插入图片描述

进程异常退出,或者崩溃,本质是操作系统杀掉了我们的进程!
那么操作系统是怎么杀掉我们的进程呢?本质是通过发送信号的方式

在这里插入图片描述

下面正常的运行:
在这里插入图片描述
异常退出:

在这里插入图片描述
程序异常,不光只是内部代码有问题,也可能是外力直接杀掉(此时子进程不能确定到底跑没跑完——所以退出码就没有意义了)。

1.3进程等待的方法

wait方法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值: 成功返回被等待进程pid,失败返回-1。
参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止

在这里插入图片描述
在这里插入图片描述

waitpid方法

waitpid(id,NULL,0) 等价于 wait(NULL).

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

参数:

pid:

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

status:(输出型参数)

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

在这里插入图片描述

options: (默认为0,表示阻塞等待)

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

在这里插入图片描述

在这里插入图片描述

二.进程程序替换

2.1 为什么要进程替换

  • 因为父进程创建出来的子进程和父进程拥有相同的代码段,所以,子进程看到的代码和父进程是一样的。当我们想要让子进程执行不同的程序时候,就需要让子进程调用进程程序替换的接口,从而让子进程执行不一样的代码
  • 举个实例:像牛客网这些刷题网站他在接收你写的程序时就会创建一个子进程,让子进程去执行你的代码,这样即使你的代码写的有问题那也是子进程崩掉了。

2.2替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变

在这里插入图片描述

2.3替换函数

其实有六种以exec开头的函数,统称exec函数

#include <unistd.h>`
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[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

在这里插入图片描述
在这里插入图片描述
命名理解
这些函数原型看起来很容易混,但只要掌握了规律就很好记。

l(list) : 表示参数采用列表

v(vector) : 参数用数组

p(path) : 有p自动搜索环境变量PATH

e(env) : 表示自己维护环境变量

在这里插入图片描述

2.3.1 int execl (const char *path, const char *arg, …);

在这里插入图片描述

那为什么上面的exit(1)之后的代码没有运行呢
原因:execl是程序替换,调用该函数成功后,会将当前进程的所有代码和数据都进行替换!包括已经执行和没有执行的!所以,一旦调用成功后,后续所以的代码,全都不会执行!
execl调用成功后为什么没有返回值
原因:execl本身也属于其中的代码,它调用成功后是连着execl本身的所有代码都替换了(连自己都换掉了),相当于完全重新开始了,与原来的代码没有什么关系了,就不可能再返回。

2.3.2 int execv(const char *path, char *const argv[]);

在这里插入图片描述

3.3.3 int execlp(const char *file, const char *arg, …);

execlp与execl几乎相同,唯一不同的地方在于,execlp在进行传参时,第一个参数不需要传命令的路径,它会自动到环境变量里面找
在这里插入图片描述
但是execlp中我们所写的为什么有2个ls呢?
在这里插入图片描述

2.3.4 int execvp(const char *file, char *const argv[]);

execvp 和execv 的差别与 execl 和 execlp 的差别一模一样,在execvp中第一个参数与execlp一样不需要带路径,会自动到环境变量里面找
在这里插入图片描述

那么我们怎么在自己的代码中运行自己写的程序呢?
以下以execl为例

在这里插入图片描述

2.3.5 int execle(const char *path, const char *arg, …,char *const envp[]);

在这里插入图片描述
在这里插入图片描述

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值