目录
exec函数族的作用是根据指定的文件名或目录找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。
通常的做法是使用fork函数创建一个子进程,然后在子进程中执行exec函数。
exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段、数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息保持原样。调用失败,会返回-1,从原程序的调用点接着往下执行。
如下函数就是exec函数族包含的函数,前6个函数是标准C库的函数,最后一个函数是Linux/unix的函数
- int execl(const char *path, const char *arg,.../* (char*)NULL*/);
- int execlp(const char *file, const char *arg,.../* (char*)NULL*/);
- int execle(const char *path, const char *arg,.../* (char*)NULL, char *const envp[]*/);
- int execv(const char *path, char *const argv[]);
- int execvp(const char *file, char *const argv[]);
- int execvpe(const char *file, char *const argv[], char *const envp[]);
- int execve(const char *path, char *const argv[], char *const envp[]);
函数名中的l/v/p/e的含义:
以上函数都在头文件 #include
参数说明:
参数path表示可执行文件的的完整路径;
参数file表示可执行文件的文件名,如果file中包含/,将其视为路径名,否则按照PATH环境变量查找指定的可执行文件;
execl()/execlp()execle()函数参数中的 arg,...表示执行可执行文件所需要的参数列表,第一个参数为了方便,一般设置为可执行文件的名字,从第二个参数开始,是程序执行需要的参数列表,参数最后以空指针NULL结尾。
execv()/execvp()/execvpe()/execve()函数中的 argv 表示执行可执行文件需要的参数列表的数组,以空指针NULL结尾,如 char *argv[] = {"test", "ps","aux", NULL};
execle()/execvpe()/execve()函数中的参数 envp 是自己指定的环境变量的参数列表,以空指针NULL结尾,如: char *envp[] = {"/home/Linux", "/home/Linux/lesson19", NULL};
函数返回值:只有当调用失败时,才会有返回值,返回-1,并设置errno;如果调用成功,没有返回值。
更详细信息:linux进程---exec族函数(execl, execlp, execle, execv, execvp, execvpe)_云英的博客-CSDN博客_execle
execl()使用示例,当前路径下有可执行文件hello,功能:打印hello world。
- #include
- #include
- #include
-
- int main()
- {
-
- // 创建一个子进程,在子进程中执行exec函数族中的函数
- pid_t pid = fork();
-
- if (pid > 0)
- {
- // 父进程
- printf("parent process, pid : %d\n", getpid());
- // sleep(1);
- }
- else if (pid == 0)
- {
- // 子进程,执行execl()函数
- const char *path = "hello";
- execl(path, path,"ps", "aux",NULL); // 也可以执行shell命令
- perror("execl");
- // 如果execl执行成功,下面这一行不会执行
- printf("chils process, pid : %d\n", getpid());
- }
-
- for (int i = 0; i < 3; i++)
- {
- printf("pid = %d, i = %d\n", getpid(), i);
- }
-
- return 0;
- }
运行显示结果,可以看到正常运行的情况下,"chils process, pid : %d\n\n" 没有被打印,因为在子进程中可执行文件hello取代了原来的子进程的内容。

execlp()函数使用示例:
- #include
- #include
- #include
-
- int main()
- {
-
- // 创建一个子进程,在子进程中执行exec函数族中的函数
- pid_t pid = fork();
-
- if (pid > 0)
- {
- // 父进程
- printf("parent process, pid : %d\n", getpid());
- }
- else if (pid == 0)
- {
- // 子进程,执行execlp()函数,执行命令 ls -l
- const char *path = "ls";
- execlp("ls", "ls","-l", NULL); // 执行shell命令
-
- //execlp("/home/zoya/Linux/chapter2/lesson6/hello", "hello", NULL); // 执行shell命令
-
- // 如果execl执行成功,下面这一行不会执行
- printf("chils process, pid : %d\n\n", getpid());
- }
-
- return 0;
- }
显示结果, 执行了 ls -l,命令

如果代码改成如下,那么会执行hello
- //execlp("ls", "ls","-l", NULL); // 执行shell命令
- execlp("/home/zoya/Linux/chapter2/lesson6/hello", "hello", NULL); // 执行shell命令

execve()函数使用示例:
- #include
- #include
- #include
-
- int main()
- {
- pid_t pid = fork();
-
- if (pid > 0)
- {
- printf("parent process, pid : %d\n", getpid());
- }
- else if (pid == 0)
- {
- char *argv[] = {"hello", NULL};
- char *envp[] = {"/home/zoya/Linux/chapter2/lesson6", NULL};
- if(-1 ==execve(argv[0],argv,envp))
- {
- perror("execve");
- }
-
- printf("chils process, pid : %d\n", getpid());
-
- }
- else{
- printf("create process failed!\n");
- }
-
- for(int i=0;i<5;i++)
- {
- printf("i=%d, pid : %d\n",i,getpid());
- }
-
- return 0;
- }
执行结果,execve会在参数envp指定的路径中查找指定的可执行文件

