7. 进程控制-进程替换

news2025/5/17 22:32:36

目录

1. 进程替换

1.1 单进程版:

1.2 进程替换的原理

1.3 多进程版-验证各种程序替换接口

2. 进程替换的各种接口

2.1 execl

2.2 execlp

2.3 execv

2.4 execvp

2.5 execle


 

1. 进程替换

上图为程序替换的接口,之后会详细介绍。

1.1 单进程版:

        最简单的看看程序替换的效果:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    printf("before: i am a process, pid: %d, ppid: %d\n", getpid(), getppid());
    execl("/usr/bin/ls", "ls", "-a", "-l", NULL);

    printf("after: i am a process, pid: %d, ppid: %d\n", getpid(), getppid());
    return 0;
}

        我们可以看到,before...执行了,然后执行了execl里的内容,最重要的是after...的内容没有被执行。这就叫程序替换。

1.2 进程替换的原理

       让我们分析一下上面代码的执行过程,刚开始肯定执行本代码,也就是before,然后execl,关键就在于这个接口,这个接口是执行了 ls -l -a,但并不是创建新进程去执行这个指令,而是在这个进程上,直接替换代码和数据,从新程序的main处开始运行,新程序的code和data替换掉老的。

        也就是说我们运行起来的 myproc 这个进程,在运行到execl时,这个进程的代码和数据被换成了 execl 中的命令内容的代码和数据。

1.3 多进程版-验证各种程序替换接口

int main()
{
    //多进程版的程序替换
    pid_t id = fork();
    if(id == 0)
    {
        printf("before: i am a process, pid: %d, ppid: %d\n", getpid(), getppid());
        sleep(5);
        execl("/usr/bin/top", "top", NULL);
        
        printf("after: i am a process, pid: %d, ppid: %d\n", getpid(), getppid());
        exit(0);
    }
    //father
    pid_t ret = waitpid(id, NULL, 0);
    if(ret > 0)
        printf("wait success, father pid: %d, ret id: %d\n", getpid(), ret);
    sleep(5);
    return 0;

程序一运行起来就有两个进程,其中一个是子进程,替换后就结束了,然后父进程5秒后也退出。

有几个现象结论:

  1. 子进程执行程序替换,不会影响父进程父进程在等待子进程结束,没有直接top完就退出
  2. execl时,替换数据时发生写时拷贝,此时替换的代码也发生了写时拷贝!子进程的代码被替换,但是父进程的还在执行。
  3. 程序替换有无创建新进程呢?进程最多都是两个,所以没有!

程序替换成功之后,exec*之后的代码不会被执行;如果替换失败呢?才可能执行后续的代码,所以exec*系列的函数,只有失败返回值,没有成功返回值,因为成功了就替换成功了,显示被替换后的代码的结果。

我们的CPU如何知道程序的入口地址在哪?代码区这么大,程序替换后的main在哪里呢?

2. 进程替换的各种接口

通过对接口参数的学习,我们可以回答上面的问题。

文章一开头的图片就是这一系列的各种接口,有6个,但是其实有7个,剩下那个不在这个手册里。

我们这里介绍5个,剩下的就都知道怎么用了。

2.1 execl

exec是一样的,l 代指 list。

所以这一系列的函数的第一个参数:你要执行一个程序的第一件事情是什么?就是先找到这个程序!这个参数就是决定如何找到程序的,什么路径下的什么程序!

找到这个程序之后,接下来要怎么办?就是如何执行这个程序,主要就是要不要涵盖选项,涵盖哪些?所以从第二个参数开始,命令行中怎么输入,就怎么输入!只不过都要加上""

execl("usr/bin/ls", "ls", "-l", NULL);

最后的NULL是必须要加的。

2.2 execlp

        我们发现它的第一个参数是file,不是path,说明第一个参数只用写明文件就行,不需要指明路径(当然指明也是可以的),它会自动去PATH环境变量中去找这个指令。

execlp("ls", "ls", "-l", NULL);

2.3 execv

        这个接口的最后一个是 v,表示vector,这种不外乎是将传参的形式改变了,之前是传list,现在是传个vector。

char* const myargv[] = {
    "ls",
    "-l",
    "-a",
    NULL
};

execv("/usr/bin/ls", myargv);

exec 会在内部把命令行参数传给ls,exec就是代码层面的加载器,把可执行程序导到内存里。

2.4 execvp

一带p就不用写path,只写file就可以。带v就是把传的参数列表换成vector

2.5 execle

这个e是我们没见过的,它的意思是环境变量,意味着我们可以自己往里传环境变量,前面的命令行参数我们知道可以在内部传给指令,那这个环境变量会传给替换后的代码吗?

在验证这个问题之前,先来讲一些前置知识:

1. 怎么用一个makefile生成两个可执行文件?

我们都知道makefile会把它碰到的第一个文件定位目标文件,那怎么能有两个目标文件呢?总得有个先后吧,所以我们可以这样处理:

.PHONY:all
all:otherExe mycommand

otherExe:otherExe.cpp
	g++ -o $@ $^ -std=c++11
mycommand:mycommand.c
	gcc -o $@ $^ std=c99
.PHONY:clean
clean:
	rm -f mycommand otherExe

接下来我们实现一下:一个可执行程序调用另一个可执行程序

mycommand.c 中可以通过exec系列接口调用其他可执行程序,当然这个可执行程序也得是编译好的,所以需要一个makefile编译两个文件。

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        printf("before: i am a process, pid: %d, ppid: %d\n", getpid(), getppid());
        execl("./otherExe", "otherExe", NULL);
    }
    return 0;
}

