当前位置: 首页 > article >正文

Linux相关概念和易错知识点(15)(exec系列函数)

进程程序替换

我们之前已经知道如何创建子进程了。fork函数内部会为我们拷,数据和代码,创建PCB,当走到最后return语句时两个进程分别返回,之后父子进程保持独立。虽然我们可以控制代码逻辑使得父子进程走的代码不同,父子文件始终是使用的同一个代码文件。如果我想让创建出来的子进程执行新的程序,加载新的代码呢?应当如何操作呢?

1.execl

(1)如何使用

这是语言级函数,我们可以在man手册第3页找到。当然它的底层是系统调用,不过我们介绍的是经过C语言封装了的。

形式:int  execl(const  char*  path,  const  char*  arg...)

path是要执行可执行程序的路径,要定位到具体的文件。arg是一个参数包,我们可以写任意个参数。这个参数包的作用是将里面所有的参数都交给我们要执行的可执行程序的命令行参数列表中的argv。也就是说arg要按照我们在命令行调用的写法。如execl("/usr/bin/ls", "ls", "-al", NULL),注意参数包的最后一个元素一定要是空指针。我们可以理解为参数包的内容会原封不动传给argv,显然argv的在最后一个元素就是空指针。

下面是一个使用实例。

为什么execl之后的那一句printf没有被打印呢?这就涉及到execl的工作原理了。

(2)工作原理

当我们用子进程去调用execl时,execl会顺着我们第一个参数的路径找到可执行程序,之后按照arg的内容来调用它。问题时如何在子进程里面去调用另一个可执行程序,系统不会生成新的进程来运行要调用的程序,它会直接用新的目标程序的代码和数据替换子进程的代码和数据。这中间涉及到子进程原有代码和数据的回收,硬盘导入新的代码和数据到内存中,修改页表和进程地址空间,修改一些PCB属性等操作。我们可以理解为,除了PCB这个进程的身份证没变(部分属性会按照新的可执行程序的要求改变),其它的基本上都被覆盖了。进程 = PCB + 代码和数据,只要execl成功了,那就意味着子进程的代码和数据全部被覆盖了。

execl的返回值需要我们特别关注,当execl失败时,就会返回-1,我们需要在execl后写一些处理这种错误的代码。但当execl成功时,它是没有返回值的,因为execl是对进程的代码数据进行完整地替换,当execl成功时,它自己也被覆盖了,自然不存在返回值这种说法。

execl底层也是调用相应系统接口来加载硬盘代码数据的。我们一般选择让子进程去加载,将子进程的代码数据都替换显然不会影响父进程。

Shell(bash)的原理就是如此。我们的所有程序都是在bash这个命令行界面(CLI)跑起来的。当程序启动时,bash创建一个子进程,但此时这个子进程拥有bash的代码数据,此时bash再对该进程调用execl,这样这个子进程就被替换为我们想要执行的程序,完成我们的任务了。

以上原理都必须基于进程的独立性。

2.其它相关函数

我们在man手册里还能见到其它的一些函数,它们都和execl同源,原理相同,功能和使用方式上略有区别

(1)execv

int  execv(const  char*  path,  char*  const  argv[])的第二个参数要求我们直接传指针数组,我们可以认为这个argv会被直接搬给程序的命令行参数列表argv用。v是vector,l是list,两者做好名字区分。

这里的test1就是刚才的test,我们发现argv中不需要写./test1,这是因为execv第一个参数就已经定位了可执行程序了,我们平时命令行写的./test1除了作为第一个argv参数以外还有定位文件的作用,而在这里就不需要了。

(2)execlp、execvp

int  execlp(const  char*  file,  const  char*  arg...)

int  execvp(const  char*  file,  char*  const  argv[])

这两个函数都是在execl和execv的基础上的,不需要我们输入路径,我们直接写要调用的可执行程序即可,它会自己按照环境变量PATH来查找,和指令一样。如execlp("ls", "ls", "a",  NULL),虽然第一个和第二个参数同,但含义却不同。第一个参数是告诉函数它想要执行的可执行程序,第二个是之后传入命令行参数列表argv的第一个参数。

p是path的意思。使用这两个函数要保证我们的可执行程序在PATH中。一般来说调用系统指令可以用这两个函数。

(3)execle、execvpe、execve

这些函数在原来的基础上,允许在最后一个参数自定义替换后的环境变量,而不是原来的环境变量。

如果我们就想新的程序继承原来程序的环境变量,最后一个参数可以写environ,也就是原进程环境变量全局指针。当然,不用这个函数是最直接的办法。

如果我们想要调整环境变量,可以像之前那样使用export、unset等来调整。也可以用putenv((要加的环境变量))来添加环境变量,如在exec覆盖后使用putenv( "A=B" )来添加环境变量。

注意环境变量的操作都只有对当前进程有影响。如果在修改后要创建子进程,那会继承给子进程,但它不会影响父进程以及之前创建的子进程。

其中execve是系统级函数,也就是说上面所有的exec系列函数本质都是以该函数为接口设计的,都是C标准库封装的接口


http://www.kler.cn/news/341568.html

相关文章:

  • 【大数据】HBase集群断电文件坏块导致集群无法启动处理
  • HTML 常用的块级元素和行内元素
  • 医院管理新境界:Spring Boot技术突破
  • stm32学习笔记-RTC实时时钟
  • Spring Boot项目实战教程:快速构建Web应用与RESTful API
  • Flutter渲染过程
  • Spring系列 循环依赖
  • 【必看!!!】Python—requests模块详解!(文末附带无偿大礼包)
  • Patroni配置文件1-动态配置
  • 使用Python实现系统时间跳变检测与日志记录
  • 【斯坦福CS144】Lab7
  • 基于floor函数报错注入sqli-labs less-5和less-6
  • Ajax面试题:(第二天)
  • 毕设分享 基于协同过滤的电影推荐系统
  • 自动化测试框架(全)
  • 2024双十一究竟买什么比较好?为您精选五款双十一必购好物清单!
  • 汽车主机厂主数据管理中一物多码或多码一物问题的具体表现有哪些?
  • 游戏盾是如何解决游戏行业攻击问题
  • LangChain使用few shot template
  • RK3568驱动指南|第十六篇 SPI-第190章 配置模式下寄存器的配置