exec函数族
使用 fork()
函数创建的子进程,其中包含的程序代码完全相同,只能根据 fork() 函数的返回值,执行不同的代码分支。
exec
函数族的功能是:根据指定的文件名或路径找到可执行文件,用该文件取代调用该函数的进程中的程序,再用该文件的 main()
函数开始执行文件的内容。
调用 exec 函数族时不创建新进程,因此进程的 pid 不会改变。exec 只是用新程序中的数据替换了进程中的代码段、数据段以及堆和栈中的数据。
exec 调用成功时没有返回值。
调用 fork()
和 exec
函数族后,进程与其中的数据变化情况如下:
exec 函数族中包含6个函数,它们包含在系统库 unistd.h
,分别为:
#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[]);
函数名及参数说明:
- 函数第一个参数为 file 或 path,其区别:
- 当参数是 path,传入的为路径名;
- 当参数是 file,传入的可执行文件名。若传入的数据中包含
/
,就将其视为路径名;否则系统会根据进程的环境变量 PATH ,在它指定的各个目录中搜素传入的可执行文件。
- 可以将 exec 函数族分为
execl
和execv
两类:- • execl 类:函数将以列举的形式传入参数,参数列表会将执行第一个文件用到的参数逐一列举,由于参数列表的长度不定,所以要用哨兵NULL表示列举结束;
- • execv 类:函数将以参数向量表传递参数,
char * argv[]
的形式传递文件执行时使用的参数,数组中最后一个参数为NULL。
- 如果没有参数
char * const envp[]
,则采用默认环境变量;如果有,则用传入的参数替换默认环境变量
实际上,只有 execve()
是真正的系统调用,其他5个函数最终都调用 execve()
。
案例 6-3(书P127):
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
pid_t pid = fork();
if( pid == -1){//子进程创建失败
perror("fork error");
exit(1);
}
else if(pid > 0){//父进程执行printf
printf("parent porcess: pid = %d\n",getegid());
}
else if(pid == 0){//子进程执行如下
printf("child process: pid = %d\n",getpid());
//1.execl()
//execl("/bin/ls","-a","-l","6-3.c",NULL);
//2.execlp()
//execlp("ls","-a","-l","6-3.c",NULL);
//3.execvp()
char *arg[]={"-a","-l","6-3.c",NULL};
execvp("ls",arg);
perror("error exec\n");
printf("child process : pid = %d\n",getpid());
}
return 0;
}
execl()
、execlp()
、execvp()
调用 ls
命令,执行结果是相同的,下面只贴出一个
exec 函数族在调用成功时不会产生返回值,不过由于各种原因(调用的文件不存在、环境变量错误等),导致调用失败的概率比较大,所以可以在调用 exeec 函数后通过 perror() 函数打印错误信息,获取函数的调用结果。
进程退出
Linux 系统中进程的退出通过 exit()
函数实现。exit()
函数存在于系统库函数 stdilb.h
(之前学C应该也用到了),形式如下:
#include <stdlib.h>
void exit(int status);
参数说明:
- status:表示进程的退出状态,0表示正常退出,非0表示异常退出,一般用-1或1表示;
- 为了可读性,标准C定义了两个宏:EXIT_SUCCESS和EXIT_FAILURE,分别表示正常退出和非正常退出。
Linux系统中有一个与 exit()
函数非常相似的函数:_exit()
_exit()
定义在 unistd.h
中,形式如下:
#include <unistd.h>
void _exit(int status);
两者区别:
_exit()
:系统会无条件停止操作,终止进程并清除进程所用内存空间及进程在内核中的各种数据结构;exit()
:对_exit()
进行了包装,在调用 _exit() 之前先检查文件的打开情况,将缓冲区中的内容写回文件(除了这两个操作外也会干别的,但是这里没学过),相对来说 exit 比 _exit 更为安全。
特殊进程
- 孤儿进程:父进程负责回收子进程,如果父进程在子进程退出之前退出,子进程就会变成孤儿进程,此时 init 进程将代替父进程完成子进程的回收工作;
- 僵尸进程:调用 exit 函数后,该进程不会马上消失,而是留下一个称为僵尸进程的数据结构。它几乎放弃进程退出前占用的所有内存,既没有可执行代码也不能被调度,只是在进程列表中保留一个位置,记载进程的退出状态等信息供父进程回收。若父进程没有回收子进程的代码,子进程将会一直处于僵尸态。