【Linux进程】进程等待 与 进程替换 原理与函数使用

文章目录

  • 一、进程等待
    • 1.1 意义 / 必要性
    • 1.2 进程等待的函数(wait / waitpid)
    • 1.3 status参数
    • 1.4 获取子进程status
    • 1.5 进程的阻塞等待与非阻塞等待
  • 二、进程替换
    • 2.1 引言
    • 2.2 进程替换原理
    • 2.3 替换函数

一、进程等待

1.1 意义 / 必要性

为什么要有进程等待? 我们知道,当子进程退出时,如果父进程不对其进行操作,子进程会变为僵尸进程。

而且当子进程结束时,我们如何知道子进程的最后执行情况呢?

父进程通过进程等待,可以:

  1. 回收子进程资源
  2. 获取子进程退出信息
  3. 同步操作

1.2 进程等待的函数(wait / waitpid)

wait函数

pid_t wait(int *status);
  • 该函数会暂停当前进程的执行,直到一个子进程结束。
  • 如果子进程已经结束,该函数会立即返回。
  • 函数返回值:执行成功 返回被等待进程pid,失败返回-1。
  • status参数:获取子进程的退出状态,不需要则设为null。

waitpid函数

pid_t waitpid(pid_t pid, int *status, int options);
  • 参数:

    • pid
      pid = -1,表示等待任意子进程
      pid = 0,等待进程id=pid 的子进程
    • status
      WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
      WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
    • option
      WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
  • 该函数会暂停当前进程的执行,直到指定的子进程结束。

  • 返回值是结束子进程的进程ID,或者出错时的返回值。

主要的区别和用法总结如下:

  • wait函数只能等待任意子进程结束,而waitpid函数可以指定具体的子进程进行等待
  • waitpid函数提供了更多的选项,可以在等待子进程时指定不同的行为。
  • waitpid函数可以通过设置options参数来控制等待的方式,例如非阻塞、只等待指定状态等。
  • 在多进程的场景中,可以使用waitpid函数更灵活地管理子进程的状态。

1.3 status参数

  • waitwaitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果 传递NULL,表示不关心子进程的退出状态信息
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程

status是int型的整数,用于存储子进程的退出状态信息。其低7位(0-6位)第7位(第7位),其他位(8位及以上),分别存放不同信息。

进程正常终止情况下

  • 低八位(0-7位) 存储的是进程的退出状态。这个状态是由exit系统调用或者C语言中的exit函数传递给操作系统的。
  • 第八位:存储是否生成了core dump文件,如果生成了core dump文件,则该位会被置为1。
  • 高八位(16-31位) 通常被设置为0,用于标识进程的终止状态。

被信号终止情况下

  • 低八位 :存储的是导致进程终止的信号编号。当进程被信号终止时,状态的低八位会存储信号的编号,例如SIGSEGV表示段错误,SIGKILL表示进程被强制终止等。
  • 第8位 :存储是否生成了core dump文件,如果生成了core dump文件,则该位会被置为1。
  • 高八位 :通常被设置为0,用于标识进程的终止状态。

我们用位图来解释status

在这里插入图片描述

1.4 获取子进程status

我们通过下面一段代码 获取子进程status

