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

Linux系统编程---exec簇:进程的加载与替换

1、exec簇基础

        在Linux中,用于加载并执行指定程序的API有exec簇和system函数。

        exec簇的进程替换不会创建一个新的进程,只是加载新的程序代码和数据,替换当前进程执行的程序代码。

        system函数的进程替换是创建一个新的子进程,然后在新的子进程里面去执行指定的命令。

上图这个图片就是exec簇中的全部的函数原型:

  • 主要功能:给进程加载指定的程序,如果成功进程的整个内存空间都会被覆盖
  • 接口解析:

     ①、执行指定程序之后,会自动获取原来的进程的环境变量。

     ②、可以观察到,上面的exec簇的各个函数的后缀是有特定含义的,其中:

        l : list 以列表的方式来组织指定程序的参数

        p: 指的是PATH环境变量,意味着执行程序时可指定搜索环境变量PATH的路径

        e: environment 环境变量,在执行指定程序前,同时设置环境变量

        v: vector 矢量、数组,以数组的方式来组织指定程序的参数

      ③、上面的这组函数只是改变了进程的内存空间里面的代码和数据,但是没有改变这个进程的其它属性

2、exec簇API详细说明

(1)、整体描述

        在exec簇中,包含了多个函数,它们的区别主要在于传递参数的方式和环境变量的处理方式。在Linux系统中,可以通过man手册查询到exec函数簇的说明与使用:man 3 exec。但内容较多,不方便理解和抓住核心重点。

        以 execl(const char *path, const char *arg, ...) 来说,参数path是要加载的指定程序,而arg是该程序运行是的命令行参数,需要注意的是,命令行参数包括了程序名本身,并且全部是字符串。

Snail@ubuntu:$ ./a.out 123 abc

        上面的这个命令用execl来表示就是:

execl("./a.out", "./a.out", "123", "abc", NULL);

        上面的命令和代码中:

①、第一个./a.out是程序本身,第二个./a.out是第一个参数。

②、参数列表以NULL结尾。

注意:只要进程 被exec族替换掉之后,在exec函数之后的原先程序的代码都不会执行!!!

        exec簇的每个API具体使用介绍如下所示:

(2)、execl

//函数原型:
#include <unistd.h>
extern char **environ;int execl(const char *pathname, const char *arg, .../* (char  *) NULL */);//函数功能:
系统调用函数,用于在当前进程的上下文中执行一个新的程序。//参数说明:
pathname:目标程序的路径名,支持绝对路径和相对路径。
arg:传递给新程序的第一个命令行参数,一般是程序的名称。
...:这是可变参数列表,代表传递给新程序的其他命令行参数。参数列表必须以 NULL 结尾,以此来表明参数列表结束。//返回值:
成功无返回值
失败返回-1,并通过errno说明错误原因//使用示范:
execl("./a.out", "b.out", "abcd", NULL);

(3)、execv

//函数原型:
#include <unistd.h>
extern char **environ;int execv(const char *pathname, char *const argv[]);  //vector//函数功能:
系统调用函数,用于在当前进程的上下文中执行一个新的程序。//参数说明:
pathname:目标程序的路径名,支持绝对路径和相对路径。
arg:指针数组,数组中的每个元素都是指向以空字符结尾的字符串的指针,这些字符串是传递给新程序的命令行参数。
数组的最后一个元素必须为 NULL,以此来表示参数列表的结束。//返回值:
成功无返回值
失败返回-1,并通过errno说明错误原因//使用示范:
char *const argv[ ] = {"hello","1.c","2.c",NULL};
execv("./hello", argv);

(4)、execlp

