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)、程序执行结果