当前位置:首页>Linux>OS27.【Linux】进程程序替换(下)

OS27.【Linux】进程程序替换(下)

  • 2026-10-11 06:28:48
OS27.【Linux】进程程序替换(下)

目录

1.知识回顾

2.让exec系列函数执行自己的可执行文件

3.让exec系列函数利用bash执行shell脚本文件

写一个简单的shell脚本

使用exec系列函数调用shell脚本

4.验证exec系列函数传入了参数

5.验证exec系列函数传入了环境变量

6.给子进程传递环境变量的方法

putenv函数

自定义环境变量,彻底替换原有的环境变量

7.execve系统调用

8.从源代码说明6个exec内部都使用了execve系统调用


1.知识回顾

参见OS24.【Linux】进程等待 (下) 和 进程程序替换(上)文章

2.让exec系列函数执行自己的可执行文件

mycommand.c编译为mycommand可执行文件

#include<stdio.h>intmain(){    printf("Mycommand is running ......\n");    return 0;}

main.c编译为main可执行文件

#include<stdio.h>#include<unistd.h>intmain(){    printf("即将执行execl\n");	execl("mycommand","mycommand",NULL);//mycommand可以不用加具体的路径,因为mycommand和main在同一个目录下	printf("execl已经执行完了\n");    return 0;}

编写makefile,让其生成多个可执行文件

.PHONY:allall:mycommand mainmycommand:mycommand.c    gcc -o $@ $^ -std=c99main:main.c    gcc -o $@ $^ -std=c99.PHONY:cleanclean:    rm -f mycommand main

注意: .PHONY:all和all:mycommand main是必须要加的,否则make会gcc只生成一个写在前的可执行文件(即只生成mycommand,而不生成main)

运行结果:

3.让exec系列函数利用bash执行shell脚本文件

让exec系列函数利用bash执行shell脚本文件来演示C语言调用脚本语言

写一个简单的shell脚本

新建一个shell.sh,sh是shell脚本的后缀名

脚本语言由解释器解释,因此必须在shell.sh中注明用哪个解释器解释

下面填上路径即可:

#! /usr/bin/bash

通过#!来设置运行shell创建一个什么样的进程来执行此脚本,而且#!可以调用任何一个解释器,例如python

然后添加要执行的命令:

#! /usr/bin/bashecho "Hello World!"

检查是否能正常运行可以手动使用bash命令:

使用exec系列函数调用shell脚本

test.c写入:

#include<stdio.h>#include<unistd.h>intmain(){    printf("即将执行test.sh\n");    sleep(1);	execl("/usr/bin/bash","bash","test.sh",NULL);//注意第一个/要加!    return 0;}

运行结果:

当然也可以写python脚本(前提要安装python),test.sh改成:

#! /usr/bin/python3print("Hello Python!")

main.c改成:

#include<stdio.h>#include<unistd.h>intmain(){    printf("即将执行test.sh\n");    sleep(1);	execl("/usr/bin/python3","python3","test.sh",NULL);//注意第一个/要加!    return 0;}

运行结果:

结论:不同语言之间可以相互调用,因为无论是哪个语言编写的程序,其运行起来本质都是进程

4.验证exec系列函数传入了参数

编译main.c

#include<stdio.h>#include<unistd.h>intmain(){	char *const argv[]={"mycommand","-a","-b","-c",NULL};    execv("./mycommand",argv);    return 0;}

编译mycommand.c

#include<stdio.h>intmain(int argc,char* argv[]){	for (int i=0;argv[i];i++)		printf("%s\n",argv[i]);    return 0;}

将mycommand和main放在同一目录下,运行结果:

5.验证exec系列函数传入了环境变量

将main可执行文件中自制的环境变量传到mycommand,然后mycommand调用env命令打印

编译main.c

#include<stdio.h>#include<unistd.h>intmain(int argc,char* argv[],char* envp[]){    execle("./mycommand","mycommand",NULL,envp);    return 0;}

编译mycommand.c

#include<stdio.h>#include<unistd.h>intmain(int argc,char* argv[],char* envp[]){	execle("/usr/bin/env","env",NULL,envp);    return 0;}

将mycommand和main放在同一目录下,运行结果:

之前在OS22.【Linux】初识进程地址空间文章提到过:进程地址空间是含有存放命令行参数和环境变量的区域的

环境变量也是数据,创建子进程的时候,环境变量就已经被子进程(上方的main进程)继承下去了

结论:程序替换执行时,环境变量不会被替换

6.给子进程传递环境变量的方法

由于环境变量信息不随程序替换而替换,而是会一路让所有子进程继承.那么给子进程传递环境变量的方法:

1. 新增环境变量 2. 彻底替换

putenv函数

putenv将环境变量添加到调用进程的上下文

编译main.c

#include<stdio.h>#include<unistd.h>#include<stdlib.h>#include<sys/wait.h>intmain(){	pid_t id = fork();	putenv("TEST=100");    if(id < 0)     {        perror("fork");        return 1;    }    else if(id == 0)//子进程    {        printf("子进程的pid:%d, ppid:%d\n", getpid(), getppid());        execlp("./mycommand","mycommand",NULL);    }    else//父进程得到子进程的PID    {		sleep(2);        printf("父进程准备回收子进程\n");        sleep(2);        pid_t ret = wait(NULL);        if(ret < 0)//返回-1等待失败        {            perror("wait failed");        }        if (ret == id)         {            printf("父进程回收子进程成功,子进程的pid:%d\n", ret);        }        sleep(2);     }    return 0;}