//函数原型:
#include <unistd.h>
extern char **environ;int execlp(const char *file, const char *arg, .../* (char  *) NULL */);//函数功能:
系统调用函数,用于在当前进程的上下文中执行一个新的程序。//参数说明:
file: 要执行的程序的文件名。execlp 会先在当前目录查找该文件,如果没找到,会根据环境变量 PATH 中指定的路径来查找该可执行文件。
arg:传递给新程序的第一个命令行参数,通常是程序的名称。
...: 可变参数列表,代表传递给新程序的其他命令行参数。参数列表必须以 NULL 结尾,以此来表明参数列表结束。//返回值:
成功无返回值
失败返回-1,并通过errno说明错误原因//使用示范:
//调用 execlp 函数执行 ls -l 命令
execlp("ls", "ls", "-l", NULL);

(5)、execle

//函数原型:
#include <unistd.h>
extern char **environ;int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */);
//函数功能:
在当前进程的上下文里执行新程序,同时可以自定义新程序的环境变量。//参数说明:
pathname: 目标程序的路径名,支持绝对路径和相对路径。
arg: 传递给新程序的第一个命令行参数,通常为程序的名称。
...: 可变参数列表,代表传递给新程序的其他命令行参数。参数列表需以 NULL 结尾,以此来表明参数列表结束。
envp: 指针数组,数组中的每个元素都是指向以空字符结尾的字符串的指针,这些字符串代表传递给新程序的环境变量。数组的最后一个元素必须为 NULL。//返回值:
成功无返回值
失败返回-1,并通过errno说明错误原因//使用示范:
//使用 execle 函数来执行一个新程序并传递自定义的环境变量:const char *path = "/usr/bin/env";    // 定义要执行的程序的路径
char *const args[] = {    // 定义传递给程序的参数"env",NULL
};
char *const env[] = {     // 定义自定义的环境变量"MY_VAR=my_value",NULL
};// 调用 execle 函数执行程序
execle(path, args[0], NULL, env);

(6)、execvp

//函数原型:
#include <unistd.h>
extern char **environ;int execvp(const char *file, char *const argv[]);//函数功能:
在当前进程的上下文中执行新程序,并且会根据环境变量 PATH 查找可执行文件。//参数说明:
file: 要执行的程序的文件名。execvp 会先在当前目录查找该文件,如果未找到,会依据环境变量 PATH 所指定的路径去查找可执行文件。
argv: 这是一个指针数组,数组中的每个元素都是指向以空字符结尾的字符串的指针,这些字符串是传递给新程序的命令行参数。数组的最后一个元素必须为 NULL,以此表示参数列表的结束。//返回值:
成功无返回值
失败返回-1,并通过errno说明错误原因//使用示范:
char *const argv[ ] = {"ls","-l","demo.c",NULL};
int ret = execvp("ls", argv);
if(ret == -1)
{perror("execvp error");
}

(7)、execvpe

//函数原型:
#include <unistd.h>
extern char **environ;int execvpe(const char *file, char *const argv[],
char *const envp[]);//函数功能:
系统调用函数,用于在当前进程的上下文中执行一个新的程序。
这个函数结合了 execvp 和 execle 的特点,既可以根据环境变量 PATH 来查找可执行文件,又可以自定义新程序的环境变量。//参数说明:
file: 要执行的程序的文件名。execvpe 会先在当前目录查找该文件,如果没找到,会根据环境变量 PATH 中指定的路径来查找可执行文件。
argv: 指针数组,数组中的每个元素都是指向以空字符结尾的字符串的指针,这些字符串是传递给新程序的命令行参数。数组的最后一个元素必须为 NULL,以此表示参数列表的结束。
envp: 指针数组,数组中的每个元素都是指向以空字符结尾的字符串的指针,这些字符串代表传递给新程序的环境变量。数组的最后一个元素必须为 NULL。//返回值:
成功无返回值
失败返回-1,并通过errno说明错误原因//使用示范:
//execvpe 不仅仅会去PATH环境变量路径下寻找程序,还会去指定的路径envp下寻找
char *const argv[ ] = {"hello","1.c","2.c",NULL};
char *const envp[ ] = {"./"};
execvpe("./hello",argv,envp);