// 程序获取进程status
int main() {pid_t id = fork();if (id == -1) {// 创建子进程失败perror("fork");exit(EXIT_FAILURE);} else if (id == 0) {// 子进程printf("子进程正在运行\n");exit(10);  // 子进程退出状态设为 10} else {// 父进程int status;pid_t iid = waitpid(id, &status, 0);printf("After the wait function\n");if(iid == id){printf("wait success, pid: %d, status: %d\n", iid, status);}sleep(10);}return 0;
}

代码执行结果如下

在这里插入图片描述

为什么我们给出退出码exit(10)而最后status值为2560?

根据上面status的存储分析:如果子进程正常退出,status 的值将会是子进程退出码的左移 8 位。

即前八位存储的是退出状态, 0000 1010 0000 0000,当我们返回其十进制后,即为2560。

1.5 进程的阻塞等待与非阻塞等待

意义

进程的阻塞等待和非阻塞等待是指在等待某个事件完成时,进程所采取的不同等待方式。

1. 阻塞等待(Blocking Wait)

  • 当一个进程进行阻塞等待时,它会暂时停止执行,并将控制权交给操作系统。进程将进入睡眠状态,直到所等待的事件发生或满足某个条件。
  • 在阻塞等待期间,操作系统会将进程从可运行状态转换为阻塞状态,并在事件发生后将进程重新唤醒。
  • 阻塞等待是一种同步等待方式,即进程会一直等待直到事件完成或超时。

2. 非阻塞等待(Non-blocking Wait)

  • 当一个进程进行非阻塞等待时,它会继续执行其他任务,而不会暂停等待。进程会周期性地检查所等待的事件是否已经发生或满足某个条件。
  • 如果事件已经发生,则进程可以立即处理该事件并继续执行。如果事件还未发生,则进程可能会继续轮询等待,或者进行其他操作。
  • 非阻塞等待是一种异步等待方式,即进程可以同时进行其他任务,而不必一直等待。

代码实现

1.5.1 进程阻塞等待

下面是代码实现:

int main()
{pid_t id = fork();if(id < 0) {// fork errorperror("fork error");exit(EXIT_FAILURE);}else if(id == 0) {// 子进程std::cout << "子进程正在运行, pid: " << getpid() << std::endl;exit(10);}else{// 父进程int status;pid_t rid = waitpid(id, &status, 0); // 阻塞等待,options设为0std::cout << "after wait" <<std::endl;if(rid == id && WIFEXITED(status)){std::cout << "wait success, pid: " << rid << ", status: " << status << std::endl;}else{std::cout << "子进程等待失败, return." << std::endl;return 1;}sleep(10);}return 0;
}

代码执行结果如下

在这里插入图片描述

1.5.2 进程非阻塞等待

代码实现如下:

int main()
{pid_t id = fork();if(id < 0){// errorperror("fork erorr");exit(EXIT_FAILURE);}else if(id == 0){printf("子进程正在运行,pid:%d \n", getpid());exit(10);}else{int status;pid_t rid;do{ // 有子进程退出时 rid返回其pidpid_t rid = waitpid(id, &status, WNOHANG);if(rid == 0){printf("child process is running\n");}sleep(5);}while(rid == 0);if(rid == id && WIFEXITED(status)){printf("wait success, child return code is :%d.\n",WEXITSTATUS(status));}else{printf("wait child failed, return.\n");return 1;}}return 0;
}

代码执行结果

在这里插入图片描述


二、进程替换

2.1 引言

首先,我们看下面一段代码:

#include <iostream>
#include <unistd.h>int main() {std::cout << "This is the original program." << std::endl;// 执行程序替换,将当前进程替换为/bin/ls(列出当前目录内容)char *args[] = {"/bin/ls", "-l", NULL};execv("/bin/ls", args);// 如果execv执行成功,下面的代码不会被执行std::cerr << "Failed to execute /bin/ls!" << std::endl;return 1;
}

代码的执行结果如下:

我们可以看到,程序运行后,执行了 ls -l 操作,而 Failed to execute /bin/ls! 并没有被打印到下面。

而ls本身也是一个程序,可以理解为,进程 被替换为了 ls 程序

2.2 进程替换原理

根据上面的图,我们分析进程替换原理,进程替换可以理解为下面的步骤:

  1. 加载新程序映像:操作系统会从磁盘上读取新程序的可执行文件,并将其加载到内存中。
  2. 创建新的页表:在加载新程序映像时,操作系统会创建一个新的页表来管理新程序所需的内存空间。
  3. 替换进程内容:一旦新程序映像加载到内存中,操作系统 会将当前进程的内容替换为新程序的内容(代码、数据和堆栈等)
  4. 开始执行新程序:替换完成后,控制权转移到新程序的入口点,新程序从那里开始执行

2.3 替换函数

像上面代码中所使用的execv函数,叫做程序替换函数:

下面列举这些程序替换函数,定义如下:

int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *filename, char *const argv[], char *const envp[]);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[]);

这些函数都是程序替换函数,它们的区别在于传递参数的方式和环境变量的处理:

  1. execv:
  • 参数:接受一个参数数组 argv,以及程序路径 path。
  • 示例:execv("/bin/ls", argv);
  1. execvp:
  • 参数:与execv 类似,但是可以通过环境变量 PATH 搜索可执行文件。
  • 示例:execvp("ls", argv);
  1. execve:
  • 参数:接受一个参数数组 argv,一个环境变量数组 envp,以及程序路径 filename。
  • 示例:execve("/bin/ls", argv, envp);
  1. execl:
  • 参数:接受多个参数,最后一个参数必须是 NULL,不接受参数数组,需要将每个参数单独列出。
  • 示例:execl("/bin/ls", "ls", "-l", NULL);
  1. execlp:
  • 参数:与execl 类似,但是可以通过环境变量 PATH 搜索可执行文件。
  • 示例:execlp("ls", "ls", "-l", NULL);
  1. execle:
  • 参数:与execl 类似,但是可以传递自定义的环境变量数组 envp。
  • 示例:execle("/bin/ls", "ls", "-l", NULL, envp);

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】泛型编程 ⑦ ( 模板类常用用法 | 模板类声明 | 模板类调用 | 模板类作为函数参数 )