编译mycommand.c

#include<stdio.h>#include<unistd.h>intmain(){	execlp("env","env",NULL);//ls没有带路径    return 0;}

将mycommand和main放在同一目录下,运行结果:

但bash查不到:

TEST是在./main进程下产生的,具有临时性,./main结束后,TEST也就销毁了

结论: 新增环境变量可以使用putenv,将环境变量添加到调用进程的上下文,这样就能被子进程继承

自定义环境变量,彻底替换原有的环境变量

编译main.c

#include<stdio.h>#include<unistd.h>#include<sys/wait.h>intmain(){	pid_t id = fork();    if(id < 0)     {        perror("fork");        return 1;    }    else if(id == 0)//子进程    {        printf("子进程的pid:%d, ppid:%d\n", getpid(), getppid());		char *const envp[]={"TEST=100",NULL};		//彻底替换从./main那里继承而来的环境变量		//./main的环境变量又是从bash那里继承而来的		//bash的环境变量又是从操作系统那里继承而来的		execle("./mycommand","./mycommand",NULL,envp);    }    else//父进程得到子进程的PID    {		sleep(2);        printf("父进程准备回收子进程\n");        sleep(2);        pid_t ret = wait(NULL);        if(ret < 0)//返回-1等待失败        {            perror("wait failed");        }        if (ret == id)         {            printf("父进程回收子进程成功,子进程的pid:%d\n", ret);        }        sleep(2);     }    return 0;}

编译mycommand.c

#include<stdio.h>#include<unistd.h>intmain(int argc,char* argv[],char* envp[]){	execlp("env","env",NULL);//ls没有带路径    return 0;}

将mycommand和main放在同一目录下,运行结果:老的环境变量被覆盖了,即彻底替换

结论:像这样execle("./mycommand","./mycommand",NULL,envp)的是环境变量的覆盖,不是追加,老的环境变量不在了

7.execve系统调用

上面6个函数的底层最终都调用了execve

注意到:上面6个函数位于3号手册,是库函数

而execve处于2号手册,是系统调用

8.从源代码说明6个exec内部都使用了execve系统调用

在glibc-2.42(2025-07-28发布,可以从https://ftp.gnu.org/gnu/libc/?C=M;O=A下载)源代码的posix的文件夹中,有exec系列函数和execve系统调用的源代码:

execv函数:

/* Execute PATH with arguments ARGV and environment from `environ'.  */intexecv (constchar *path, char *const argv[]){  return __execve (path, argv, __environ);}

execl函数:

节选了关键代码

intexecl (constchar *path, constchar *arg, ...){    //......    int ret = __execve (path, (char *const *) argv, __environ);    //......    return ret;}libc_hidden_def (execl)

execle函数:

节选了关键代码

/* Execute PATH with all arguments after PATH until a NULL pointer,   and the argument after that for environment.  */intexecle (constchar *path, constchar *arg, ...){  //......  int ret = __execve (path, (char *const *) argv, (char *const *) envp);  //......  return ret;}libc_hidden_def (execle)

execvp函数:

节选了关键代码

/* Execute FILE, searching in the `PATH' environment variable if it contains   no slashes, with arguments ARGV and environment from `environ'.  */intexecvp(file, argv)const char* file;char* const argv[];{	if (strchr(file, '/') != NULL)	{		/* Don't search when it contains a slash.  */		__execve(file, argv, __environ);		if (errno == ENOEXEC)		{			//......			if (script_argv != NULL)			{				scripts_argv(file, argv, argc, script_argv);				__execve(script_argv[0], script_argv, __environ);				free(ptr);			}		}	}	else	{		//......		do		{			//......			__execve(startp, argv, __environ);			if (errno == ENOEXEC)			{				//......				__execve(script_argv[0], script_argv, __environ);			}			//......		} while (*p++ != '\0');		//......	}	/* Return the error from the last attempt (probably ENOENT).  */	return -1;}libc_hidden_def(execvp)

execlp函数:

节选了关键代码,内部调用了execvp函数

/* Execute FILE, searching in the `PATH' environment variable if   it contains no slashes, with all arguments after FILE until a   NULL pointer and environment from `environ'.  */intexeclp(constchar* file, constchar* arg, ...){	//......	int ret = execvp(file, (char* const*)argv);	//......	return ret;}libc_hidden_def(execlp)

execvpe函数:

节选了关键代码,其中weak_alias (__execvpe, execvpe)说明execvpe是__execvpe函数的别名, __execvpe调用了__execvpe_common函数,后者又调用了__execve

/* Execute FILE, searching in the `PATH' environment variable if it contains   no slashes, with arguments ARGV and environment from ENVP.  */int__execvpe (const char *file, char *const argv[], char *const envp[]){  return __execvpe_common (file, argv, envp, true);}weak_alias (__execvpe, execvpe)static int __execvpe_common(const char* file, char* const argv[], char* const envp[],    bool exec_script){	//......    /* Don't search when it contains a slash.  */    if (strchr(file, '/') != NULL)    {        __execve(file, argv, envp);        if (errno == ENOEXEC && exec_script)            maybe_script_execute(file, argv, envp);        return -1;    }    //......    for (const char* p = path; ; p = subp)    {        //......        __execve(buffer, argv, envp);		//......    }	//......    return -1;}

结论:6个exec内部都使用了execve系统调用(__execve)

最新文章

随机文章