Linux:进程控制(三)——进程程序替换

news2024/11/23 15:51:45

目录

一、概念

二、使用

1.单进程程序替换

2.多进程程序替换

3.exec接口

4.execle 


一、概念

  • 背景

        当前进程在运行的时候,所执行的代码来自于自己的源文件。使用fork创建子进程后,子进程执行的程序中代码内容和父进程是相同的,如果子进程想要执行其他程序的代码呢?

  • 概念

        子进程通过调用一类exec接口来执行另一个程序,这种操作称为程序替换

  • 原理

        如下是一个进程的信息蓝图。

        现在,这个进程想要执行其他程序。

        调用exec这类函数所做的工作就是,将其他程序的代码和数据覆盖式的写入到之前这个程序代码和数据的物理内存空间中,也可能开辟新的空间用来存储,或许还会修改页表的映射关系,总之,这个操作的结果就是,发生替换后,CPU在执行这个进程的时候,代码和数据已经是其他程序的。

  • 程序替换而不是进程替换

        程序替换过程中,只是将程序的代码和数据做了替换,并不是替换进程,所以,这个过程并没有创建新的进程,进程的PID不会发生变化

二、使用

        程序替换过程需要从外设加载数据到内存,因此程序替换这个工作一定是由操作系统来执行的,所以程序替换必然会使用系统调用,先介绍一下相关的接口函数。

        查看man手册,发现有6个接口是语言函数。

man execl
EXEC(3)              Linux Programmer's Manual                                              

NAME
       execl, execlp, execle, execv, execvp, execvpe - execute a file

SYNOPSIS
       #include <unistd.h>

       extern char **environ;

       int execl(const char *pathname, const char *arg, ...
                       /* (char  *) NULL */);
       int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
       int execle(const char *pathname, const char *arg, ...
                       /*, (char *) NULL, char *const envp[] */);
       int execv(const char *pathname, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[],
                       char *const envp[]);

        在man手册中查找系统调用,发现还有一个接口是系统调用。

man execve
EXECVE(2)         Linux Programmer's Manual                                                 

NAME
       execve - execute program

SYNOPSIS
       #include <unistd.h>

       int execve(const char *pathname, char *const argv[],
                  char *const envp[]);

1.单进程程序替换

        以这个函数举例:

int execl(const char *pathname, const char *arg, ... (char  *) NULL);
//第一个参数是待执行程序的路径
//第二个参数是const char * arg
//第三个参数是···即可变参数
//第二个参数和第三个参数(不止一个)是程序的执行选项,传参方式类似于命令行传参
//比如指令 ls -a -l -n
//传参为 "ls","-a","-l","-n"
//注意,参数最终以NULL结尾,不是"NULL"

        编写源文件如下。

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

int main()
{
	printf("exec Before\n");
	execl("/usr/bin/ls","ls","-l","-a","-n",NULL);
	printf("exec End\n");
	return 0;
}	
exec Before
总计 36
drwxrwxr-x  2 1000 1000  4096 10月  9 16:30 .
drwxrwxr-x 13 1000 1000  4096 10月  9 10:23 ..
-rw-rw-r--  1 1000 1000    85 10月  9 10:23 Makefile
-rwxrwxr-x  1 1000 1000 17224 10月  9 16:30 myprocess
-rw-rw-r--  1 1000 1000   207 10月  9 16:15 myprocess.c
  • 疑问,程序中最后一行没有打印,原因是什么?

        调用exec类函数完成程序替换后,当前程序的剩余代码都不会再被执行,因为此时执行的代码已经是另外一个程序的。

  • 关于exec这类函数的返回值

        程序替换成功,则没有返回值,转而执行另外的程序。只有程序替换失败时,才会返回-1,并且设置错误码,如此,调用exec函数后可以直接加一行退出程序的代码,因为程序替换失败时当前程序的运行必然不合预期。

RETURN VALUE
       The exec() functions return only if an error has occurred.  
       The return value is -1, and errno is set to indicate the error.
execl("/usr/bin/ls","ls","-l","-a","-n",NULL);
exit(1);
  • 验证程序替换不会创建新进程

        编写代码如下:

utocoo@utocoo-virtual-machine:~/Desktop/linux/241009$ ll
总计 16
drwxrwxr-x  2 utocoo utocoo 4096 10月 10 10:21 ./
drwxrwxr-x 13 utocoo utocoo 4096 10月  9 10:23 ../
-rw-rw-r--  1 utocoo utocoo   85 10月  9 10:23 Makefile
-rw-rw-r--  1 utocoo utocoo  259 10月 10 10:21 myprocess.c
//myprocess.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>

