【linux】进程等待与进程替换

Alt

🔥个人主页Quitecoder

🔥专栏linux笔记仓

Alt

目录

    • 01.进程等待
      • 系统调用
      • 获取子进程status
      • 常用宏
      • 使用示例
    • 02.进程替换
      • 替换函数
      • 关键点解释:
      • 代码详细分析
      • `execvpe` 函数的使用

01.进程等待

任何子进程,在退出的情况下,一般必须要被父进程进行等待。进程在退出的时候,如果父进程不管不顾,退出进程,状态Z(僵尸状态),内存泄漏

  • 进程一旦变成僵尸状态,kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程
  • 父进程通过等待,解决子进程退出的僵尸问题,回收系统资源(一定要考虑的)
  • 获取子进程的退出信息,知道子进程是因为什么原因退出的(可选的功能)

系统调用

  1. wait()

    • wait() 函数使调用的进程(通常是父进程)暂停执行,直到一个子进程终止或发生一个信号。这个调用通常用于简单的父子进程同步。
    • 函数原型:pid_t wait(int *status);
    • 如果有子进程退出,wait() 返回子进程的 PID,并可通过 status 指针获取子进程的退出状态。
  2. waitpid()

    • waitpid() 函数提供更多的控制,允许父进程等待特定的子进程,或者是与父进程有特定关系的任何子进程。
    • 函数原型:pid_t waitpid(pid_t pid, int *status, int options);
    • 参数:
      • pid:指定要等待的子进程的 PID;若为 -1,则等待任何子进程,与wait等效
      • status:和 wait() 一样,用于存放子进程的终止状态。
      • options:可以控制 waitpid() 的行为,如 WNOHANG(非阻塞),不会等待子进程终止,立即返回。
    • 返回值:
      • 当正常返回的时候waitpid返回收集到的子进程的进程ID;
      • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
      • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回
    在这里插入图片描述
    在这里插入图片描述
    所以说父进程通过等待,解决子进程退出的僵尸问题,回收系统资源

如果子进程没有退出,父进程其实一直在进行阻塞等待!

在这里插入图片描述

获取子进程status

waitpid 函数中,status 是一个指向整数的指针,用于存储子进程的终止状态信息。这个状态不仅仅是一个简单的退出代码,而是一组位的组合,这些位可以表示子进程的多种状态。

下面是如何解释 status 值的相关宏和方法:

常用宏

  1. WIFEXITED(status):

    • 判断子进程是否正常退出(调用 exit 或者返回 main 函数)。
    • 返回非零值表示子进程正常退出,可以通过 WEXITSTATUS(status) 获取退出状态。
  2. WEXITSTATUS(status):

    • WIFEXITED(status) 为真时使用。
    • 获得子进程的退出码(也就是子进程传递给 exit() 的参数或 main() 函数的返回值),这是一个8位的整数。
  3. WIFSIGNALED(status):

    • 判断子进程是否因为未捕获信号而终止。
    • 返回非零值表示子进程被信号终止,可以通过 WTERMSIG(status) 获取导致终止的信号编号。
  4. WTERMSIG(status):

    • WIFSIGNALED(status) 为真时使用。
    • 获得导致子进程终止的信号编号。
  5. WIFSTOPPED(status):

    • 判断子进程是否因信号停止。
    • 返回非零值表示子进程被信号停止,可以通过 WSTOPSIG(status) 获取导致停止的信号编号。
  6. WSTOPSIG(status):

    • WIFSTOPPED(status) 为真时使用。
    • 获得导致子进程停止的信号编号。
  7. WIFCONTINUED(status):

    • 判断子进程是否由 SIGCONT 信号继续。
    • 返回非零值表示子进程接收到 SIGCONT 信号后继续执行,这个宏主要在系统支持 WCONTINUED 选项时使用。

使用示例

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

int main() {
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程
        printf("Child process (PID: %d) executing...\n", getpid());
        exit(42);  // 子进程结束并返回状态码42
    } else {
        // 父进程
        int status;
        pid_t waited = waitpid(pid, &status, 0);
        if (waited == -1) {
            perror("waitpid failed");
        } else {
            if (WIFEXITED(status)) {
                printf("Child process (PID: %d) exited with status %d\n", waited, WEXITSTATUS(status));
            } else if (WIFSIGNALED(status)) {
                printf("Child process (PID: %d) terminated by signal %d\n", waited, WTERMSIG(status));
            } else if (WIFSTOPPED(status)) {
                printf("Child process (PID: %d) stopped by signal %d\n", waited, WSTOPSIG(status));
            } else if (WIFCONTINUED(status)) {
                printf("Child process (PID: %d) continued\n", waited);
            }
        }
    }

    return 0;
}

