学习视频链接
黑马程序员-Linux系统编程_哔哩哔哩_bilibili/video/BV1KE411q7ee?p=95&spm_id_from=333.1007.top_right_bar_window_history.content.click
目录
一、exec函数族
1.1 函数族作用
1.2 函数族包括
1.3execlp 和 execl
1.4 项目
1.5execvp 函数
1.6exec 函数族一般规律
二、回收子进程
2.1 孤儿进程
2.2 僵尸进程
2.3 wait函数
2.4waitpid函数
一、exec函数族
1.1 函数族作用
1、简介
fork 创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种 exec 函数以执行另一个程序。当进程调用一种 exec 函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新进程,所以调用 exec 前后该进程的 id 并未改变。
将当前进程的 .text、.data 替换为所要加载的程序的 .text、.data,然后让进程从新的 .text 第一条指令开始执行,但进程ID不变,换核不换壳。
2、以前的做法
在一个程序里面写子进程和父进程执行什么操作
3、现在的做法
让子进程去执行另外一个程序
1.2 函数族包括
六种以 exec 开头的函数,统称 exec 函数
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[]);
查看函数使用 man 3 exec(说明是库函数)
1.3execlp 和 execl
1、execlp
作用:加载一个进程,借助 PATH 环境变量
int execlp(const char *file, const char *arg, ... );
成功:无返回; 失败:-14
参数1:要加载的程序的名字。该函数需要配合 PATH 环境变量来使用,当 PATH 中所有目录搜索后没有参数 1 则出错返回。
该函数通常用来调用系统程序。如:Is、date、cp、cat 等命令。
2、execl
作用:加载一个进程,通过路径 + 程序名来加载。
int execl(const char *file, const char *arg, ... );
成功:无返回; 失败:-1
对比 execlp,如加载 "Is" 命令带有 -l,-F 参数
execlp("ls", "Is", "-I", "-F", NULL); 使用程序名在 PATH 中搜索
execl("/bin/s", "Is", "-I", "-F", NULL); 使用参数 1 给出的绝对路径搜索
3、代码
NULL,代表参数结束
第一个 ls 代表可执行文件名
第二个 ls 代表 arg 的第一个参数,即 argv[0] 是程序本身(我们平时 C 语言传入的参数是从 argv[1] 开始的)
execlp 不出错,后面的 perror 和 exit 是没有机会执行的
3、让子进程执行其他的编译好的 C语言程序
1.4 项目
1、描述:
把进程信息存到一个文件里面
2、linux 中命令行的做法 ps aux > out
3、代码
1.5execvp 函数
1、作用
加载一个进程,使用自定义环境变量 env
2、int execvp(const char *file, const char *argv[]);
变参形式:① ... ② argv[] (main 函数也是变参函数,形式上等同于 int main(int argc, char *argv0, ...))
变参终止条件:① NULL 结尾 ② 固参指定
execvp 与 execlp 参数形式不同,原理一致
3、代码
1.6exec 函数族一般规律
1、exec 函数一旦调用成功即执行新的程序,不返回。只有失败才返回,错误值 -1。所以通常我们直接在 exec 函数调用后直接调用 perror() 和 exit(),无需 if 判断。
2、事实上,只有 execve 是真正的系统调用,其它五个函数最终都调用 execve,所以 execve 在 man 手册第 2 节,其它函数在 man 手册第 3 节。这些函数之间的关系如下图所示
二、回收子进程
2.1 孤儿进程
1、孤儿进程:
父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为 init 进程,称为 init 进程领养孤儿进程。
2、查看父进程 id
ppid:父进程 id
pid:进程id
pgid:进程组id
sid:会话 id(后面在守护进程会说这个)
3、程序案例
这里我的电脑上显示的是这个
老师电脑上面显示的是这个
所有失去父进程的子进程都被 init 进程收养,目的是为了回收子进程退出的状态
4、程序运行问题解决
如果没有设定上面函数中只循环10次,而是 while(1),就会出现以下情况
使用 ctrl c结束不掉进程,因为 ctrlc 是发送给 shell,而进程是在后台运行,我们这个时候使用 kill 杀死进程
2.2 僵尸进程
1、僵尸进程:
进程终止,父进程尚未回收,子进程残留资源 (PCB) 存放于内核中,变成僵尸 (Zombie) 进程。
任何一个进程运行结束都会进入这个状态,等待父进程回收(PCB 中会标志进程死亡的原因)
特别注意,僵尸进程是不能使用 kill 命令清除掉的。因为 kill 命令只是用来终止进程的,而僵尸进程已经终止。
思考:用什么办法可清除掉僵尸进程呢?
2、测试僵尸进程
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/wait.h>int main(void){pid_t pid;pid = fork();if (pid == 0) {printf("1. child process, my parent = %d, going to sleep 10s\n", getppid());sleep(10);printf("2. child process die");}else if (pid > 0) {while (1) {printf("3. parent process, my pid = %d, my son = %d\n", getpid(), pid);sleep(1);}}else {perror("fork");return 1;}return 0;}
编译执行该程序:
再开一个窗口查看进程状况
子进程死亡,而父进程还在执行
使用 kill -9 3465,杀死子进程,再次查看一遍
是无效的,僵尸进程仍然存在。此时只能杀死父进程,子进程给孤儿院,孤儿院发现他是僵尸进程,就会回收进程控制块 PCB
2.3 wait函数
1、一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的 PCB 还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用 wait 或 waitpid 获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在 Shell 中用特殊变量 $? 查看,因为 Shell 是它的父进程,当它终止时 Shell 调用 wait 或 waitpid 得到它的退出状态同时彻底清除掉这个进程。
2、父进程调用 wait 函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出。
② 回收子进程残留资源。
③ 获取子进程结束状态(退出原因)
3、pid_t wait(int *status);
status 是传出参数,回收的子函数结束以后的返回值会保存到 status 中
成功:返回值是被清理掉的子进程 ID
失败:返回 -1 (没有子进程)
查看函数:man 2 wait(库函数)
4、代码
(1) 测试的子进程 id
(2) 增加查看子进程死亡状态的代码
包括:判断进程是否正常退出、正常退出查看退出返回值、进程是否非正常退出、查看非正常退出原因(kill -9 进程编号,这种写法是使用 9 号信号杀死进程)
下面是对应的宏函数
判断进程是否正常退出、正常退出查看退出返回值
进程是否非正常退出、查看非正常退出原因
我这里执行不了老师演示的代码:
查看进程终止的信号代表什么:
11 代表段错误终止信号
5、总结
如果使用 wait(NULL) 表示不关心进程状态,直接回收
2.4waitpid函数
1、作用
作用同 wait,但可指定 pid 进程清理,可以不阻塞。
2、pid_t waitpid(pid_t pid, int *status, in options);
(1) 返回值
> 0 回收的子进程的 ID
0 函数调用时,参数3指定了 WNOHANG,并且没有子进程结束
-1 失败 error
(2) pid
>0 回收指定ID的子进程
-1 回收任意子进程(相当于wait)
0 回收和当前调用 waitpid,一个组的所有子进程(默认情况下,子进程和父进程在同一个组里面,可以通过系统调用把进程从这个组里面分离出去)
< -1 回收指定进程组内的任意子进程
3、注意:
一次 wait 或 waitpid 调用只能清理一个子进程,清理多个子进程应使用循环
4、代码
(1)回收指定进程组内的任意子进程
(2) 回收指定 pid 进程
测试阻塞回收
(3) 回收多个子进程