Linux系统编程-进程回收

目录

[一. 进程回收](#一. 进程回收)

[二. 进程回收相关函数](#二. 进程回收相关函数)

[2.1 wait()](#2.1 wait())

[2.2 waitpid()](#2.2 waitpid())

[三. exec函数族](#三. exec函数族)

[3.1 exec函数族实现的原理](#3.1 exec函数族实现的原理)

命令whereis:

[3.2 exec函数族的区别](#3.2 exec函数族的区别)

环境变量:

[3.3 shell命令的种类](#3.3 shell命令的种类)

[3.4 返回值](#3.4 返回值)

[3.5 system函数](#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,在子进程中的退出值最好不要超过此范围

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

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

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

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

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

程序:

cpp 复制代码
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回收子进程时,应该使用轮询的方法

代码:

cpp 复制代码
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:保存当前系统下可执行程序的路径

cpp 复制代码
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位整数,结构如下:

cpp 复制代码
高 8 位    低 8 位
退出状态   终止信号

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

cpp 复制代码
WIFEXITED(status)    // 是否正常退出
WEXITSTATUS(status)  // 退出码

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

cpp 复制代码
/根据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;
}
相关推荐
妖怪喜欢风2 小时前
connecthomeip/matter 专栏文章汇总
linux·matter
wefg12 小时前
【Linux】网络高级 IO
linux·运维·服务器
kebidaixu2 小时前
VSCode 安装和使用 Claude Code 完整指南
linux
朗晴2 小时前
Linux开机重置密码时做了什么?
linux·运维·服务器
烟雨江南aabb2 小时前
Docker第四弹:Dockerfile
linux·运维·docker
坤昱2 小时前
cfs调度类深入解刨——EAS科普篇
linux·cfs·linux内核调度·cfs调度类深入解刨·cfs调度类·eas·cfs调度器eas特性
itinymeng2 小时前
在Alibaba Cloud Linux 4 LTS 64位 中安装htop
linux·运维·服务器
白藏y3 小时前
【Linux】基础 IO(一)—— 文件操作及文件系统
linux