unix系统编程day02--Liunx中exec函数族和回收进程函数讲解

exec函数组

作用:在程序运行的过程中,再执行一个完整的程序。

execlp函数

  • 解释:其中l表示list,p表示PATH。
  • 作用:借助PATH环境变量,加载一个进程,新进程会清空原进程的虚拟地址空间的0-3G空间,但是进程id不会变。
  • 函数原型:int execlp(const char * filename, const char * filename, const char * arg0, const char * arg1, … , NULL)。
  • 返回值:成功无返回,失败返回-1。
  • 函数解释:后面的参数为命令行参数,第一个参数arg0一般没有用,后面才是对应的真正的参数,并且由于是命令行参数,所以最后一定要有一个NULL参数作为哨兵。

通过运行一个程序来实现子进程运行ls命令:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
extern char ** environ;

int main(void) {
    pid_t pid = 0;
    pid = fork();
    if(pid < 0) {
        perror("fork error");
        exit(1);
    } else if(pid == 0) {
        execlp("ls", "ls", "-al", NULL);
    } else {
        sleep(1);
        printf("I'm father");
    }
    return 0;
}

注:一定要注意execlp的参数,后面的arg0一般为无用参数,一定要加NULL作为哨兵结尾!

execl函数

  • 作用:加载一个进程,通过路径+程序名,无PATH环境变量参与
  • 函数原型:int execl(const char * filename, const char * filename, const char * arg0, const char * arg1, … , NULL); 可见名字中没有了p所以没有环境变量,所以filename要写绝对路径。
  • 返回值:成功无返回,失败返回-1

使用execl调用ls:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
extern char ** environ;

int main(void) {
    pid_t pid = 0;
    pid = fork();
    if(pid < 0) {
        perror("fork error");
        exit(1);
    } else if(pid == 0) {
        execl("/bin/ls", "ls", "-al", NULL);
    } else {
        sleep(1);
        printf("I'm father");
    }
    return 0;
}

注:既然是程序的绝对路径,所以我们可以运行我们自定义的程序,还是要注意参数,最后加NULL!!!

execle函数

  • 解释:e表示environ的意思,即环境变量
  • 函数原型:int execle(const char * path, const char * arg0, const char * arg1, … , NULL, char * const envp[]);

execv函数

  • 解释:v表示argv的意思,顾名思义,后边的那一堆参数可以用一个结构体来代替了,所执行的程序依然用绝对路径表示。
  • 函数原型:int execv(const char * path, char * const argv[]);
    注:argv数组最后一个元素为NULL;
    使用execv函数执行ls的程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
extern char ** environ;

int main(void) {
    pid_t pid = 0;
    pid = fork();
    if(pid < 0) {
        perror("fork error");
        exit(1);
    } else if(pid == 0) {
        char *argv[] = {"ls", "-al", NULL};
        execv("/bin/ls", argv);
    } else {
        sleep(1);
        printf("I'm father");
    }
    return 0;
}

注:参数最后一定为NULL

execvp函数

  • 解释:使用环境变量(p)并且把参数用数组表示
  • 函数原型:int execvp(const char * file, char * const argv[]);

execve函数

  • 解释:使用数组参数,使用environ环境变量
  • 函数原型:int execve(const char * path, char * const argv[], char * const envp[]);
    总结:l表示参数要list出来,v表示参数要数组起来,e表示要引入环境变量,p表示用到PATH环境变量,然后组合一下总共有6个函数,两个用不到环境变量的,然后两两组合的四个,都是exec开头。所有的exec族函数成功无返回,失败返回-1。

复习使用dup2重定向输出,把输出重定向到out文件

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
extern char ** environ;

int main(void) {
    int fd = open("out", O_CREAT | O_RDWR, 0777);
    dup2(fd, 1);
    pid_t pid = 0;
    pid = fork();
    if(pid < 0) {
        perror("fork error");
        exit(1);
    } else if(pid == 0) {
        char *argv[] = {"ls", "-al", NULL};
        execv("/bin/ls", argv);
    } else {
        sleep(1);
        printf("I'm father");
    }
    return 0;
}

简单的记忆:dup2(a, b)可以简单的记为把描述符a移到描述符b的位置(看个人理解)。

回收子进程

孤儿进程

  • 孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿过程,父亲结束后,儿进程的getppid()为1,通过查看1表示的是init进程。init进程又称为init进程孤儿院。

僵尸进程

  • 僵尸进程:进程终止,父进程尚未回收,子进程的0-4G虚拟地址全部释放,但是子进程残留资源(PCB)一直存在在内核中,变成僵尸进程,残留的目的就是让父进程回收,想要父进程获得死亡状态。
    制作僵尸进程代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main(void) {
    pid_t pid = 0;
    pid = fork();
    if(pid < 0) {
        perror("fork error");
        exit(1);
    } else if(pid == 0) {
        printf("I'm child, I Will sleep 10s\n");
        sleep(10);
        printf("-----------child die----------\n");
    } else {
        while(1) {
            sleep(1);
            printf("I'm father pid = %d, my son = %d\n", getpid(), pid);
        }
    }
    return 0;
}

我们发现:出现了一个僵尸进程
f21d12e6dad0df8933cb6416cf5dba01.png