int main()
{
	printf("exec Before\n");
	printf("I am process,PID:%d\n",getpid());
	sleep(5);

	execl("/usr/bin/top","top",NULL);
	exit(1);
	printf("exec End\n");
	return 0;
}	

      执行如下这条指令,在监视窗口观察PID。

while :; do ps ajx | head -1 && ps ajx | grep myprocess | grep -v grep;sleep 1; done

        编译运行可执行程序myprocess,监视窗口打印结果如下。

   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
   2295    3476    3476    2295 pts/0       3476 S+    1000   0:00 ./myprocess
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
   2295    3476    3476    2295 pts/0       3476 S+    1000   0:00 ./myprocess
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
   2295    3476    3476    2295 pts/0       3476 S+    1000   0:00 ./myprocess
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
   2295    3476    3476    2295 pts/0       3476 S+    1000   0:00 ./myprocess
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND

        PID为3476,但是在程序替换后,捕捉不到PID了,原因是可执行程序的名字发生了变化,执行这条语句再来对比PID。

while :; do ps ajx | head -1 && ps ajx | grep top | grep -v grep;sleep 1; done
PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND 
2295    3476    3476    2295 pts/0       3476 S+    1000   0:00 top

        结果符合预期,PID并未发生变化,即没有创建新的进程。

  • 创建一个进程时,先创建PCB、进程地址空间、页表等内容,再将磁盘中的代码和数据加载到内存中。

        程序替换时,由于并不需要创建新的进程,所以只需要将新的程序代码和数据加载到内存中即可。于是,更进一步理解,操作系统在将程序的代码和数据加载到内存中时,是通过程序替换完成的。

2.多进程程序替换

  • 操作举例

        编写代码如下。

int main()
{
	pid_t id = fork();
	if(id == 0)
	{
 	 printf("exec Before\n");
	 printf("I am child process,PID:%d\n",getpid());
	 sleep(3);

	 execl("/usr/bin/ls","ls","-l",NULL);
	 exit(1);
	 printf("exec End\n");
	}
	sleep(1);
	pid_t rid = waitpid(id,NULL,0);
	if(rid>0)
	{
		printf("wait success!\n");
	}
	return 0;

}

        程序替换的场景,更多的是创建子进程作程序替换,原因很简单,父进程可以获取到程序替换的结果。

        当子进程作程序替换时,此时子进程共享的还是父进程的代码数据,因此发生写时拷贝,不仅是数据发生改变,代码也会发生变化

  • Shell是如何运行指令的?

        指令即一个程序,Shell正在运行时,输入指令后,创建一个子进程,Shell等待子进程(waitpid),子进程此时共享Shell的代码,子进程作程序替换,替换的目标程序就是输入的指令,执行完毕,同时Shell也能获取到子进程的执行结果。

3.exec接口

        前面已经说明了,exec类接口中,语言函数6个,而系统调用只有一个,不难总结出来,函数内部都是封装了系统调用的。

        这些接口的功能大致类似,都是完成程序替换,只是用法传参有所差异。

        先来介绍一下6个接口函数。

int execl(const char *pathname, const char *arg, ...
                       /* (char  *) NULL */);