在上面的代码中,status 变量通过 waitpid 获得子进程的状态。根据不同的状态宏,可以判断子进程是如何退出的,并做相应的处理。这种机制使得父进程能够详细了解子进程的退出原因,而不仅仅是它的退出码。

status不能简单的当作整形来看待,可以当作位图来看待在这里插入图片描述

虽然在不同的 Unix 系统中这个结构可能略有差异,但通常 status 会被设计成如下所示的位字段结构:

  • 位 0-7: 子进程的退出代码(如果子进程是正常退出的)。
  • 位 8-15: 在一些实现中,这些位可以包含信号编号,表示子进程因信号而终止。
  • 特定的位字段:表明子进程是否被信号中止、是否正常退出、是否由信号停止(这些信息是由 WIFEXITEDWIFSIGNALEDWIFSTOPPED 等宏检查)。

在这里插入图片描述

02.进程替换

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

替换函数

其实有六种以exec开头的函数,统称exec函数:

#include <unistd.h>`
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。

命名理解:

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量

在这里插入图片描述

让进程用exec函数,执行起来新的程序

main 函数演示了如何使用 execl 函数进行进程替换。这段代码旨在在 Unix-like 系统上运行,其中 execl 是用来替换当前进程并执行新的程序。这里,新程序是系统的 ls 命令,用来列出当前目录中的所有文件和目录(包括隐藏文件),并以长格式显示。

以下是每一行代码的具体解释:

int main()
{
    printf("testexec...begin\n"); // 打印开始消息

    execl("/usr/bin/ls", "ls", "-l", "-a", NULL); // 替换当前进程,执行 ls 命令

    printf("testexec...end\n"); // 打印结束消息,理论上不应执行到这里

    return 0; // 程序正常结束返回
}

关键点解释:

  1. printf("testexec...begin\n");

    • 这行代码输出 “testexec…begin” 到标准输出,标示程序开始执行。
  2. execl("/usr/bin/ls", "ls", "-l", "-a", NULL);

    • execlexec 系列函数之一,用于替换当前进程的映像为一个新的可执行文件。
    • 第一个参数 "/usr/bin/ls" 指定了要执行的程序的绝对路径
    • 接下来的参数 “ls”, “-l”, “-a” 是传递给 ls 程序的参数,分别代表程序名、长格式列表和显示所有文件(包括以点开头的隐藏文件)。
    • 最后一个参数 NULL 表示参数列表的结束
    • 成功调用 execl 后**,原进程的代码和数据将被 ls 程序替换,原 main 函数之后的代码不会被执行**。
  3. printf("testexec...end\n");

    • 理论上,这行代码永远不会执行,因为一旦 execl 成功,当前进程的地址空间已经被新程序(这里是 ls)所替换
    • 如果出现这行代码被执行的情况,那意味着 execl 调用失败了。失败的原因可能包括指定的程序不存在,或者进程没有执行该程序的权限等。

execl函数的返回值可以不关心了。只要替换成功,就不会向后继续运行只要继续运行了,一定是替换失败了!

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

int main()
{
    printf("testexec...begin\n");
    pid_t id=fork();
    if(id==0)
    {
       execl("/usr/bin/ls","ls","-l","-a",NULL);
       exit(1);
    }
    int status=0;
    pid_t rid=waitpid(id,&status,0);
    if(rid>0)
    {
        printf("father wait success,child exit code:%d\n",WEXITSTATUS(status));
    }
    printf("testexec...end\n");

    return 0;
}

main 函数展示了如何结合 fork()execl() 进行进程创建和替换,还演示了如何使用 waitpid() 来等待子进程结束并获取子进程的退出状态。这是 Unix-like 系统编程的一个典型示例,通常用于需要同时运行多个程序或监控其他程序执行的情况。

代码详细分析

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

