目录
[一. 进程回收](#一. 进程回收)
[二. 进程回收相关函数](#二. 进程回收相关函数)
[2.1 wait()](#2.1 wait())
[2.2 waitpid()](#2.2 waitpid())
[三. exec函数族](#三. exec函数族)
[3.1 exec函数族实现的原理](#3.1 exec函数族实现的原理)
[3.2 exec函数族的区别](#3.2 exec函数族的区别)
[3.3 shell命令的种类](#3.3 shell命令的种类)
[3.4 返回值](#3.4 返回值)
[3.5 system函数](#3.5 system函数)
一. 进程回收
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。
二. 进程回收相关函数
2.1 wait()

作用:阻塞等待子进程结束并且将其回收
**返回值:**成功返回回收的进程的pid 失败返回-1且设置errno
参数wstatus的作用:
当进程终止时,操作系统的隐式回收机制:1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)
不关心子进程的退出状态时,可用传入NULL
可使用wait函数传出参数wstatus来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:
- **WIFEXITED(wstatus)**为真 → 进程正常结束
WEXITSTATUS(wstatus) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
注意:wstatus是一个int类型的32位数,而进程的退出状态在wstatus中只占8位,故需要用宏函数来提取,注意退出状态是8位:0~255,在子进程中的退出值最好不要超过此范围
- WIFSIGNALED(wstatus) 为真 → 进程异常终止
WTERMSIG(wstatus) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
*3. WIFSTOPPED(wstatus) 为真 → 进程处于暂停状态
WSTOPSIG(wstatus) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(wstatus) 为真 → 进程暂停后已经继续运行
程序:
cpp
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork()");
exit(1);
}
else if(pid > 0){
int wstatus;
//pid_t wpid = wait(NULL);//不保存子进程的退出状态
//printf("parent:wait finish,pid is %d\n",wpid);
pid_t wpid = wait(&wstatus);
if(WIFEXITED(wstatus)){
printf("parent:child terminated normally,pid is %d,status is %d\n",
wpid,WEXITSTATUS(wstatus));
//注意这里退出状态是8位:范围0~255,在子进程中注意退出值不可超过此范围
}
else if(WIFSIGNALED(wstatus)){
printf("parent:child kill by sig,pid is %d,sig is %d\n",
wpid,WTERMSIG(wstatus));
}
while(1){
printf("parent:pid is %d\n",getpid());
sleep(1);
}
}
else{
int num = 5;
while(num--){
printf("child:pid is %d\n",getpid());
sleep(1);
}
exit(66);
}
return 0;
}
结果:
子进程正常退出结果:

子进程被信号结束结果:


2.2 waitpid()

作用: 可用指定回收子进程,也可用设置非阻塞模式
参数:
pid:
|-------|--------------------|
| > 0 | 回收指定pid的子进程 |
| = 0 | 回收与调用该函数同组的任意进程 |
| = -1 | 回收任意子进程 |
| < -1 | 回收取绝对值对应的进程组内的任意进程 |
options: 可以设置非阻塞回收:WNOHANG
**wstatus:**作用同wait函数
**返回值:**成功返回回收进程的pid号 失败返回-1并且设置errno
注意:当设置了非阻塞回收并且无子进程结束时,将返回0
注意: 使用waitpid回收子进程时,应该使用轮询的方法
代码:
cpp
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork()");
exit(1);
}
else if(pid > 0){
int wstatus;
//使用waitpid非阻塞回收时要轮询
pid_t wpid;
while(1){
wpid = waitpid(pid, NULL, WNOHANG);
if(wpid > 0){
printf("wait finish, wpid is %d\n",wpid);
}
else if(wpid < 0){
perror("waitpid()");
}
printf("parent:pid is %d\n",getpid());
sleep(1);
}
}
else{
int num = 5;
while(num--){
printf("child:pid is %d\n",getpid());
sleep(1);
}
exit(66);
}
return 0;
}
结果:

三. exec函数族
3.1 exec函数族实现的原理
1、fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换(代码区和数据区) ,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的pid并未改变。
2、将当前进程的**.text、.data** 替换为所要加载的程序的**.text、.data** ,然后让进程从新的.text第一条指令开始执行,但进程ID不变。
3、实现原理:系统实际上是完成文本区的二进制代码替换,并不创建新进程

3、作用: 执行一个新的文件,在一个进程中执行另外一个文件
4、**参数:**路径+执行程序时需要传递的参数+NULL哨兵位
命令whereis:
查看指定命令的可执行程序文件在哪里

3.2 exec函数族的区别
1、execl->l代表list: 列表,要求以列表的方式传参
执行自己的可执行程序,要求传入路径

path:要执行的文件的路径与名称
arg:执行该文件所需要的参数
2、execv->v代表vector: 容器(数组),
执行可执行程序或者系统命令,要求是传入一个数组
数组中也要带NULL

path:要执行的文件的路径和名称
argv:执行该文件需要的参数存放的数组
3、execlp->p代表path: 路径
执行系统命令,借助环境变量PATH中的路径

file:需要执行的文件名称
arg:执行该文件需要的参数
环境变量:
使用命令env可查看系统中的环境变量(操作系统自己为其定义的全局变量),其中PATH:保存当前系统下可执行程序的路径
cpp
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:
/bin:/usr/games:/usr/local/games:/snap/bin:
/home/linux/opt/arm-2009q3/bin/:/home/linux/opt/arm-2009q3/bin/
3.3 shell命令的种类
shell命令分为三种:外部命令: cp,rm,mv ----> 可以通过execl来执行
内置命令: cd
别名命令: ll
使用whereis 二进制/库 来查看对应的位置
3.4 返回值
只有在出现错误时,exec() 函数才会返回。其返回值为 -1,并且会设置errno。当执行成功是,代码段会被替换,不会执行下面的代码,故执行错误时直接使用perror打印错误信息即可,不用if判断,能执行到perror这句,一定出错!
3.5 system函数

**作用:**系统库函数 system() 利用 fork() 函数创建一个子进程,该子进程会使用 execl() 函数来执
行指定的 shell 命令。
在程序中执行一条系统指令
**参数:**command:要执行的命令字符串
返回值: 创建进程失败,返回 -1
命令执行完成(成功 / 失败都会返回一个整数):返回值 = 32位整数,结构如下:
cpp
高 8 位 低 8 位
退出状态 终止信号
要想知道命令是否正常结束可以使用宏:
cpp
WIFEXITED(status) // 是否正常退出
WEXITSTATUS(status) // 退出码
现在根据system函数的原理来实现my_system:
cpp
/根据system的原理去实现system
void my_system(char *cmd)
{
char *argv[10] = { NULL };
char buff[512];
int i = 0;
strcpy(buff, cmd);/传参过来的是一个常量字符串,不可以直接使用
/先将字符串分割,进行命令解析
for(argv[i] = strtok(buff," "); argv[i] != NULL; argv[i] = strtok(NULL," ")){
i++;
}
/*
argv[i] = strtok(buff," ");
while(argv[i]){
i++;
argv[i] = strtok(NULL," ");
}*/
/创建子进程去执行
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork()");
exit(1);
}
else if(pid > 0){
wait(NULL);
}
else{
execvp(argv[0], argv);
}
}
int main(int argc, const char *argv[])
{
my_system("ls -l -h");
printf("after system\n");
return 0;
}