int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
int execle(const char *pathname, const char *arg, ...
                       /*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
                       char *const envp[]);

        它们都以exec*开头,只是后缀有所区别,有l 、p、e、v

        在介绍函数用法之前,先来区分它们后缀,因为不同的后缀就表示了它们不同的用法。

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

        你可能对这些后缀的意义不明所以,下面来看用法。

  • execlp
int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);

        这个函数包含两个后缀l、p

        包含p,说明带环境变量,在程序替换时,目标程序可以只给出名字,不用给出全部路径,因为环境变量已经包含了一部分,比如系统的指令。

        list则说明需要列表式的传参。

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

        值得一提的是,前两个参数一模一样,但是并不冲突,二者的意义不一样,第一个参数表示目标程序,第二个参数(不止一个)表示执行目标程序的方式,其他参数"-l"、"-a"含义上也是第二个参数。

  • execv
int execv(const char *pathname, char *const argv[]);

        关于argv这个指针数组,这一文中Linux:环境变量介绍main函数的参数时也提到了。这个数组的元素是一个个的指针,每一个指针指向一个字符串,这些字符串其实就是原来使用execl传参时的字符串。

char* const argv[]=
	{
		(char*)"ls",
		(char*)"-l",
		(char*)"-a",
		NULL
	};
execv("/usr/bin/ls",argv);
  • execvp
int execvp(const char *file, char *const argv[]);
execvp("ls",argv);

4.execle 

int execle(const char *pathname, const char *arg, ...
/*, (char *) NULL, char *const envp[] */);

        在介绍这个函数用法之前,要给出一个结论:exec*类函数,可以替换系统的指令,也可以替换任何程序,比如cpp、python、java程序。(C++程序的源文件后缀有.cc、.cpp、.cxx)

        即,程序替换可能发生的情况:使用C语言程序运行的进程,创建子进程后,子进程程序可能被替换为Java程序,等等类似的情况,从这一层面看,程序替换的意义重大!!!

        发生上面所述情况的原因只有一个,就是无论是何种语言编写的程序,在运行之后,都是由操作系统统一管理的进程!!!

        在替换这一层,只有被区分为代码和数据的二进程内容,没有语言的差异,因此替换只是二进制文本被替换。

        使用Makefile文件时,由于make指令只生成第一个可执行程序,因此,想要一次编译链接多个源文件,可以使用下面这样的方式。

.PHONY:all
all:mytest myprocess

mytest:mytest.cc
	g++ -o $@ $^ -g -std=c++11
myprocess:myprocess.c
	gcc -o $@ $^ -g -std=c99
.PHONY:clean
clean:
	rm -f myprocess

        在 Linux:环境变量一文中,总结出环境变量可以被子进程继承,环境变量具备了全局属性

        现在将这些联系起来,操作系统启动Bash程序,等待命令行输入,命令行输入指令,Bash将输入的指令字符串作为exec函数的参数,然后子进程作程序替换,这些参数传给了目标程序的main函数。

        但是环境变量并不是通过传参这样的方式传递的


        编写代码如下,子进程程序被替换为由mytest.cc生成的C++程序,在mytest.cc中打印环境变量,得到结果符合预期。但是在替换程序时调用exec函数时并没有传环境变量的参数

//myprocess.c程序替换时没有传环境变量的参数
execl("./mytest","mytest",NULL);
//mytest.cc打印环境变量
#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
	for(int i =0;environ[i];++i)
	{
		cout <<"environ["<< i << "]" << ":"<< environ[i] << endl;
	}
	return 0;
}
//打印结果是有环境变量的
exec Before
I am process,PID:2819
environ[0]:SHELL=/bin/bash
environ[1]:SESSION_MANAGER=local/utocoo-virtual-machine:@/tmp/.ICE-unix/1578,unix/utocoo-virtual-machine:/tmp/.ICE-unix/1578
environ[2]:QT_ACCESSIBILITY=1
environ[3]:COLORTERM=truecolor
······

        在Linux:地址分区一文中,我有写到,命令行参数、环境变量在虚拟地址空间中的大致位置。

        一个进程的PCB信息包含着虚拟地址空间、页表等内容。当子进程由父进程创建后,子进程有着自己的PCB、虚拟地址空间等,而程序替换不会将物理内存中的环境变量数据替换,因此,子进程是通过继承虚拟地址空间的方式继承全局环境变量


  • 拥有自己的环境变量

        当前进程myprocess想要拥有一个自己的环境变量,可以用函数putenv来实现,这个环境变量会被myprocess的子进程继承,但是它的父进程bash则不包含这个环境变量。

        在myprocess.c的源文件中添加下面一行代码用来导出环境变量。

putenv("NewENV=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");

        在命令行中查看是否有环境变量NewENV,打印结果说明bash(myprocess的父进程)没有这个环境变量。

utocoo@utocoo-virtual-machine:~/Desktop/linux/241009$ echo $NewEnv

utocoo@utocoo-virtual-machine:~/Desktop/linux/241009$ 

       运行myprocess后,做程序替换操作,打印环境变量,发现子进程会继承环境变量。

······
environ[53]:LC_NUMERIC=zh_CN.UTF-8
environ[54]:_=./myprocess
environ[55]:OLDPWD=/home/utocoo/Desktop/linux
environ[56]:NewENV=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        新增环境变量会被子进程继承,但不影响父进程的环境变量


        现在我们清楚的知道,每一个进程的环境变量都会继承于父进程,如果现在要求新建的一个子进程拥有全新的环境变量,不继承父进程的环境变量,要如何做呢?

        使用带e的exec*接口

        编写代码如下。