文章目录 一、类模板基础用法1、类模板声明定义2、类模板使用3、类模板做函数参数 二、完整代码示例1、代码示例2、执行结果 一、类模板基础用法 1、类模板声明定义 上一篇博客中 , 【C】泛型编程 ⑥ ( 类模板 | 类模板语法 | 代码示例 ) 讲解了模板类的基础语法 , 模板类声明如…

从一到无穷大 #19 TagTree,倒排索引入手是否是优化时序数据库查询的通用方案?

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 文章主旨时序数据库查询的一般流程扫描维度聚合时间聚合管控语句 TagTree整体结构索引…

SQL零基础入门教程,贼拉详细!贼拉简单! 速通数据库期末考!(十)

SQL 函数 SQL 拥有很多可用于计数和计算的内建函数。 比如&#xff1a; AVG() - 返回平均值 COUNT() - 返回行数 MAX() - 返回最大值 MIN() - 返回最小值 SUM() - 返回总和 FIRST() - 返回第一个记录的值 LAST() - 返回最后一个记录的值 GROUP BY 学习SQL函数前&#xff0c…

【数据结构&C++】二叉平衡搜索树-AVL树(25)

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 目录 一.AVL树的概念二.AVL树节点的定义(代码…

Unity2021及以上 启动或者禁用自动刷新

Unity 2021以以上启动自动刷新 Edit---> Preferences--> Asset Pipline --> Auto Refresh 禁用的结果 如果不启动自动刷新在Project面板选择Refresh是不会刷新已经修改后的脚本的。

C++之常用算法

C之常用算法 for_each transform #include<iostream> using namespace std; #include<vector> #include<algorithm>class Tranfor { public:int operator()(int var){return var;} };class MyPrint { public:void operator()(int var){cout << var&l…

23.11.19日总结

经过昨天的中期答辩&#xff0c;其实可以看出来项目进度太慢了&#xff0c;现在是第十周&#xff0c;预计第十四周是终级答辩&#xff0c;在这段时间要把项目写完。 前端要加上一个未登录的拦截器&#xff0c;后端加上全局的异常处理。对于饿了么项目的商品建表&#xff0c;之前…

面向对象与面向过程的区别

面向对象 以对象为中心&#xff0c;把数据封装成为一个整体&#xff0c;其他数据无法直接修改它的数据&#xff0c;将问题分解成不同对象&#xff0c;然后给予对象相应的属性和行为。 面向过程 关注代码过程&#xff0c;直接一程序来处理数据&#xff0c;各模块之间有调用与…

mybatis使用xml形式配置

以这个注解形式的查询代码为例 Select("select * from emp where name like concat(%,#{name},%) and gender #{gender} and entrydate between #{begin} and #{end} order by update_time desc ")public List<Emp> list(String name, Short gender, LocalDat…

Unity中Shader法线贴图(上)

文章目录 前言一、法线纹理的作用二、为什么法线贴图长这样&#xff1f;&#xff08;蓝色&#xff09;三、法线贴图能使纹理采样时&#xff0c;进行偏移采样四、在Shader中使用法线贴图1、在属性面板定义一个变量来接收法线贴图2、在使用前声明 _NormalTex3、在片元着色器中&am…

【ATTCK】MITRE Caldera-路径发现插件

CALDERA是一个由python语言编写的红蓝对抗工具&#xff08;攻击模拟工具&#xff09;。它是MITRE公司发起的一个研究项目&#xff0c;该工具的攻击流程是建立在ATT&CK攻击行为模型和知识库之上的&#xff0c;能够较真实地APT攻击行为模式。 通过CALDERA工具&#xff0c;安全…

SQLite 安装和 Java 使用教程

SQLite是一个C语言库&#xff0c;它实现了一个小型、快速、自包含、高可靠性、功能齐全的SQL数据库引擎。SQLite是世界上使用最多的数据库引擎。SQLite内置于所有手机和大多数计算机中&#xff0c;并捆绑在人们每天使用的无数其他应用程序中。 SQLite文件格式稳定、跨平台、向…