控制进程退出函数有:
- #include
- void exit(int status);
-
- #include
- void _exit(int status);
参数status表示退出的状态信息,父进程回收子进程资源时可以获取该信息;
exit()和_exit()的区别:
父进程运行结束,但是子进程还在运行(未运行结束),这样的子进程称为孤儿进程(Orphan Process)。
当出现一个孤儿进程时,内核就会把该孤儿进程的父进程设置为init(pid=1的进程),而init进程会循环等待它的已经退出的子进程。当孤儿进程结束其生命周期时,init进程会处理善后工作。
孤儿进程没有什么危害。
示例:
- #include
- #include
- #include
-
- int main()
- {
- pid_t pid = fork();
-
- if(pid > 0)
- {
- printf("parent process,pid : %d, ppid : %d\n",getpid(),getppid());
- }
- else if(pid == 0)
- {
- sleep(1);
- printf("child process, pid : %d, ppid : %d\n",getpid(),getppid());
- }
- else{
- printf("create process failed!\n");
- }
-
- for (int i=0;i<3;i++)
- {
- printf("i=%d, pid : %d\n",i,getpid());
- }
-
- return 0;
- }
显示结果,当父进程结束后,子进程会被托管给init进程(pid=1) ,init进程会对子进程的资源进行回收。

每个子进程结束之后,都会释放自己地址空间中的用户区数据,但是内核区的PCB没有办法自己释放掉,需要父进程释放。
若子进程终止时,父进程尚未回收,子进程残留资源存放于内核中,变成僵尸进程(Zombie)。
僵尸进程不能被 kill -9杀死。会导致一个问题:如果父进程不调用wait()或waitpid()的话,子进程保留的信息就不会被释放,其进程号一直被占用,但是系统所能使用的进程号是有限的,如果系统中有大量的僵尸进程,将因为没有可用的进程号导致系统不能产生新的进程。所以僵尸进程是有危害的,应当避免产生僵尸进程。
避免僵尸进程:父进程有义务释放子进程的资源,可以使用wait/waitpid函数对内核区释放。
每个进程退出时,内核释放该进程的所有资源、包括打开的文件、占用的内存等。但仍然为其保留一定的信息,这些信息主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)。
父进程可以通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
wait和waitpid函数功能一样,区别在于wait函数会阻塞,waitpid函数默认是阻塞,但是可以设置不阻塞,waitpid函数还可以指定等待哪个子进程结束。
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应该使用循环。
wait()/waitpid()函数声明:
- pid_t wait(int *wstatus);
- pid_t waitpid(pid_t pid, int *wstatus, int options);
这两个函数都可以释放与子进程相关的资源;
参数说明:
| pid指 | 说明 |
| >0 | 表示回收进程id为pid的子进程 |
| =0 | 表示回收当前进程组的任意子进程,比较常用 |
| =-1 | 表示回收任意子进程,包括其它组的子进程,相当于wait(),最常用 |
| <-1 | 表示回收进程组的组ID=|pid|的子进程 |
返回值:
- WIFEXITED(status) 非0 ,表示进程正常退出
- WEXITSTATUS(status) 如果宏为真,获取进程退出的状态
-
- WIFSIGNALED(status) 非0,进程异常终止
- WTERMSIG(status) 如果宏为真,获取使进程终止的信号编号
-
- WIFSTOPPED(status) 非0,表示进程处于暂停状态
- WSTOPSIG(status) 如果宏为真,获取使进程暂停的信号的编号
-
- WIFCONTINUED(status) 非0,进程暂停后已继续运行
使用示例:
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
- // 父进程创建5个子进程
- pid_t pid;
-
- // 创建5个子进程
- for(int i=0;i<5; i++){
- pid = fork();
- if(pid == 0) // 子进程
- {
- break;
- }
- }
-
- if(pid > 0){
- // 父进程
- while (1)
- {
- printf("parent process, pid : %d, ppid : %d\n",getpid(),getppid());
- sleep(1);
-
- // 调用wait()/waitpid()进行子进程资源回收
- int st;
- //int ret = wait(&st); // wait():父进程在这里被阻塞
-
- pid_t id=-1; // -1表示回收任意的子进程,包括其它组的子进程
- int options=0; // 0表示阻塞
- options = WNOHANG; // WNOHANG表示非阻塞
- int ret = waitpid(-1,&st,options); // 父进程调用waitpid()等待子进程退出,并回收子进程资源
-
- if(ret == -1) // 函数调用失败,或者没有子进程了
- {
- break;
- }
- else if(ret == 0) // 说明还有子进程存在
- {
- continue;
- }else if(ret > 0) // 有子进程被回收,ret表示被回收的子进程的ID
- {
- // 输出 子进程退出状态信息
- if(WIFEXITED(st)){ // WIFEXITED() 宏 非0表示进程正常退出
- printf("退出的状态码:%d\n",WEXITSTATUS(st)); // WEXITSTATUS() 宏 获取进程退出状态,就是进程中使用exit()的参数值
- }
- if(WIFSIGNALED(st)){ // WIFSIGNALED() 宏 非0 表示进程异常终止
- printf("被哪个信号干掉: %d\n",WTERMSIG(st)); // WTERMSIG() 宏 获取使进程异常终止的信号编号
- }
-
- printf("child end, pid=%d\n",ret); // 打印终止的子进程pid
- }
-
- sleep(1);
- }
-
- }
- else if(pid == 0){
- // 子进程
- //while(1)
- {
- printf("child process, pid : %d, ppid : %d\n",getpid(),getppid());
- sleep(1);
- }
-
- exit(0); // 0表示正常退出
-
- }
- else{
- perror("fork");
- }
-
- return 0;
- }
结果:正常退出,打印退出的状态码。