//myprocess.c
int main()
{
	printf("I am process,PID:%d\n",getpid());
	sleep(3);
	char* const env[]=
	{
		(char*)"1+1=2",
		(char*)"2+2=3",
		NULL
	};
	pid_t id = fork();
	if(id ==0)
	{
	printf("exec Before\n");
	execle("./mytest","mytest",NULL,env);
	exit(1);
	printf("exec End\n");
	}
	pid_t rid = waitpid(id,NULL,0);
	
	return 0;
}

        程序运行结果如下,结果显示子进程没有继承父进程的环境变量。

utocoo@utocoo-virtual-machine:~/Desktop/linux/241009$ ./myprocess 
I am process,PID:3353
exec Before
environ[0]:1+1=2
environ[1]:2+2=3

        这就是带e的exec接口的使用场景。

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

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

相关文章

Python入门笔记(七)

文章目录 第十五章. 下载数据15.1 csv文件15.2 json文件 第十六章. 使用API16.1 requests 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转&#xff1a;人工智能从入门到精通教程 本文电子版获取…

猫头虎分享已解决Bug || Error: ERESOLVE unable to resolve dependency tree 解决方案

&#x1f42f; 猫头虎分享已解决Bug || Error: ERESOLVE unable to resolve dependency tree 解决方案 摘要 在前端开发中&#xff0c;尤其是使用 Node.js 和 npm 管理依赖时&#xff0c;ERESOLVE unable to resolve dependency tree 错误是很多开发者遇到的常见问题。这个 Bu…

ES 入门 -http-条件查询分页查询查询排序

第一种方法的url 地址: http://192.168.1.108:9200/shopping/_search?qcategory:小米 上述url地址的情况&#xff0c;对应的后面的参数信息包含中文&#xff0c;有些时候也会出现乱码导致无法查询到数据&#xff0c; 第二种方式进行body的row -json的传参方式. { "que…

双十一最值得购买的好物?这四款数码好物一定要收藏好!

随着双十一购物节的脚步日益临近&#xff0c;消费者们的热情也在逐渐升温&#xff0c;大家都在翘首以待这场年度购物狂欢。回顾过去&#xff0c;我在双十一期间入手的不少商品都显得格外物超所值&#xff0c;与平日相比确实省下了不少开支。我很高兴能够分享这些精明的购物心得…

【图论】(一)图论理论基础与岛屿问题

图论理论基础与岛屿问题 图论理论基础深度搜索&#xff08;dfs&#xff09;广度搜索&#xff08;bfs&#xff09;岛屿问题概述 岛屿数量岛屿数量-深搜版岛屿数量-广搜版 岛屿的最大面积孤岛的总面积沉没孤岛建造最大人工岛水流问题岛屿的周长 图论理论基础 这里仅对图论相关核…

《企业实战分享 · SonarQube10.x 代码质量推广手册》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

【AI算法岗面试八股面经【超全整理】——CV】

AI算法岗面试八股面经【超全整理】 概率论【AI算法岗面试八股面经【超全整理】——概率论】信息论【AI算法岗面试八股面经【超全整理】——信息论】机器学习【AI算法岗面试八股面经【超全整理】——机器学习】深度学习【AI算法岗面试八股面经【超全整理】——深度学习】NLP【A…

vbox创建虚拟机后用户没有root 权限

XXX is not in the sudoers file. This incident will be reported. 打开终端输入 Su - 输入密码进入root账户 终端visudo修改配置文件 添加如下代码&#xff1a;Syy ALL(ALL:ALL) ALL 编写完成后保存文件 ctrlO 后输入文件名 sudoers.tmp 后按Enter键 退出编辑器&#xf…

018 发布商品

文章目录 获取分类关联的品牌CategoryBrandController.javaCategoryBrandServiceImpl.javaBrandVo.java 获取分类下的所有分组&关联属性AttrGroupController.javaAttrGroupServiceImpl.java 保存七张表sqltb_spu_info.sqltb_spu_info_desc.sqltb_spu_images.sqltb_product_…

UE4 材质学习笔记06(布料着色器/体积冰着色器)

一.布料着色器 要编写一个着色器首先是看一些参考图片&#xff0c;我们需要找出一些布料特有的特征&#xff0c;下面是一个棉织物&#xff0c;可以看到布料边缘的纤维可以捕捉光线使得边缘看起来更亮 下面是缎子和丝绸的图片&#xff0c;与棉织物有几乎相反的效果&#xff0c;…

