目录
1. exec函数族介绍
2. 进程控制
2.1 进程退出
2.2 孤儿进程
2.3 僵尸进程
2.4 进程回收
2.5 退出信息相关宏函数
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;
}
结果:正常退出,打印退出的状态码。