目录

一. 进程回收

二. 进程回收相关函数

2.1 wait()

2.2 waitpid()

三. exec函数族

3.1 exec函数族实现的原理

命令whereis:

3.2 exec函数族的区别

环境变量:

3.3 shell命令的种类

3.4 返回值

3.5 system函数

一. 进程回收

        一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。

二. 进程回收相关函数

2.1 wait()

作用:阻塞等待子进程结束并且将其回收

返回值:成功返回回收的进程的pid        失败返回-1且设置errno

参数wstatus的作用:
当进程终止时,操作系统的隐式回收机制:1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)

不关心子进程的退出状态时,可用传入NULL

可使用wait函数传出参数wstatus来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:

 1.  WIFEXITED(wstatus) 为真    → 进程正常结束

         WEXITSTATUS(wstatus) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)

        注意:wstatus是一个int类型的32位数,而进程的退出状态在wstatus中只占8位,故需要用宏函数来提取,注意退出状态是8位:0~255,在子进程中的退出值最好不要超过此范围

 2. WIFSIGNALED(wstatus) 为真 → 进程异常终止

         WTERMSIG(wstatus) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。

*3. WIFSTOPPED(wstatus) 为真 → 进程处于暂停状态

         WSTOPSIG(wstatus) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。

         WIFCONTINUED(wstatus) 为真 → 进程暂停后已经继续运行
 

程序:

int main()
{
    pid_t pid;
    pid = fork();
    if(pid < 0){
        perror("fork()");
        exit(1);
    }
    else if(pid > 0){
        int wstatus;
        //pid_t wpid = wait(NULL);//不保存子进程的退出状态
        //printf("parent:wait finish,pid is %d\n",wpid);
        pid_t wpid = wait(&wstatus);
        if(WIFEXITED(wstatus)){
            printf("parent:child terminated normally,pid is %d,status is %d\n",
                   wpid,WEXITSTATUS(wstatus));
            //注意这里退出状态是8位:范围0~255,在子进程中注意退出值不可超过此范围
        }
        else if(WIFSIGNALED(wstatus)){
            printf("parent:child kill by sig,pid is %d,sig is %d\n",
                   wpid,WTERMSIG(wstatus));
        }
        while(1){
            printf("parent:pid is %d\n",getpid());
            sleep(1);
        }
    }
    else{
        int num = 5;
        while(num--){
            printf("child:pid is %d\n",getpid());
            sleep(1);
        }
        exit(66);
    }
    return 0;
}

结果:

子进程正常退出结果:

子进程被信号结束结果:

2.2 waitpid()

作用:可用指定回收子进程,也可用设置非阻塞模式

参数:

        pid:

> 0 回收指定pid的子进程
= 0 回收与调用该函数同组的任意进程
= -1 回收任意子进程
< -1 回收取绝对值对应的进程组内的任意进程

        options:可以设置非阻塞回收:WNOHANG

        wstatus:作用同wait函数

返回值:成功返回回收进程的pid号                失败返回-1并且设置errno

                注意:当设置了非阻塞回收并且无子进程结束时,将返回0

注意:使用waitpid回收子进程时,应该使用轮询的方法

代码:

int main()
{
    pid_t pid;
    pid = fork();
    if(pid < 0){
        perror("fork()");
        exit(1);
    }
    else if(pid > 0){
        int wstatus;
        //使用waitpid非阻塞回收时要轮询
        pid_t wpid;
        while(1){
            wpid = waitpid(pid, NULL, WNOHANG);
            if(wpid > 0){
                printf("wait finish, wpid is %d\n",wpid);
            }
            else if(wpid < 0){
                perror("waitpid()");
            }

            printf("parent:pid is %d\n",getpid());
            sleep(1);
        }
    }
    else{
        int num = 5;
        while(num--){
            printf("child:pid is %d\n",getpid());
            sleep(1);
        }
        exit(66);
    }
    return 0;
}

结果:

三. exec函数族

3.1 exec函数族实现的原理

        1、fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换(代码区和数据区),从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的pid并未改变。

        2、将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变

        3、实现原理:系统实际上是完成文本区的二进制代码替换,并不创建新进程

        

3、作用:执行一个新的文件,在一个进程中执行另外一个文件
4、参数:路径+执行程序时需要传递的参数+NULL哨兵位

命令whereis:

        查看指定命令的可执行程序文件在哪里

3.2 exec函数族的区别

    1、execl->l代表list:列表,要求以列表的方式传参
             执行自己的可执行程序,要求传入路径

path:要执行的文件的路径与名称

arg:执行该文件所需要的参数


    2、execv->v代表vector:容器(数组),
            执行可执行程序或者系统命令,要求是传入一个数组

            数组中也要带NULL

path:要执行的文件的路径和名称

argv:执行该文件需要的参数存放的数组


    3、execlp->p代表path:路径
            执行系统命令,借助环境变量PATH中的路径

file:需要执行的文件名称

arg:执行该文件需要的参数

环境变量:


           使用命令env可查看系统中的环境变量(操作系统自己为其定义的全局变量),其中PATH:保存当前系统下可执行程序的路径

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:
/bin:/usr/games:/usr/local/games:/snap/bin:
/home/linux/opt/arm-2009q3/bin/:/home/linux/opt/arm-2009q3/bin/

3.3 shell命令的种类

    shell命令分为三种:外部命令:cp,rm,mv ----> 可以通过execl来执行
                                     内置命令:cd
                                     别名命令:ll
            使用whereis 二进制/库   来查看对应的位置

3.4 返回值

        只有在出现错误时,exec() 函数才会返回。其返回值为 -1,并且会设置errno。当执行成功是,代码段会被替换,不会执行下面的代码,故执行错误时直接使用perror打印错误信息即可,不用if判断,能执行到perror这句,一定出错!

3.5 system函数

作用:系统库函数 system() 利用 fork() 函数创建一个子进程,该子进程会使用 execl() 函数来执

           行指定的 shell 命令。

           在程序中执行一条系统指令

参数:command:要执行的命令字符串

返回值: 创建进程失败,返回 -1

                命令执行完成(成功 / 失败都会返回一个整数):返回值 = 32 位整数,结构如下:

高 8 位    低 8 位
退出状态   终止信号

要想知道命令是否正常结束可以使用宏:

WIFEXITED(status)    // 是否正常退出
WEXITSTATUS(status)  // 退出码

现在根据system函数的原理来实现my_system:

/根据system的原理去实现system
void my_system(char *cmd)
{
    char *argv[10] = { NULL };
    char buff[512];
    int i = 0;
    strcpy(buff, cmd);/传参过来的是一个常量字符串,不可以直接使用
    /先将字符串分割,进行命令解析
    for(argv[i] = strtok(buff," "); argv[i] != NULL; argv[i] = strtok(NULL," ")){
        i++;
    }
    /*
    argv[i] = strtok(buff," ");
    while(argv[i]){
        i++;
        argv[i] = strtok(NULL," ");
    }*/
    /创建子进程去执行
    pid_t pid;
    pid = fork();
    if(pid < 0){
        perror("fork()");
        exit(1);
    }
    else if(pid > 0){
        wait(NULL);
    }
    else{
        execvp(argv[0], argv);
    }
}
int main(int argc, const char *argv[])
{
    my_system("ls -l -h");
    printf("after system\n");
    return 0;
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