一、exec()族函数说明
二、exec族函数(execl, execlp, execle, execv, execvp, execvpe)
1、带 L的一类exac函数(execl、execlp、execle)
2、带 P的一类exec函数(execlp、execvp、execvpe)
3、带 V不带 L的一类exec函数(execv、execvp、execve)
4、带 E的一类exec函数(execle、execvpe)
三、exec配合fork使用
在C语言中,exec()函数是一组函数族,用于执行其他程序或者替换当前进程的映像(image)的内容。这些函数允许你在C程序中执行外部程序,通常是可执行文件。
以下是C语言中的exec函数族的主要成员:
execl函数用于执行一个新的程序,并且你需要为它提供可执行文件的路径以及一系列的参数,以及一个以空指针结尾的参数列表。通常,arg0是程序的名称,后面的参数是传递给新程序的命令行参数。
execv函数也用于执行一个新的程序,但它接受参数作为一个字符串数组。argv是一个以空指针结尾的字符串数组,包含了程序的名称和参数。
execlp函数与execl类似,但是它会在系统的路径变量中查找可执行文件,不需要提供完整的路径。
execvp函数类似于execv,但它也会在系统的路径变量中查找可执行文件,不需要提供完整的路径。
- 这些exec函数在执行成功时不会返回,因为它们会替换当前进程的映像。如果执行失败,它们将返回-1,并设置errno以指示错误的类型。
- 这些函数通常与fork()函数一起使用,用于创建新的子进程,然后在子进程中使用exec函数执行其他程序。这是一种常见的模式,用于在C语言中执行其他程序或切换到不同的程序上下文。
我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。
当进程调用exec函数时,该进程被完全替换为新程序。
因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
SYNOPSIS
#include
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[]);
exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
- l : 使用参数列表
- p:使用文件名,并从PATH环境进行寻找可执行文件
- v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
- e:多了envp[ ]数组,使用新的环境变量代替调用进程的环境变量
以
execl
函数为例子来说明:
//文件execl.c(demo18.c)
#include
#include
#include
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(execl("./bin/echoarg","echoarg","abc",NULL) == -1)
{
printf("execl failed!\n");
perror("why failed");
}
printf("after execl\n");
return 0;
}
//文件echoarg.c
#include
int main(int argc,char *argv[])
{
int i = 0;
for(i = 0; i < argc; i++)
{
printf("argv[%d]: %s\n",i,argv[i]);
}
return 0;
}
插入一个perror函数
perror
()是一个C标准库函数,用于打印与上一个系统调用相关的错误信息。这个函数通常用于调试和错误处理,以便在程序发生错误时输出有关错误的信息。perror()函数的原型如下:
void perror(const char *s);
perror("why failed");
- 参数 s 是一个可选的自定义错误消息,通常是一个字符串,用于提供关于错误的额外信息。如果 s 参数为NULL,则perror()将只打印系统调用的错误信息。
- perror()函数通常与全局变量errno一起使用,errno用于保存上一个系统调用的错误代码。perror()函数会解释errno中的错误代码并将相应的错误消息打印到标准错误流(stderr)。
- 程序尝试打开一个不存在的文件并尝试读取文件。如果文件打开失败或读取失败,perror()函数将输出相关的错误消息,帮助程序员诊断问题。
说明echoarg可执行程序文件不在./bin/echoarg路径下
//文件execl.c(demo18.c)
#include
#include
#include
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(execl("./echoarg","echoarg","abc",NULL) == -1)
{
printf("execl failed!\n");
perror("why failed");
}
printf("after execl\n");
return 0;
}
实验说明:
我们先用gcc编译echoarg.c,生成可执行文件echoarg并放在当前路径bin目录下。文件echoarg的作用是打印命令行参数。然后再编译execl.c并执行execl可执行文件。用execl 找到并执行echoarg,将当前进程main替换掉,所以”after execl” 没有在终端被打印出来。
execl("/bin/ls","ls",NULL,NULL)
execl("/bin/ls","ls","-l",NULL)
execl("/bin/date","date",NULL,NULL)
//文件execl.c(demo19.c)
#include
#include
#include
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("execl this process get system ls:\n");
if(execl("/bin/ls","ls",NULL,NULL) == -1)
{
printf("execl failed!\n");
perror("why failed");
}
printf("after execl\n");
return 0;
}
//文件execl.c(demo22.c)
#include
#include
#include
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("execl this process get system ps:\n");
if(execl("ps","ps",NULL,NULL) == -1)
{
printf("execl failed!\n");
perror("why failed");
}
printf("after execl\n");
return 0;
}
上面这个例子因为参数没有带路径,所以execl找不到可执行文件。
下面再看一个例子对比一下:
//文件execlp.c(demo22.c)
#include
#include
#include
//函数原型:int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int main(void)
{
printf("execl this process get system ps:\n");
if(execlp("ps","ps",NULL,NULL) == -1)
{
printf("execl failed!\n");
perror("why failed");
}
printf("after execl\n");
return 0;
}
从上面的实验结果可以看出,上面的exaclp函数带 p ,所以能通过环境变量PATH查找到可执行文件ps
环境变量是一种在计算机操作系统中用于存储配置信息和系统参数的机制。它们是以键-值对的形式存储的,其中键是环境变量的名称,值是与该名称相关联的配置或参数值。环境变量通常用于控制操作系统和应用程序的行为,以及在不同的计算环境中配置和定制软件。
在Unix/Linux系统中,
$PATH
是一个非常常见的环境变量。它用于指定操作系统在搜索可执行文件时应该查找的目录路径。具体来说,$PATH
环境变量包含一系列目录路径,这些目录路径以冒号分隔(在Windows中可能以分号分隔)。当您在终端中输入命令时,操作系统将会按顺序查找这些目录,以查找与命令匹配的可执行文件。
例如,当您在终端中输入ls命令时,操作系统会搜索$PATH
中列出的目录,以查找名为ls的可执行文件,然后执行它。如果ls可执行文件存在于$PATH
中的某个目录,系统将执行它,否则会报错,指示找不到命令。
您可以使用echo $PATH
命令来查看当前系统中$PATH环境变量的值。这将输出一个以冒号分隔的目录路径列表,每个目录路径对应一个操作系统在查找可执行文件时要搜索的位置。
echo $PATH
如char *arg[ ]这种形式,且arg最后一个元素必须是NULL,例如char *arg[ ] = {“ls”,”-l”,NULL};
下面以execvp函数为例说明:
//文件execlp.c(demo23.c)
#include
#include
#include
//函数原型:int execvp(const char *file, char *const argv[]);
int main(void)
{
printf("execl this process get system ps:\n");
char *argv[] = {"ps",NULL,NULL};
if(execvp("ps",argv) == -1)
{
printf("execl failed!\n");
perror("why failed");
}
printf("after execl\n");
return 0;
}
参数例如char *env_init[] = {“AA=aa”,”BB=bb”,NULL}; 带e表示该函数取envp[]数组,而不使用当前环境。
下面以execle函数为例:
//文件execle.c(demo24.c)
#include
#include
#include
//函数原型:int execle(const char *path, const char *arg,..., char * const envp[]);
char *env_init[] = {"AA=aa","BB=bb",NULL};
int main(void)
{
printf("before execle****\n");
if(execle("./echoenv","echoenv",NULL,env_init) == -1)
{
printf("execle failed!\n");
perror("why failed");
}
printf("after execle*****\n");
return 0;
}
//文件echoenv.c
#include
#include
extern char** environ;
int main(int argc , char *argv[])
{
int i;
char **ptr;
for(ptr = environ;*ptr != 0; ptr++)
printf("%s\n",*ptr);
return 0;
}
我们先写一个显示全部环境表的程序,命名为echoenv.c,然后编译成可执行文件放到当前目录下。然后再运行可执行文件execle,发现我们设置的环境变量确实有传进来。
exec函数族的主要作用是在一个进程内启动另一个程序。这些函数用于取代当前进程的映像,也就是将当前运行的程序替换为新的程序。以下是一些使用exec函数族的主要目的和作用:
执行其他程序:
通过使用exec函数,你可以在一个程序中执行另一个程序。这对于在C语言中启动其他程序非常有用,例如,你可以通过exec函数来启动Shell命令、脚本、编译器等其他程序。
程序切换:
exec函数可以用于切换到不同的程序上下文。当一个程序达到了它的目标并且不再需要执行时,它可以使用exec函数切换到一个完全不同的程序,从而释放资源并执行新任务。
定制进程环境:
通过exec函数,你可以自定义新程序的环境。你可以指定不同的参数、工作目录、文件描述符等,以适应不同的需求。这使得你能够灵活地配置新程序的运行环境。
脚本解释:
在某些情况下,exec函数可用于执行脚本文件或解释器。你可以通过exec来启动脚本语言的解释器,比如Python、Perl或Shell脚本,从而执行脚本中的代码。
资源共享:
一个常见的用例是在父子进程之间共享文件描述符或其他资源。子进程可以通过exec函数来继承父进程的资源,以便它们可以在相同的上下文中运行。
总之,exec函数族提供了一种机制,使程序能够动态地启动和执行其他程序,从而扩展了程序的功能和灵活性。这在编写Shell脚本、进程管理、多进程通信等许多领域都非常有用。
exec函数通常与fork函数一起使用,以创建子进程并在子进程中执行另一个程序。这是一种在Unix/Linux系统中常见的编程模式,用于实现进程的替换。以下是一个简单示例,演示了fork和exec的配合使用:
#include
#include
#include
#include
int main() {
pid_t child_pid = fork();
if (child_pid == -1) {
perror("fork");
return 1; // Failed to create a child process
}
if (child_pid == 0) {
// This code is executed in the child process
// Use exec to replace the child process with a new program
char *const args[] = {"ls", "-l", NULL};
execvp("ls", args);
// If exec fails, this code will run
perror("exec");
return 1;
} else {
// This code is executed in the parent process
// Wait for the child process to finish
int status;
waitpid(child_pid, &status, 0);
if (WIFEXITED(status)) {
printf("Child process exited with status %d\n", WEXITSTATUS(status));
}
}
return 0;
}
在这个示例中,fork函数用于创建一个新的子进程,然后在子进程中使用execvp函数来执行ls -l命令,替换子进程的映像。如果execvp执行成功,子进程的映像将被ls命令替代,并在新程序中继续执行。如果execvp执行失败,perror(“exec”)将打印出错误信息。
在父进程中,我们使用waitpid函数等待子进程完成,并获取子进程的退出状态。通过这种方式,父进程可以控制和监视子进程的执行。
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
int changedata = 10;
while(1){
printf("please input a changedata.\n");
scanf("%d",&changedata);
if(changedata == 1){
int fdSrc;
pid = fork();
if(pid > 0){
wait(NULL);
}
if(pid == 0){
char *readBuf=NULL;
fdSrc = open("protest.txt",O_RDWR);
int size = lseek(fdSrc,0,SEEK_END);
lseek(fdSrc,0,SEEK_SET);
readBuf=(char *)malloc(sizeof(char)*size + 8);
int n_read = read(fdSrc, readBuf, size);
char *p = strstr(readBuf,"LENGTH=");
if(p==NULL){
printf("not found\n");
exit(-1);
}
p = p+strlen("LENGTH=");
*p = '5';
lseek(fdSrc,0,SEEK_SET);
int n_write = write(fdSrc,readBuf,strlen(readBuf));
close(fdSrc);
exit(0);
}
}
else{
printf("wait,do nothing\n");
}
}
return 0;
}
SPEED=3
LENGTH=8
SCORE=9
LEVEL=5
gcc ../FILE/demo14.c -o ./changData
#include
#include
#include
#include
#include
#include
#include
//整数类型主函数(整数类型统计参数个数,字符类型指针数组指向字符串参数)
int main(int argc,char **argv)
{
int fdSrc;
char *readBuf = NULL;
//参数错误
if(argc != 2){
printf("param error\n");
exit(-1);
}
//打开原文件Src
fdSrc = open(argv[1],O_RDWR);
//计算文件大小并让光标回到头
int size = lseek(fdSrc,0,SEEK_END);
lseek(fdSrc,0,SEEK_SET);
//开辟空间给buf并读取原文件Src
readBuf = (char *)malloc(sizeof(char)*size + 8);
int n_read = read(fdSrc,readBuf,size);
//查找并修改数据
//char *strstr(const char *haystack, const char *needle);
char *p = strstr(readBuf,"LENGTH=");
if(p == NULL){
printf("not found\n");
exit(-1);
}
p = p+strlen("LENGTH=");
*p = '5';
lseek(fdSrc,0,SEEK_SET);
int n_write = write(fdSrc,readBuf,strlen(readBuf));
//关闭文件
close(fdSrc);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
int changedata = 10;
while(1){
printf("please input a changedata.\n");
scanf("%d",&changedata);
if(changedata == 1){
int fdSrc;
pid = fork();
if(pid > 0){
wait(NULL);
}
if(pid == 0){
execl("./changeData","changeData","protest.txt",NULL);
}
}
else{
printf("wait,do nothing\n");
}
}
return 0;
}