【Linux】exec函数族及进程控制相关

目录

1. exec函数族介绍

 2. 进程控制

2.1 进程退出

2.2 孤儿进程

2.3 僵尸进程

2.4 进程回收

2.5 退出信息相关宏函数


1. exec函数族介绍

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的含义:

  • l(list):表示参数地址列表,以空指针结尾;
  • v(vector):表示存有个参数地址的指针数组的地址;
  • p(path):按PATH环境变量指定的目录搜索可执行文件
  • e(environment):给程序设置新的环境变量;

以上函数都在头文件 #include <unistd.h> 中声明。

参数说明:

参数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 <sys/types.h>
#include <unistd.h>
#include <stdio.h>

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 <sys/types.h>
#include <unistd.h>
#include <stdio.h>

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 <sys/types.h>
#include <unistd.h>
#include <stdio.h>

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指定的路径中查找指定的可执行文件

 2. 进程控制

2.1 进程退出

控制进程退出函数有:

#include <stdlib.h>
void exit(int status);

#include <unistd.h>
void _exit(int status);

参数status表示退出的状态信息,父进程回收子进程资源时可以获取该信息;

exit()和_exit()的区别:

  • exit()在退出前会调用退出处理函数,刷新I/O缓冲,关闭文件描述符等操作,然后调用系统函数_exit()终止进程。

2.2 孤儿进程

父进程运行结束,但是子进程还在运行(未运行结束),这样的子进程称为孤儿进程(Orphan Process)。

当出现一个孤儿进程时,内核就会把该孤儿进程的父进程设置为init(pid=1的进程),而init进程会循环等待它的已经退出的子进程。当孤儿进程结束其生命周期时,init进程会处理善后工作。

孤儿进程没有什么危害。

示例:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

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进程会对子进程的资源进行回收。

2.3 僵尸进程

每个子进程结束之后,都会释放自己地址空间中的用户区数据,但是内核区的PCB没有办法自己释放掉,需要父进程释放。

若子进程终止时,父进程尚未回收,子进程残留资源存放于内核中,变成僵尸进程(Zombie)。

僵尸进程不能被 kill -9杀死。会导致一个问题:如果父进程不调用wait()或waitpid()的话,子进程保留的信息就不会被释放,其进程号一直被占用,但是系统所能使用的进程号是有限的,如果系统中有大量的僵尸进程,将因为没有可用的进程号导致系统不能产生新的进程。所以僵尸进程是有危害的,应当避免产生僵尸进程。

避免僵尸进程:父进程有义务释放子进程的资源,可以使用wait/waitpid函数对内核区释放。

2.4 进程回收

每个进程退出时,内核释放该进程的所有资源、包括打开的文件、占用的内存等。但仍然为其保留一定的信息,这些信息主要指进程控制块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);

这两个函数都可以释放与子进程相关的资源;

参数说明:

  • wstatus:进程退出时的状态信息;
  • pid:
pid指说明
>0表示回收进程id为pid的子进程
=0表示回收当前进程组的任意子进程,比较常用
=-1表示回收任意子进程,包括其它组的子进程,相当于wait(),最常用
<-1表示回收进程组的组ID=|pid|的子进程
  • options:设置阻塞或非阻塞状态,0:表示阻塞状态,WNOHANG设置非阻塞状态,表示没有子进程退出就立即返回。

返回值:

  • >0 表示被回收的子进程的ID;
  • =-1 表示函数调用失败,或者没有子进程了;
  • waitpid()函数返回0 表示是在非阻塞状态,还有子进程没有退出;

2.5 退出信息相关宏函数

WIFEXITED(status)    非0 ,表示进程正常退出
WEXITSTATUS(status)    如果宏为真,获取进程退出的状态

WIFSIGNALED(status)    非0,进程异常终止
WTERMSIG(status)    如果宏为真,获取使进程终止的信号编号

WIFSTOPPED(status)    非0,表示进程处于暂停状态
WSTOPSIG(status)    如果宏为真,获取使进程暂停的信号的编号

WIFCONTINUED(status)    非0,进程暂停后已继续运行

使用示例:

#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

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;
}

结果:正常退出,打印退出的状态码。

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值