【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 error
        perror("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设为0
        std::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){
        // error
        perror("fork erorr");
        exit(EXIT_FAILURE);
    }else if(id == 0){
        printf("子进程正在运行,pid:%d \n", getpid());
        exit(10);
    }else{
        int status;

        pid_t rid;
        do{ // 有子进程退出时 rid返回其pid
            pid_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.mfbz.cn/a/165340.html

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

相关文章

2020年09月 Scratch(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 下面哪个按钮可以实现音乐结束时音量慢慢变小? A: B: C: D:

Network(四)NAT实现方式与VRRP概述

一 NAT 1 NAT概述 &#xff08;1&#xff09;NAT的作用 Network Address Translation&#xff0c;网络地址转换 通过将内部网络的私有IP地址转换成全球唯一的公网IP地址使内部网络可以连接到互联网。 &#xff08;2&#xff09;私有IP地址分类 A类10.0.0.0~10.255.255.…

基于springboot实现智能热度分析和自媒体推送平台系统项目【项目源码】

基于springboot实现自媒体社区平台系统演示 系统开发平台 在该自媒体分享网站中&#xff0c;Eclipse能给用户提供更多的方便&#xff0c;其特点一是方便学习&#xff0c;方便快捷&#xff1b;二是有非常大的信息储存量&#xff0c;主要功能是用在对数据库中查询和编程。其功能…

RTD系统

RTD&#xff08;实时派工系统&#xff09;帮助半导体工厂优化派工&#xff0c;提升生产效率&#xff0c;提高设备利用率&#xff0c;降低Lot Cycle Time&#xff0c;RTD分为&#xff1a;WhatNext和WhereNext&#xff0c;解决工厂内部机台下一步跑什么Lot和Lot生产完后去哪里的问…

可拖动、可靠边的 popupWindow 实现

0 背景 开发要实现一个可以拖动的圆角小窗&#xff0c;要求松手时&#xff0c;哪边近些靠哪边。并且还规定了拖动范围。样式如下&#xff1a; 1 实现 首先把 PopupWindow 的布局文件 pop.xml 实现 <?xml version"1.0" encoding"utf-8"?> <R…

2024年全网最全的Jmeter+ant+jenkins实现持续集成教程

jmeterantjenkins持续集成 一、下载并配置jmeter 首先下载jmeter工具&#xff0c;并配置好环境变量&#xff1b;参考&#xff1a;https://www.cnblogs.com/YouJeffrey/p/16029894.html jmeter默认保存的是.jtl格式的文件&#xff0c;要设置一下bin/jmeter.properties,文件内容…

当小白遇到电脑程序不完全退出怎么办?

方法一&#xff1a;使用软件默认的退出方式 此处拿百度网盘举例&#xff1a; 用户登录网盘后&#xff1a; 如果直接点击右上角红框中的x 程序不会完全退出 百度网盘点击“x”后仍然在后台继续运行,可以在电脑桌面的右下角看到图标仍然在&#xff0c;证明程序肯定还在运行 部…

有成效的工作

从开始上班起&#xff0c;听到过工作是做不完得。 大概的意思&#xff0c;现在的工作做完了&#xff0c;就会分配新的工作。所以总也做不完。 如果是做不完的&#xff0c;那么是不是在一个岗位上就一直干着呢。既然这个很难成立。那其实工作是可以干得完的。 一个岗位的终结&am…

qemu + busybox + 内核实验环境搭建(2023-11)

主要是参考网上的例子&#xff0c;网上的一些例子可能用的busybox 老旧&#xff0c;编译各种问题&#xff0c;以及rootfs hda的方式或者ramfs的方式。可能有些概念还是不清楚&#xff0c;以下是最终完成测试成功的案例。 下载kernel https://cdn.kernel.org/pub/linux/kernel…

动态改标题

<el-dialog :title"showTitle" :visible"showDialog" close"close"> </el-dialog>使用计算属性 computed: {showTitle() {//这里根据点击的是否有具体点击的那个id来判断return this.form.id ? "编辑部门" : "新增部…

警惕.360勒索病毒,您需要知道的预防和恢复方法。

引言&#xff1a; 网络威胁的演变无常&#xff0c;.360勒索病毒作为一种新兴的勒索软件&#xff0c;以其狡猾性备受关注。本文将深入介绍.360勒索病毒的特点&#xff0c;提供解决方案以恢复被其加密的数据&#xff0c;并分享一系列强化网络安全的预防措施。如果您在面对被勒索…

【洛谷 B2002】Hello,World!(顺序结构)

Hello,World! 题目描述 编写一个能够输出 Hello,World! 的程序。 提示&#xff1a; 使用英文标点符号&#xff1b;Hello,World! 逗号后面没有空格。H 和 W 为大写字母。 输入格式 输出格式 样例 #1 样例输入 #1 无样例输出 #1 Hello,World!思路 #include 是一个预处…

Shell条件测试练习

1、取出/etc/passwd文件的第6行&#xff1b; [rootshell ~]# head -6 /etc/passwd | tail -1 sync:x:5:0:sync:/sbin:/bin/sync [rootshell ~]# sed -n 6p /etc/passwd sync:x:5:0:sync:/sbin:/bin/sync [rootshell ~]# awk NR6 /etc/passwd sync:x:5:0:sync:/sbin:/bin/sync 2…

数据结构之链表练习与习题详细解析

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 数据结构初阶 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂。 目录 1.前言 2.习题解…

一个C语言程序的分析:运行速度和文件大小以及变量初始值

环境 Ubuntu 22.04gcc 11.4.0Window 11Microsoft Visual Studio Community 2022 (64-bit) - Current Version 17.6.2 运行速度 一个C程序 test1.c 如下&#xff1a; int array[30000][30000];int main() {for (int i 0; i < 30000; i)for (int j 0; j < 30000; j) …

Windows安装Vmware 虚拟机

目录 一、Vmware 虚拟机介绍 二、Vmware 虚拟机的三种网络模式 2.1桥接模式 2.2仅主机模式 2.3NAT 网络地址转换模式 三、Vmware 虚拟机的安装 一、Vmware 虚拟机介绍 VMware Workstation Pro 是一款可以在个人电脑的操作系统上创建一个完全与主机操作系统隔离的 虚拟机&…

windows pgsql 数据库 数据目录更改

一.先停止postgres服务 cmd命令 services.msc找到服务停止 二.修改注册表 cmd命令 regedit找到路径 \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\postgresql-x64-13 将“&#xff0d;D”后的目录名修改为新的数据目录位置即可&#xff0c;如果目录路径中含有…

MyISAM和innoDB两种引擎的对比

innoDB 3.23就有了innoDB引擎&#xff0c;5.5成为了默认引擎&#xff0c;支持外键 是一种事务型引擎&#xff0c;可以保证完整提交和回滚 更新、删除比较多的场景&#xff0c;推荐使用innoDB 不过innoDB对内存要求高&#xff0c;因为索引和数据存到一个表了&#xff1b;写操作…

记录:RK3568显示异常。

最近调一个RK3568的新板子&#xff0c;板子其它接口功能都调试ok。可唯独在适配显示时发现&#xff0c;HDMI和MIPI显示均出现异常。当系统启动要进入桌面时候内核就开始报错。 因为这套源码之前在其它的板子上适配过&#xff0c;所以第一反应就是硬件问题或者是那个电压没配置…

[C/C++]数据结构 LeetCode:用栈实现队列

题目描述: 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09;&#xff1a; 实现 MyQueue 类&#xff1a; void push(int x) 将元素 x 推到队列的末尾int pop() 从队列的开头移除并返回元素int peek() 返…