Linux——进程的退出、等待与替换
文章目录
- 一、进程退出
- 1.退出场景
- 2.常见退出方法
- 3.退出码与退出信号
- 4._exit函数与exit函数
- 二、进程等待
- 1.什么是进程等待
- 2.为什么要有进程等待
- 僵尸进程与孤儿进程
- 3.如何进行进程等待
- 3.1.`wait`
- 3.2.`waitpid`
- 3.3.获取status
- 3.4.阻塞与非阻塞等待
- 三、进程替换
- 1.进程替换原理
- 2.进程替换函数
一、进程退出
1.退出场景
进程退出一共就三种情况:
- 进程正常退出,执行结果正确
- 进程正常退出,执行结果错误
- 进程异常退出
2.常见退出方法
- main函数中return退出
- 使用_exit函数退出
- 使用exit函数退出
_exit和exit在下文详解。
3.退出码与退出信号
- 退出码:标识程序的退出状态
- 退出信号:当程序异常退出后,退出信号标记了进程异常退出的原因,如果是正常退出则退出信号为0。
在对于退出信号可以使用kill -l
查询,如下:
4._exit函数与exit函数
_exit是一个系统提供
的接口,它的参数是一个int类型,需要传一个退出信号返回。
而exit是C语言提供
的接口,它同样是让程序退出,需要传一个退出信号返回。
而_exit与exit一个很大的区别就是 _exit不会刷新缓冲区,而exit会刷新缓冲区,其中exit底层还是使用了_exit实现。
可以做一个简单的小测试:
注意:
- 这里程序是从exit和_exit退出的,而不是从return 0退出。
- 这里我有意在printf输出字符串后没有加\n,因为\n会让缓冲区刷新,会干扰测试。
二、进程等待
1.什么是进程等待
进程等待指的是父进程等待子进程结束。
在子进程结束后它的pcb不会立马释放,而是进入僵尸状态,让父进程回收。 当然如果父进程永远不来回收,那么子进程pcb就永远得不到释放,从而内存泄漏。
而父进程在等待子进程退出这个过程就叫作进程等待。
2.为什么要有进程等待
- 父进程创建子进程就是要子进程完成任务,所以父进程需要知道任务的完成情况,从而决定下一步要做什么。
- ⼦进程退出,⽗进程如果不管不顾,就可能造成
僵⼫进程
的问题,进⽽造成内存泄漏
。 - ⽗进程通过进程等待的⽅式,回收⼦进程资源,获取⼦进程退出信息(任务完成情况)。
僵尸进程与孤儿进程
僵尸进程
: 是父进程一直都在,子进程退出了,但是父进程没有回收子进程,子进程保留 PID 和内核资源,状态为 Z(Zombie)。
风险:占用内核进程表条目,过多会导致系统无法创建新进程。
解决:修复父进程逻辑或终止父进程(僵尸会被 init 接管清理)。
孤儿进程
: 是父进程先退出了,子进程还在执行, 子进程被 init 进程(PID=1)接管,状态仍为 R/S 等活跃状态。
风险:自动变成后台进程,无直接危害,但需确保子进程能正确完成任务或终止。
解决:通常由 init 自动管理。
3.如何进行进程等待
3.1.wait
wait是一个用来进程等待的函数,使用它需要包含的头文件为 sys/types.h 和 sys/wait.h。函数声明如下:
pid_t wait(int *status);
- 返回值:成功返回被等待进程pid,失败返回-1。
- 参数:输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL。
3.2.waitpid
waitpid同样是一个用来进程等待的函数,它的功能要更多,使用它需要包含的头文件为 sys/types.h 和 sys/wait.h。函数声明如下:
pid_t waitpid(pid_t pid, int *status, int options);
返回值:
- 当正常返回的时候waitpid返回收集到的⼦进程的进程ID;
- 如果第三个参数设置了选项WNOHANG,那么调⽤中waitpid发现没有已退出的⼦进程可收集,否则返回0。
- 如果调⽤中出错,则返回-1,这时errno会被设置成相应的值以指⽰错误所在;
参数:
- 1.pid:(1)
pid=-1,等待任⼀个⼦进程
。与wait等效
(2)pid>0,等待其进程ID为pid的⼦进程
。 - 2.status:输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL。下面3.3.再细讲。
- 3.options:默认为0,表⽰如果子进程没有结束需要等待。
如果设置为WNOHANG,子进程没有结束则不需要等待,接着往下执行。
3.3.获取status
status可以得到进程的退出码和退出信号。
它是如何同时储存退出码和退出信号呢?其实用了一个位图的思想。status是一个int类型一共占4*8个比特位。而这里用了它的低16位,如下:
所以退出码我们可以使用(status>>8)&0xFF获取,退出信号通过status&0x7F获取。当然系统给我们提供了WIFEXITED和WEXITSTATUS两个宏来做进程退出状态检查。功能如下:
- WIFEXITED(status):若为正常终⽌⼦进程返回的状态,则为真(查看进程是否是正常退出)。
- WEXITSTATUS(status):若WIFEXITED⾮零,提取⼦进程退出码(查看进程的退出码)。
3.4.阻塞与非阻塞等待
- 阻塞等待:使用wait或使用waitpid第三个参数
传0
的话,父进程在等待子进程过程中,如果子进程没有结束,那么父进程就会一直等。直到子进程退出。 - 非阻塞等待:父进程是很忙的,当子进程没有退出的时候也不能在那里干等着。所以正如刚才所讲,waitpid第三个参数中传入
WNOHANG
就能实现子进程还没退出就不等,继续做自己的其他工作,这就是非阻塞等待。当然刚才没有等到子进程结束,还得找个时间再等,要不然到时候子进程pcd就不能被回收。所以如果使用WNOHANG就需要重复的waitpid,这就是非阻塞轮询。
三、进程替换
1.进程替换原理
如下是一个程序替换的程序(execl函数使用在下文会讲解):
#include<stdio.h>
#include<unistd.h>
int main()
{execl("/usr/bin/ls","/usr/bin/ls","-la",NULL);//程序替换printf("hello linux\n");return 0;}
进程替换就是在一个进程执行中把该进程后续的内容替换成其他进程。要知道指令的本质就是一个可执行文件,它是用c语言写的。以上代码就相当于把ls这个程序的代码把后面的原代码覆盖掉, 所以其中pcd,虚拟地址空间,页表并没有改变,而是物理内存改变了。
注意:
(1) 进程替换并不会有新的进程生成。
(2)进程替换可以替换为任何进程,无论是什么语言,打个比方就是说,x写的程序,在执行过程中可以替换成y写的程序(x,y表示任意语言)。
2.进程替换函数
如下是一个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[]);
int execve(const char *path, char *const argv[], char *const envp[]);
替换一个程序起码要知道这个程序的路径吧?需要知道它的名字吧?还需要知道命令行参数吧?
- 第一个参数:需要替换的程序的路径。
- 第二个参数:需要替换的程序的名字。
- 第三个参数:给该程序传入命令行参数。
- 第四个参数:如果有这个参数,需要传入自己组装的环境变量。如果没有改参数不用传,就用当前的环境变量。
上面函数的命名特点如下:
- l(list):表⽰参数采⽤列表传入。
- v(vector):参数⽤数组。
- p(path):有p⾃动搜索环境变量PATH。
- e(env):表⽰⾃⼰维护环境变量。
这些exec族的返回规则是一样的,如果执行成功没有返回值,如果调用失败返回-1。