int main()
{
    printf("testexec...begin\n"); // 输出程序开始执行的标志
    
    pid_t id = fork(); // 创建一个新进程
    if (id == 0) // 子进程执行分支
    {
       execl("/usr/bin/ls", "ls", "-l", "-a", NULL); // 子进程中执行 ls 命令
       exit(1); // 如果 execl 执行失败,退出子进程,返回状态 1
    }
    // 父进程执行分支
    int status = 0;
    pid_t rid = waitpid(id, &status, 0); // 父进程等待子进程结束
    if (rid > 0) // waitpid 成功
    {
        if (WIFEXITED(status)) { // 判断子进程是否正常退出
            printf("father wait success, child exit code: %d\n", WEXITSTATUS(status)); // 输出子进程的退出状态
        }
    }
    printf("testexec...end\n"); // 输出程序结束的标志

    return 0;
}
  1. 进程创建 (fork())

    • fork() 创建一个新的子进程,子进程是父进程的一个副本。
    • fork() 在父进程中返回子进程的 PID,在子进程中返回 0。
    • 由于操作系统的调度策略,父进程和子进程之后的执行顺序是不确定的。
  2. 进程替换 (execl())

    • 在子进程中,execl() 用于加载并执行指定的程序(这里是 /usr/bin/ls)。
    • 如果 execl() 成功,它不会返回;如果失败,会返回 -1,并且子进程继续执行后续代码。
  3. 退出处理 (exit())

    • 在子进程中,如果 execl() 调用失败,紧接着调用 exit(1) 来结束子进程,并返回状态码 1
  4. 进程同步 (waitpid())

    • 父进程使用 waitpid() 等待子进程结束,并通过 status 变量获取子进程的退出状态。
    • WIFEXITED(status) 检查子进程是否正常结束,WEXITSTATUS(status) 获取子进程的返回码。

错误处理和输出

  • 子进程在 execl() 调用失败时通过 exit(1) 明确指示错误退出。
  • 父进程检查 waitpid() 返回值以确认等待是否成功,并从状态码中提取具体的退出信息,正确处理并报告子进程的退出状态。

这个程序结构清晰,展示了进程的创建、执行替换、等待及状态检查的完整流程,是学习 Unix/Linux 系统编程的一个很好的实例。

一旦子进程进行了替换,也要进行写时拷贝

#include <unistd.h>
int main()
{
 char *const argv[] = {"ps", "-ef", NULL};
 char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
 execl("/bin/ps", "ps", "-ef", NULL);
 // 带p的,可以使用环境变量PATH,无需写全路径
 execlp("ps", "ps", "-ef", NULL);
 // 带e的,需要自己组装环境变量
 execle("ps", "ps", "-ef", NULL, envp);
 execv("/bin/ps", argv);
 
 // 带p的,可以使用环境变量PATH,无需写全路径
 execvp("ps", argv);
 // 带e的,需要自己组装环境变量
 execve("/bin/ps", argv, envp);
 exit(0);
}

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示
在这里插入图片描述
我们现在用c语言的文件来替代c++的程序:
在这里插入图片描述
修改makefile让其一次性生成两个可执行文件
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

检测pid:在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同理,其他类型的程序我们也可以替换

pid_t id=fork();

    if(id==0)
    {
       char *const argv[]={"mypragma",NULL};

       printf("child pid:%d\n",getpid());
       sleep(2);
       execvpe("./mypragma",argv,NULL);
       // execl("./mypragma","mypragma",NULL); 
       // char const* argv[]={(char*)"ls",(char *)"-l",(char*)"-a",NULL};
       // execv("/usr/bin/ls",argv);
       
       exit(1);
    }

execvpe 函数的使用

execvpe 的原型如下:

int execvpe(const char *file, char *const argv[], char *const envp[]);
  • file: 要执行的程序的名称或路径。
  • argv: 指向以 NULL 结尾的字符串数组的指针,这些字符串为要传递给新程序的命令行参数。
  • envp: 指向以 NULL 结尾的字符串数组的指针,这些字符串构成了新程序的环境。

代码中,使用 execvpe 来执行 ./mypragma 程序,并将 argv 设置为 {"mypragma", NULL}。这意味着 mypragma 作为参数0(通常是程序名称)传递给 mypragma 程序。

在这里插入图片描述

在这里插入图片描述
打印结果:

[dyx@VM-8-13-centos process_test]$ ./myprocess 
testexec...begin
child pid:21680
argv[0]:mypragma
-------------------------
env[0]:HAHA=111111
env[1]:HEHE=222222
-------------------------
hello c++,I am a c++ pragma:21680
hello c++,I am a c++ pragma:21680
hello c++,I am a c++ pragma:21680
hello c++,I am a c++ pragma:21680
father wait success,child exit code:0
testexec...end

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/917041.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

认证鉴权框架SpringSecurity-5--权限管理篇

上面两篇我们重点介绍了如何在代码上集成springSecurity&#xff0c;同时完成登录认证和token认证的过程。我们直到springSecurity处理能帮我们完成认证外&#xff0c;还可以帮助我们完成权限校验的工作&#xff0c;这篇我们来重点介绍下springSecurity是如何实现鉴权的。 一、…

RK3588开发板Android12-SDK更新通知

迅为RK3588开发板Android12 SDK升级至RK的android-12.1-mid-rkr14版本 内核版本&#xff1a;升级至 5.10.160 版本&#xff0c;提供更好兼容性和性能。 rkbin 版本&#xff1a;支持最新的 1.17 版本 bin 和 1.46 版本的 bl31。

stm32教程:OLED屏显示字母、汉字、图片工程讲解

早上好啊&#xff0c;大佬们&#xff0c;今天带来的是我们 stm32系列的第一个外设——OLED&#xff0c;相信大家对于OLED都不陌生了吧&#xff0c;这个可以说每一个项目里的必需品了&#xff0c;单片机离不开OLED就像西方离不开耶路撒冷。 在生活中&#xff0c;我们见到的OLED的…

力扣 LeetCode 28. 找出字符串中第一个匹配项的下标(Day4:字符串)

解题思路&#xff1a; KMP算法 需要先求得最长相等前后缀&#xff0c;并记录在next数组中&#xff0c;也就是前缀表&#xff0c;前缀表是用来回退的&#xff0c;它记录了模式串与主串(文本串)不匹配的时候&#xff0c;模式串应该从哪里开始重新匹配。 next[ j - 1 ] 记录了 …

我与Linux的爱恋:进程间通信 匿名管道

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;Linux的学习 文章目录 匿名管道pipe 匿名管道 匿名管道&#xff08;Anonymous Pipes&#xff09;是Unix和类Unix操作系统中的一种通信机制&#xff0c;用于在两个进程之间传递数据。匿名…

Java之JDBC,Maven,MYBatis

前言 就是用来操作数据库的 1.JDBC快速入门 注意在使用前一定要导入jar包 在模块那里新建目录&#xff0c;新建lib&#xff0c;粘贴复制jar包&#xff0c;我这个jar设置的是模块有效 package test1017;import java.sql.Connection; import java.sql.DriverManager; import…

基于Matlab的碎纸片的自动拼接复原技术

碎纸片的自动拼接复原技术 摘要&#xff1a;破碎文件的拼接在司法物证复原、历史文献修复以及军事情报获取等领域都有着重要的应用。目前发现对碎纸片的拼接大部分由人工完成&#xff0c;准确率较高&#xff0c;但耗费大量人力财力及时间&#xff0c;效率很低。随着计算机技术的…

STM32 设计的较为复杂的物联网项目,包括智能家居控制系统,涵盖了硬件和软件的详细设计。

使用 STM32 设计的较为复杂的物联网项目&#xff0c;包括智能家居控制系统&#xff0c;涵盖了硬件和软件的详细设计。 一、硬件设计 微控制器&#xff1a;选择 STM32F4 系列微控制器&#xff0c;如 STM32F407ZGT6&#xff0c;具有高性能和丰富的外设资源。 传感器模块&#x…

1.7 JS性能优化

从输入url到页面加载完成都做了些什么 输入 URL - 资源定位符 http://www.zhaowa.com - http 协议 域名解析 https://www.zhaowa.com > ip 1. 切HOST&#xff1f; > 浏览器缓存映射、系统、路由、运营商、根服务器 2. 实际的静态文件存放&#xff1f; 大流量 > 多个…

LPDDR4芯片学习(四)——DDR Training