这里有一个小问题:我们在调用 otherExe时,是 ./otherExe 这样执行,为什么我们传的第二个参数是otherExe,而不是 ./otherExe 呢?

  • 因为前面已经说明它在哪里了,这里就不用再写一遍了

既然我一个C程序可以调用C++程序,那能不能调用其他语言写的程序呢?

让我们来验证一下:

脚本语言(所谓的脚本语言就是bash从文件中一行一行的执行):test.sh:

#!/usr/bin/bash

echo "hello 1"
echo "hello 1"
echo "hello 1"
echo "hello 1"
echo "hello 1"
echo "hello 1"
echo "hello 1"
ls -a -l

要注意直接./test.sh是不能执行的。

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        printf("before: i am a process, pid: %d, ppid: %d\n", getpid(), getppid());
        //execl("./otherExe", "otherExe", NULL);
        execl("/usr/bin/bash", "bash", "test.sh", NULL);
    }
    return 0;
}

这样就可以在C程序中执行脚本代码,还可以发现其他什么 .py 这种python文件也一样可以运行。

无论是我们的可执行程序,还是脚本,为什么能跨语言调用呢???

这是因为:所有的语言写成的运行起来,本质都是进程!!!只要是进程,就能用exec系列接口调用

回到正题:将这个的目的就是为了验证execle时,要传环境变量,因为我们自己写的程序是可以让他输出环境变量、命令行参数的,所以要用我们自己写的程序验证是否传入成功。

这时就需要观察:由一个程序形成的环境变量如何导给另一个程序(需要我们自己写程序并且一个调用另一个)

先来验证一下命令行参数:我们前面的结论说,命令行参数会在接口内部传给那个命令。

mycommand.c:

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        printf("before: i am a process, pid: %d, ppid: %d\n", getpid(), getppid());
        char *const myargv[] = {
            "otherExe",
            "-a",
            "-b",
            "-c",
            NULL
        };
        execv("./otherExe", myargv);
    }
    return 0;
}

它会把这些个命令行参数传给 otherExe,然后otherExe.cpp:

int main(int argc, char *argv[])
{
    cout << argv[0] << " begin running" << endl;
    for(int i=0; argv[i]; i++)
    {
        cout << i << ":" << argv[i] << endl;
    }
    cout << argv[0] << " stop running" << endl;

    return 0;
}

他会接收到命令行参数,然后打印出来,事实上的效果:

可以看到命令行参数确实传递成功了。

接下来验证一下环境变量:

将otherExe修改成:

int main(int argc, char *argv[], char* env[])
{
    cout << argv[0] << " begin running" << endl;
    cout << "这是命令行参数" << endl;
    for(int i=0; argv[i]; i++)
    {
        cout << i << ":" << argv[i] << endl;
    }
    
    cout << "这是环境变量" << endl;
    for(int i=0; env[i]; i++)
    {
        cout << i << ":" << env[i] << endl;
    }
    cout << argv[0] << " stop running" << endl;
    return 0;
}

我们可以看到:我们调用execv,只传了命令行参数,都没传环境变量,就能打印出来。

环境变量是什么时候给进程的呢?

环境变量也是数据,在创建子进程的时候,环境变量已经被子进程继承下去了!!!

所以即便没传,也有上面继承下来的环境变量。

所以,程序替换中,环境变量信息不会被替换!会一路继承。