3、exec簇程序应用举例

        要求:写一个程序,这个程序创建一个子进程后,子进程打印haha,然后监控键盘,通过键盘的参数去启动另外一个程序,比如键盘输入 hello 123 456,就代表启动 hello程序,传递 123 456 到程序hello中,并且在程序hello把传递过来的参数打印出来。

(如果键盘输入"end"表示输入结束)

(1)、hello.c

#include <stdio.h>int main(int argc, char **argv)
{printf("%s\n", __FILE__);printf("进程ID:%d\n", getpid());for(int i=0; i<argc; i++){printf("argv[%d]:%s\n", i, argv[i]);}printf("Byebye!\n");    return 0;
}

(2)、exec_demo.c

#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>int main(int argc , char **argv)
{printf("main id:%d\n",getpid());pid_t id = fork();if(id >0)         //父进程{printf("父进程 ID:%d\n",getpid());}else if(id ==0)  //子进程{//创建子进程之后,让子进程 执行新的目标程序printf("子进程 ID:%d \n",getpid());char input_str[10][256] = {0};  //char (*p)[256] = input_str;  --> char (*)[256]char *argv_buf[10] = {NULL};     //数组 ,每个元素存储的是 地址 char*// char*(*p)  = argv;  --> char **int cnt = 0;//1、用户输入 子进程要执行的新程序的名字  以及 要传递 新程序的参数printf("请输入子进程需要加载替换的程序名及参数(end结束):");  //hello  123  456  789  endwhile(1){scanf("%s",input_str[cnt]);//如果输入 end ,则退出if(0 == strcmp(input_str[cnt],"end")){break;}//argv[0] ---》char * 存储字符串argv_buf[cnt] = input_str[cnt];cnt++;}char file_name[1024] = {0};sprintf(file_name,"%s%s","./",input_str[0]);//2、调用exec函数簇 执行 新的程序 //execv(const char *path, char**argv);if(execv(file_name, argv_buf) == -1)  //char (*)[256]{perror("execv error");}}wait(NULL);return 0;
}

 (3)、程序执行结果

相关文章:

  • 空间计算:开启人机交互新纪元的下一代技术范式
  • 解决 Win11/Win10 “为了对电脑进行保护,已经阻止此应用”问题
  • JAVA设计模式——(八)单例模式
  • 3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目
  • JQuery 使用技巧
  • MCP之一_MCP协议解析
  • 邦芒秘籍:面试时自我介绍主要包含四个方面
  • PyCharm 2023升级2024 版本
  • 线下CPG零售的核心:POG与销量的循环优化
  • 回归问题常用模型以及优缺点和使用场景
  • TP5兼容达梦国产数据库
  • JAVA EE_网络原理_UDP与TCP
  • DeepSearch复现篇:QwQ-32B ToolCall功能初探,以Agentic RAG为例
  • SAP /SDF/SMON配置错误会导致HANA OOM以及Disk Full的情况
  • 【TS入门笔记3---接口(interface)、 函数与泛型 、类与面向对象 】
  • [原创](现代Delphi 12指南):[macOS 64bit App开发]: 跨平台开发同样支持retain()引用计数器处理.
  • 【Spark入门】Spark RDD基础:转换与动作操作深度解析
  • 爬虫学习笔记(三)--Http协议
  • 厚铜PCB如何兼顾质量与成本?供应商设计规范执行的黄金平衡点
  • 【行业特化篇2】金融行业简历特化指南:合规性要求与风险控制能力的艺术化呈现
  • 伊朗港口爆炸死亡人数升至70人
  • 国家卫健委:工作相关肌肉骨骼疾病、精神和行为障碍成职业健康新挑战
  • 吉林省公安厅出入境管理总队政委明志全已任省安保集团总经理
  • 夜读丨庭院春韵
  • 国家统计局:一季度规模以上工业企业利润延续持续恢复态势
  • 人民日报:光荣属于每一个挺膺担当的奋斗者