一、ZQ Calibration DDR 学习时间 (Part B - 6)&#xff1a;DRAM ZQ 校正 - 知乎 (zhihu.com) 从原理上解释什么是DDR的ZQ校准&#xff1f; - 知乎 (zhihu.com) LPDDR4的训练(training)和校准(calibration)--ZQ校准(Calibration)_wonder_coole-腾讯云开发者社区 01 ZQ校准的…

pycharm分支提交操作

一、Pycharm拉取Git远程仓库代码 1、点击VCS > Get from Version Control 2、输入git的url&#xff0c;选择自己的项目路径 3、点击Clone&#xff0c;就拉取成功了 默认签出分支为main 选择develop签出即可进行开发工作 二、创建分支&#xff08;非必要可以不使用&#xf…

鸿蒙实战:页面跳转

文章目录 1. 实战概述2. 实现步骤2.1 创建项目2.2 准备图片素材2.3 编写首页代码2.4 创建第二个页面 3. 测试效果4. 实战总结 1. 实战概述 实战概述&#xff1a;本实战通过ArkUI框架&#xff0c;在鸿蒙系统上开发了一个简单的两页面应用。首页显示问候语和“下一页”按钮&…

IDEA部署AI代写插件

前言 Hello大家好&#xff0c;当下是AI盛行的时代&#xff0c;好多好多东西在AI大模型的趋势下都变得非常的简单。 比如之前想画一幅风景画得先去采风&#xff0c;然后写实什么的&#xff0c;现在你只需描述出你想要的效果AI就能够根据你的描述在几分钟之内画出一幅你想要的风景…

深入理解 Spark 中的 Shuffle

Spark 的介绍与搭建&#xff1a;从理论到实践_spark环境搭建-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交&#xff1a;本地与集群模式全解析-CSDN博客 Spark on YARN&#xff1a;Spark集群模式…

常用在汽车PKE无钥匙进入系统的高度集成SOC芯片:CSM2433

CSM2433是一款集成2.4GHz频段发射器、125KHz接收器和8位RISC&#xff08;精简指令集&#xff09;MCU的SOC芯片&#xff0c;用在汽车PKE无钥匙进入系统里。 什么是汽车PKE无钥匙进入系统&#xff1f; 无钥匙进入系统具有无钥匙进入并且启动的功能&#xff0c;英文名称是PKE&…

人力资源招聘系统-提升招聘效率与质量的关键工具

在当今这个竞争激烈的商业环境中&#xff0c;企业要想在市场中立于不败之地&#xff0c;关键在于拥有高素质的人才队伍。然而&#xff0c;传统的招聘方式往往效率低下&#xff0c;难以精准匹配企业需求与人才特质&#xff0c;这无疑给企业的发展带来了不小的挑战。 随着科技的飞…

R语言贝叶斯分析:INLA 、MCMC混合模型、生存分析肿瘤临床试验、间歇泉喷发时间数据应用|附数据代码...

全文链接&#xff1a;https://tecdat.cn/?p38273 多模态数据在统计学中并不罕见&#xff0c;常出现在观测数据来自两个或多个潜在群体或总体的情况。混合模型常用于分析这类数据&#xff0c;它利用不同的组件来对数据中的不同群体或总体进行建模。本质上&#xff0c;混合模型是…

算法--解决二叉树遍历问题

第一 实现树的结构 class Node(): # 构造函数&#xff0c;初始化节点对象&#xff0c;包含数据和左右子节点 def __init__(self, dataNone): self.data data # 节点存储的数据 self.left None # 左子节点&#xff0c;默认为None self.rig…

华为eNSP:MSTP

一、什么是MSTP&#xff1f; 1、MSTP是IEEE 802.1S中定义的生成树协议&#xff0c;MSTP兼容STP和RSTP&#xff0c;既可以快速收敛&#xff0c;也提供了数据转发的多个冗余路径&#xff0c;在数据转发过程中实现VLAN数据的负载均衡。 2、MSTP可以将一个或多个VLAN映射到一个Inst…

从零到一:利用 AI 开发 iOS App 《震感》的编程之旅

在网上看到一篇关于使用AI开发的编程经历&#xff0c;分享给大家 作者是如何在没有 iOS 开发经验的情况下&#xff0c;借助 AI&#xff08;如 Claude 3 模型&#xff09;成功开发并发布《震感》iOS 应用。 正文开始 2022 年 11 月&#xff0c;ChatGPT 诞生并迅速引发全球关注。…