基于SPI的flash读写操作

1、实验目标 使用页写或连续写操作向Flash芯片写入数据&#xff0c;再使用数据读操作读取之前写入数据&#xff0c;将读取的数据使用串口传回PC机&#xff0c;使用串口助手传回数据并与之前写入数据比较&#xff0c;判断正误。 注意&#xff1a;在向Flash芯片写入数据之前&…

【Redis原理】数据结构(上)

文章目录 动态字符串(SDS)概念SDS特点SDS的优势 IntSet概念IntSet的特点升序统一的编码格式IntSet自动升级 Dict概念Dict特点Dict的伸缩Dict的扩容Dict收缩 Dict的rehash渐进式哈希 总结Dict的结构Dict的伸缩 动态字符串(SDS) 概念 Redis是使用C语言实现的,C语言字符串底层是…

【后端开发】自动化部署、服务管理、问题排查工具(cicd流水线,k8s集群,ELK日志)

【后端开发】自动化部署、服务管理、问题排查工具&#xff08;cicd流水线&#xff0c;k8s集群&#xff0c;ELK日志&#xff09; 文章目录 1、Devops与CICD流水线(TeamCity, Jenkins&#xff0c;GitHub Actions)2、Kubernetes 集群的管理和操作&#xff08;对比Portainer&#x…

【解决】Set-ExecutionPolicy不是内部或外部命令

简介 当使用 VsCode 配置Django项目时&#xff0c;需要配置环境&#xff0c;但是当切换至虚拟环境时&#xff0c;出现了下面的情况。 无法加载文件&#xff1a;D:\django\Scripts\Activate.ps1&#xff0c; 上述问题可通过下面的命令进行解决 解决方法 1 命令行(最好是管理员…

JVM进阶调优系列(1)类加载器原理一文讲透

今天开始写JVM调优系列&#xff0c;并发编程系列也会继续穿插连载&#xff0c;让各位同学闲暇之余有更多阅读选择。 起笔写第一篇&#xff0c;并不好写。首先要构思整个系列的大概框架&#xff0c;一个好的框架一定是深度上由浅入深、逻辑上有严格顺序&#xff0c;读者订阅跟踪…

免费获取的8个SVG图标库,轻松下载与复制!

SVG图标相比传统的JPG、PNG图标具有诸多优势&#xff0c;适用于各种类型的图像&#xff0c;不仅能在不同尺寸下保持清晰度&#xff0c;还具备高度压缩性和轻量特性&#xff0c;支持静态和动态效果。因此&#xff0c;SVG格式在网页设计中往往是优选。尽管如今有很多免费的图标库…

风扇PD协议取电协议芯片-ECP 5702

随着USB-C的普及&#xff0c;市面上消费者PD充电器越来越多&#xff0c;如何让小家电产品也能够支持PD协议快充呢&#xff1f;加入一颗能芯科技PD协议取电协议芯片ECP5702试试看 USB PD协议受电端诱骗协议芯片 1、概述 ECP5702是能芯科技开发的一款专门PD协议的Sink控制器。 …

【论文速看】DL最新进展20241010-扩散模型、目标检测、行人检测

目录 【扩散模型】【目标检测】【行人检测】 【扩散模型】 []Faster Diffusion: Rethinking the Role of UNet Encoder in Diffusion Models 论文链接&#xff1a;https://arxiv.org/pdf/2312.09608 代码链接&#xff1a;https://github.com/hutaiHang/Faster-Diffusion 扩散…

No.10 笔记 | PHP学习指南:PHP数组掌握

本指南为PHP开发者提供了一个全面而简洁的数组学习路径。从数组的基本概念到高级操作技巧&#xff0c;我们深入浅出地解析了PHP数组的方方面面。无论您是初学者还是寻求提升的中级开发者&#xff0c;这份指南都能帮助您更好地理解和运用PHP数组&#xff0c;提高编码效率和代码质…

java批量发送邮件:如何实现高效邮件群发?

java批量发送邮件的教程指南&#xff1f;利用Java实现邮件批发&#xff1f; 随着技术的进步&#xff0c;java批量发送邮件已经成为企业实现高效邮件群发的关键工具。AokSend将探讨如何利用java批量发送邮件技术&#xff0c;实现高效的邮件群发&#xff0c;提升营销效果。 jav…