bash创建一个子进程,会传给它环境变量,./mycommand也会创建进程,程序替换后调用./otherExe,也会创建进程,一路继承。

如果不想让bash创建环境变量,非要在mycommand中创建环境变量也是可以的:

  • putenv("PRIVATE_ENV=2025516");
  • extern char** environ;
    execle("./otherExe", "otherExe", "-a", "-b", NULL, environ);

    这个environ就是父进程环境变量

自定义环境变量也是可以的,但是这样传过去就只有自己定义的环境变量了,其他的就看不到了。

所以我们想给子进程传递环境变量,该怎么传递??

  1. 新增环境变量
    1. 父进程地址空间中直接putenv
    2. bash直接添加
  2. 彻底替换

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

理解 C# 中的各类指针

前言 变量可以理解成是一块内存位置的别名&#xff0c;访问变量也就是访问对应内存中的数据。 指针是一种特殊的变量&#xff0c;它存储了一个内存地址&#xff0c;这个内存地址代表了另一块内存的位置。 指针指向的可以是一个变量、一个数组元素、一个对象实例、一块非托管内存…

真题卷001——算法备赛

蓝桥杯2024年C/CB组国赛卷 1.合法密码 问题描述 小蓝正在开发自己的OJ网站。他要求用户的密码必须符合一下条件&#xff1a; 长度大于等于8小于等于16必须包含至少一个数字字符和至少一个符号字符 请计算一下字符串&#xff0c;有多少个子串可以当作合法密码。字符串为&am…

Vue3中实现轮播图

目录 1. 轮播图介绍 2. 实现轮播图 2.1 准备工作 1、准备至少三张图片&#xff0c;并将图片文件名改为数字123 2、搭好HTML的标签 3、写好按钮和图片标签 ​编辑 2.2 单向绑定图片 2.3 在按钮里使用方法 2.4 运行代码 3. 完整代码 1. 轮播图介绍 首先&#xff0c;什么是…

微信小程序 自定义图片分享-绘制数据图片以及信息文字

一 、需求 从数据库中读取头像&#xff0c;姓名电话等信息&#xff0c;当分享给女朋友时&#xff0c;每个信息不一样 二、实现方案 1、先将数据库中需要的头像姓名信息读取出来加载到data 数据项中 data:{firstName:, // 姓名img:, // 头像shareImage:,// 存储临时图片 } 2…

全栈项目中是否可以实现统一错误处理链?如果可以,这条链路该如何设计?需要哪些技术支撑?是否能同时满足性能、安全性和用户体验需求?

在复杂系统中&#xff0c;错误一旦出现&#xff0c;可能不断扩散&#xff0c;直到让整个系统宕机。尤其在一个全栈项目中&#xff0c;从数据库到服务器端逻辑、再到前端用户界面&#xff0c;错误可能在任意一个环节产生。如果我们不能在全栈范围内实现统一的错误处理机制&#…

排序01:多目标模型

用户-笔记的交互 对于每篇笔记&#xff0c;系统记录曝光次数、点击次数、点赞次数、收藏次数、转发次数。 点击率点击次数/曝光次数 点赞率点赞次数/点击次数 收藏率收藏次数/点击次数 转发率转发次数/点击次数 转发是相对较少的&#xff0c;但是非常重要&#xff0c;例如转发…

Dify中使用插件LocalAI配置模型供应商报错

服务器使用vllm运行大模型&#xff0c;今天在Dify中使用插件LocalAI配置模型供应商后&#xff0c;使用工作流的时候&#xff0c;报错&#xff1a;“Run failed: PluginInvokeError: {"args":{},"error_type":"ValueError","message":&…

初识计算机网络。计算机网络基本概念,分类,性能指标

初识计算机网络。计算机网络基本概念&#xff0c;分类&#xff0c;性能指标 本系列博客源自作者在大二期末复习计算机网络时所记录笔记&#xff0c;看的视频资料是B站湖科大教书匠的计算机网络微课堂&#xff0c;祝愿大家期末都能考一个好成绩&#xff01; 视频链接地址 一、…

C++ QT图片查看器