回收子进程函数

一个进程在终止的时候会关闭所有的文件描述符,释放用户空间分配的内存等资源,但是他的PCB还保留着,内核存了一些信息:如果正常终止,则保存着退出状态,如果异常终止则保存导致异常终止的信号是哪个,这个进程的父进程可以调用wait或者waitpid获取这些信息,然后彻底清楚掉这个进程,我们知道一个进程的退出状态可以在shell中用$?查看,因为shell是他的父进程,当他终止时shell可以调用wait或者waitpid来获取他的退出状态同时彻底清楚掉这个进程。

wait函数

  • 函数原型:pid_t wait(int * staus)
  • 参数:staus为传出参数,表示进程结束的状态是正常退出还是异常退出异常的时候保存的是信号
  • 返回值:成功返回被回收的进程号,否则返回-1
  • wait会阻塞函数知道子进程被回收之后程序才会继续运行下去
  • 作用:
    1. 阻塞等待子进程退出
    2. 回收子进程的PCB资源
    3. 获取子进程的状态
      通过wait回收一个子进程的程序(暂时不获取退出状态):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main(void) {
    pid_t pid = 0, wpid = 0;
    pid = fork();
    if(pid < 0) {
        perror("fork error");
        exit(1);
    } else if(pid == 0) {
        printf("I'm child, I Will sleep 10s\n");
        sleep(10);
        printf("-----------child die----------\n");
    } else {
        wpid = wait(NULL);
        if(wpid < 0) {
            perror("wait error");
            exit(1);
        }
        while(1) {
            sleep(1);
            printf("I'm father pid = %d, my son = %d\n", getpid(), pid);
        }
    }
    return 0;
}

  • 获取子进程的退出状态,根据传出参数status
    需要掌握四个宏函数来确定具体的退出原因,我们知道在Linux中所有的异常退出都是由于信号引起。
宏函数
  1. WIFEXITED(status):如果返回为非零则表示子进程正常退出。可以记(WAIT IF EXITED)。
  2. WEXITSTATUS(status):如果1位真则执行该宏来确定返回值。可以记忆(WAIT EXIT STATUS)即wait退出状态。
  3. WIFSIGNALED(status):如果返回值为非零,那么表示异常退出,需要调用4来确定是哪个信号导致异常退出。
  4. WTERMSIG(status):如果3为真,则执行这个来查看具体是哪个信号导致程序异常退出。可以记忆 WAIT TREM SIGNAL wait的退出信号是多少
    下面我们通过wait回收子进程并确定正常退出的返回值是多少:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main(void) {
    pid_t pid = 0, wpid = 0;
    int status;
    pid = fork();
    if(pid < 0) {
        perror("fork error");
        exit(1);
    } else if(pid == 0) {
        printf("I'm child, I Will sleep 10s\n");
        sleep(3);
        printf("-----------child die----------\n");
        return 19;
    } else {
        wpid = wait(&status);
        if(wpid < 0) {
            perror("wait error");
            exit(1);
        }
        if(WIFEXITED(status)) {
            printf("return value = %d\n", WEXITSTATUS(status));
        }
        if(WIFSIGNALED(status)) {
            printf("killed by signal %d\n", WTERMSIG(status));
        }
    }
    return 0;
}

执行结果
在这里插入图片描述

然后我们使用信号终止一个程序来看一下结果,代码就是把上面的代码子进程睡眠改为60秒,方便我们使用kill来终止它。
结果为:
在这里插入图片描述
可以看到被15号信号终止,查看所有的信号可以用 kill -l在终端来查看

waitpid函数

  • 函数原型:pid_t waitpidwaitpid(pid_t pid, int *stat_loc, int options);
  • 其中第三个参数options为0的时候为阻塞方式,WNOHANG为非阻塞方式
  • 作用:用来回收指定进程。
  • 第一个参数如果>0则回收指定的进程,如果为-1表示回收任意子进程,如果为0则回收和当前调用waitpid函数的进程同组的任意子进程,<-1回收指定进程组内的任意子进程
  • 返回值:如果参3为0则成功返回回收的pid否则返回-1,如果为WNOHANG如果回收成功返回对应的pid,否则返回0

总结

exec函数族:

  • execlp --p表示PATH环境变量,运行指定的程序,需要PATH环境变量的支持
    l: list
  • execl 运行自己的程序,需要指定绝对路径
  • execv:
    v:表示argv,意思是把参数放在一个char * argv[]里面
  • execve
  • execle
    回收子进程:
    wait(status)函数:
    作用:
      1. 阻塞回收子进程
      2. 回收子进程的PCB资源
      3. 获取子进程的退出状态
    1)WIFEXITED()True
        WEXITSTATUS()获取退出状态
    2)WIFSIGNALED()True
        WTERMSIG()获取让程序异常退出的信号
    waitpid:
  • 参1:
    pid>0:回收指定的pid函数
    pid=-1:回收任意子进程
    pid=0:回收本组的任意子进程
    pid<-1:回收指定组的任意子进程
  • 参2:staus同wait函数
  • 参3:options=0,阻塞方式回收,成功返回pid,失败返回-1
    options=WNOHANG,非阻塞方式回收,成功返回pid,失败返回0。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值