- exec函数族
fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳。
其实有六种以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[]);
值得注意的是,exec函数一旦调用成功即执行新的程序,不返回。只有失败才返回,错误值-1。所以通常我们直接在exec函数调用后直接调用perror()和exit(),无需if判断。
l (list) 命令行参数列表
p (path) 搜素file时使用path变量
v (vector) 使用命令行参数数组
e (environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量
execlp函数(l是list的缩写,p是PATH的缩写)
加载一个进程,借助PATH环境变量
int execlp(const char *file, const char *arg, ...); 成功:无返回;失败:-1
参数1:要加载的程序的名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回。
该函数通常用来调用系统程序。如:ls、date、cp、cat等命令。
execl函数(l是list的缩写)
加载一个进程,通过路径+程序名来加载。
int execl(const char *path, const char *arg, ...); 成功:无返回;失败:-1
对比execlp,如加载"ls"命令带有-l,-F参数
execlp("ls", "ls", "-l", "-F", NULL); 使用程序名在PATH中搜索。
execl("/bin/ls", "ls", "-l", "-F", NULL); 使用参数1给出的绝对路径搜索。
- 回收子进程
孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
特别注意,僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止。
1.wait函数
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
①阻塞等待子进程退出(父进程进行回收,执行这个函数后,如果子进程没有死,会在这里一直等待子进程结束然后回收资源。)
②回收子进程残留资源
③获取子进程结束状态(退出原因)。
pid_t wait(int *status); 成功:清理掉的子进程ID;失败:-1 (没有子进程)
当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)。
可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组(后三个了解,前四个要掌握):
1. WIFEXITED(status) 为非0 →进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏→获取进程退出状态 (exit的参数)
2. WIFSIGNALED(status) 为非0 →进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏→取得使进程终止的那个信号的编号。
3. WIFSTOPPED(status) 为非0 →进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏→取得使进程暂停的那个信号的编号。
WIFCONTINUED(status) 为真→进程暂停后已经继续运行
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid, wpid;
int status;
pid = fork();
if(pid == -1){
perror("fork error");
exit(1);
} else if(pid == 0){ //son
printf("I'm process child, pid = %d\n", getpid());
#if 1
execl("./abnor", "abnor", NULL);
perror("execl error");
exit(1);
#endif
sleep(1);
exit(10);
} else {
//wpid = wait(NULL); //传出参数
wpid = wait(&status); //传出参数
if(WIFEXITED(status)){ //正常退出
printf("I'm parent, The child process "
"%d exit normally\n", wpid);
printf("return value:%d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) { //异常退出
printf("The child process exit abnormally, "
"killed by signal %d\n", WTERMSIG(status));
//获取信号编号
} else {
printf("other...\n");
}
}
return 0;
}
2.waitpid函数
注意:一次wait调用只能回收一个子进程,如果有五个子进程需要回收的时候,调用一次wait只能回收最早死亡的子进程。
pid_t waitpid(pid_t pid, int *status, int options); 成功:返回清理掉的子进程ID;失败:-1(无子进程)
特殊参数和返回情况:
参数pid(重点掌握前面的两个):
> 0 回收指定ID的子进程
-1 回收任意子进程(相当于wait)
0 回收和当前调用waitpid一个组的所有子进程
< -1 回收指定进程组内的任意子进程
返回0:参3为WNOHANG(把waitpid设置成非阻塞,当传0值的时候就是阻塞了,和wait一样),且子进程正在运行。
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。