private:QList<QString> fs;int i;void MainWindow::on_btnSlt_clicked() {QStringList files QFileDialog::getOpenFileNames(this,"选择图片",".","Images(*.png *.jpg *.bmp)");qDebug()<<files;ui->picList->clear();ui-…

数据集-目标检测系列- 杨桃 数据集 Starfruit>> DataBall

数据集-目标检测系列- 杨桃 数据集 Starfruit&#xff1e;&#xff1e; DataBall * 相关项目 1&#xff09;数据集可视化项目&#xff1a;gitcode: https://gitcode.com/DataBall/DataBall-detections-100s/overview 2&#xff09;数据集训练、推理相关项目&#xff1a;GitH…

【Linux网络】网络套接字编程

套接字编程 一&#xff0c;理解端口号二&#xff0c;初识TCP/UDP协议三&#xff0c;网络字节序四&#xff0c;UDP套接字编程常用API4.1 struct sockaddr类型4.2 socket接口4.3 bind接口4.4 recvfrom4.5 sendto 五&#xff0c;TCP套接字常用API5.1 listen接口5.2 accept接口5.3 …

【data】上海膜拜数据

数据初始样貌 一、数据预处理 1. 数据每5分钟栅格统计 时间数据的处理 path"mobike_shanghai.csv" dfpd.read_csv(path) # 获取时间信息&#xff0c;对于分钟信息&#xff0c;5分钟取整 def time_info(df,col): df[datetime] pd.to_datetime(df[col])df[wee…

DDS(数据分发服务) 和 P2P(点对点网络) 的详细对比

1. 核心特性对比 维度 DDS P2P 实时性 微秒级延迟&#xff0c;支持硬实时&#xff08;如自动驾驶&#xff09; 毫秒至秒级&#xff0c;依赖网络环境&#xff08;如文件传输&#xff09; 架构 去中心化发布/订阅模型&#xff0c;节点自主发现 完全去中心化&#xff0c;节…

【LeetCode 热题 100】动态规划 系列

&#x1f4c1; 70. 爬楼梯 状态标识&#xff1a;爬到第i层楼梯时&#xff0c;有多少种方法。 状态转移方程&#xff1a;dp[i] dp[i-1] dp[i-2]&#xff0c;表示从走一步和走两步的方式。 初始化&#xff1a;dp[1] 1 , dp[2] 2。 返回值&#xff1a;dp[n]&#xff0c;即走到…

计网实验笔记(一)CS144 Lab1

Lab0 ByteStream : 实现一个在内存中的 有序可靠字节流Lab1 StreamReassembler&#xff1a;实现一个流重组器&#xff0c;一个将字节流的字串或者小段按照正确顺序来拼接回连续字节流的模块Lab2 TCPReceiver&#xff1a;实现入站字节流的TCP部分。Lab3 TCPSender&#xff1a;实…

使用 OpenCV 将图像中标记特定颜色区域

在计算机视觉任务中&#xff0c;颜色替换是一种常见的图像处理操作&#xff0c;广泛用于视觉增强、目标高亮、伪彩色渲染等场景。本文介绍一种简单而高效的方式&#xff0c;基于 OpenCV 检测图像中接近某种颜色的区域&#xff0c;并将其替换为反色&#xff08;对比色&#xff0…

智源联合南开大学开源Chinese-LiPS中文多模态语音识别数据集

2025年5月6日&#xff0c;智源研究院在法国巴黎举行的GOSIM全球开源创新论坛上发布Chinese-LIPS中文多模态语音识别数据集&#xff0c;该数据为智源研究院联合南开大学共同构建。 在语音识别技术飞速发展的背景下&#xff0c;多模态语音识别正逐步成为学术界和工业界的研究热点…

RabbitMQ最新入门教程

文章目录 RabbitMQ最新入门教程1.什么是消息队列2.为什么使用消息队列3.消息队列协议4.安装Erlang5.安装RabbitMQ6.RabbitMQ核心模块7.RabbitMQ六大模式7.1 简单模式7.2 工作模式7.3 发布订阅模式7.4 路由模式7.5 主题模式7.6 RPC模式 8.RabbitMQ四种交换机8.1 直连交换机8.2 主…

python爬虫实战训练

前言&#xff1a;哇&#xff0c;今天终于能访问豆瓣了&#xff0c;前几天爬太多次了&#xff0c;网页都不让我访问了&#xff08;要登录&#xff09;。 先来个小练习试试手吧&#xff01; 爬取豆瓣第一页&#xff08;多页同上篇文章&#xff09;所有电影的排名、电影名称、星…

Redis(三) - 使用Java操作Redis详解

文章目录 前言一、创建项目二、导入依赖三、键操作四、字符串操作五、列表操作六、集合操作七、哈希表操作八、有序集合操作九、完整代码1. 完整代码2. 项目下载 前言 本文主要介绍如何使用 Java 操作 Redis 数据库&#xff0c;涵盖项目创建、依赖导入及 Redis 各数据类型&…