进程程序替换

引言:

父进程通过fork()函数创建一个子进程,子进程将和父进程运行相同的代码,但创建子进程的大多情况,是希望能够运行一些其他的程序,这时候就需要用到进程程序替换。

exec函数族

想要实现进程程序替换就要用到exec函数族,exec函数族内有6个函数

 #include <unistd.h> 
int execl(const char *path, const char *arg, ...);

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

int execle(const char *path, const char *arg, ..., char *const envp[]);

int execv(const char *path, char *const argv[]);

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

int execve(const char *path, char *const argv[], char *const envp[]); 

调用exec函数族内的函数,能够替换当前当前程序,转而运行一段新程序。

exec函数族中的函数的作用是,在一个正在运行的进程内部根据函数所指定的文件名和路径找到可执行的文件,并将进程执行的程序替换为新程序,新程序从main处开始执行。

这里有几点需要注意:

  1. exec函数族替换掉了当前程序虚拟地址空间内的代码和数据,并不重新创建页表和虚拟地址空间,这也同样说明进程的标识符(pid)不变。
  2. 当exec函数族的函数调用成功后,会直接运行新代码,原代码exec函数后面的代码将不在运行(因为代码段里的代码已经被换掉了)。除非调用失败。
  3. 只有exec函数调用失败(调用成功是没有返回值的),才有返回值,失败返回-1.
  4. 替换后的新程序将从入口函数运行。
  5. 函数族内只有execve属于系统调用,其他的几个函数都是经过封装的库函数,最终都会调用execve,下图是对各个函数调用的过程
    在这里插入图片描述

exec函数解析

之前只是写出了exec函数的所有种类,下面将对各个函数进行分析。

exec函数的命名

exec函数族内的函数都是以exec开头的,他们的具体功能取决于最后的一个或多个字母:
最后的字母代表如下含义:

l(list): 表示参数采用列表
v(vector):参数用数组
p(path):能够自动搜索环境变量path
e(env):表示自己维护环境变量

不带字母p(表⽰path)的exec函数, 第⼀个参数必须是程序的相对路径或绝对路径,例如 “/bin/ls“或”./a.out”,⽽不能 是”ls”或”a.out”。

对于带字母p的函数,无需写全路径,如果参数中包含/,则将其视为路径名。否则视为不带路径的程序名,在PATH环境变量的⽬录列表中搜索这个程序。

带有字母l(表⽰list)的exec函数,要求将新程序的每个命令⾏参数都当作⼀个参数传给它,命令⾏ 参数的个数是可变的,因此函数原型中有…,…中的最后⼀个可变参数应该是NULL, 起sentinel的作⽤。

带有字母v(表⽰vector)的函数,则应该先构造⼀个指向各参数的指针数 组,然后将该数组的⾸地址当作参数传给它,数组中的最后⼀个指针也应该是NULL,就像main函数 的argv参数或者环境变量表⼀样。

对于以e(表⽰environment)结尾的exec函数,要自己组装环境变量,其他exec函数仍使⽤当前的环境变量表执⾏新程序。

下面对各个函数进行测试:

1.execl:

因为execl函数没有字母p,所以第一个参数要写完整的路径名,且因‘l’,所以每个命令行参数均要作为execl的参数,且最后以NULL结尾。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    extern char**  environ;
    char** ptr;
    pid_t id=vfork();
    if(id==0)
    {
        //child
        sleep(1);
        printf("Hello child,pid:%d,ppid:%d\n",getpid(),getppid());
        execl("/bin/ls","ls","-a","-l",NULL);
        exit(1);
    }
    else
    {
        printf("Hello father,pid:%d,ppid:%d\n",getpid(),getppid());
        printf("<----environ list---->\n");
        wait(NULL);
    }
    return 0;
}

运行结果:
在这里插入图片描述

2.execv

execv也要在第一个参数写清楚全路径,他的特点是要弄一个指针数组来放各个命令行参数,在传参时直接传数组名即可。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    extern char**  environ;
    char* const Arg[]={
        "ls",
        "-a",
        "-l",
        NULL,
    };
    pid_t id=vfork();
    if(id==0)
    {
        //child
        sleep(1);
        printf("Hello child,pid:%d,ppid:%d\n",getpid(),getppid());
        //execl("/bin/ls","ls","-a","-l",NULL);
        execv("/bin/ls",Arg);
        exit(1);
    }
    else
    {
        printf("Hello father,pid:%d,ppid:%d\n",getpid(),getppid());
        printf("<----environ list---->\n");
        wait(NULL);
    }
    return 0;
}

3.execvp

相比较于execv,execvp函数主要是我们不需要列出程序的具体路径,只需要列出文件名,函数会根据环境变量PATH自动的在PATH中指定的目录查找我们列出的可执行文件。

所以只要吧把上述代码中的 execv("/bin/ls",Arg) 改成execvp(“ls”,Arg);即可

4.execlp

同理,execlp函数就是把execl函数的路径更改成可执行文件名。

5.execle

相比于前面的四个函数,execle函数允许我们指定所替换的进程中的环境变量,而不是所替换的进程直接继承他的父进程的environ.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    extern char**  environ;
    char* const Envp[]={
        "MYPATH=/leap",
        "MyID=010",
        NULL
    };
    pid_t id=vfork();
    if(id==0)
    {
        //child
        sleep(1);
        printf("Hello child,pid:%d,ppid:%d\n",getpid(),getppid());
        //execl("/bin/ls","ls","-a","-l",NULL);
        //execvp("ls",Arg);
        execle("./Env","./Env",NULL,Envp);
        exit(1);
    }
    else
    {
        printf("Hello father,pid:%d,ppid:%d\n",getpid(),getppid());
        printf("<----environ list---->\n");
        wait(NULL);
    }
    return 0;
}
<think>我们正在讨论Linux系统中的进程程序替换。根据引用[2],进程程序替换是指子进程执行全新的程序,不再执行父进程的代码。引用[3]展示了一个使用execl函数进行程序替换的例子。原理上,当调用exec系列函数时,当前进程的代码和数据被新程序的代码和数据替换,但进程ID保持不变。引用[1]和[4]也提到了进程程序替换的概念和函数使用。具体来说,进程程序替换的工作原理如下:1.调用exec函数(如execl、execv等)后,操作系统会加载指定的新程序到当前进程的内存空间。2.新程序替换当前进程的代码段(text)、数据段(data)、堆(heap)和栈(stack)等。3.进程ID(PID)保持不变,因此替换后仍然是同一个进程,但执行的是全新的程序。4.如果exec函数调用成功,则不会返回(因为原程序的代码已被替换),只有调用失败时才会返回错误。例如,在引用[3]的代码中:```cexecl("/usr/bin/ls","ls","-l","-a",NULL);```调用execl后,当前进程将执行`/usr/bin/ls`程序,并传递参数"ls","-l","-a"。注意事项(引用[4]):-exec系列函数只有调用失败时才返回,所以成功时后面的代码不会执行。-新程序从main函数开始执行。-环境变量可以通过exec函数传递(如execle、execvpe等)。常见的exec函数族包括:-execl:参数以列表形式传递(可变参数)。-execv:参数以数组形式传递。-execlp、execvp:使用PATH环境变量查找程序。-execle、execvpe:可传递环境变量。例如,使用execvp:```cchar*argv[]={"ls","-l","-a",NULL};execvp("ls",argv);```总结:进程程序替换通过exec系列函数实现,它替换当前进程的映像为新的程序映像,但保持进程ID不变,从而实现让子进程执行与父进程完全不同的程序。§§相关问题§§1.在Linux中,exec函数族有哪些常见函数?它们有什么区别?2.进程程序替换后,原进程打开的文件描述符会如何处理?3.如何传递环境变量给新程序?4.为什么exec函数调用成功不会返回?5.进程程序替换和创建新进程(fork)有什么区别和联系?</think>### Linux 进程程序替换的工作原理 在 Linux 系统中,**进程程序替换**是指在不创建新进程的前提下,将当前进程执行的程序完全替换为新程序的技术。其核心原理是通过 `exec` 函数族实现,具体工作原理如下: #### 1. **内存空间替换(核心机制)** - 当调用 `exec` 函数(如 `execl()`)时,操作系统会**清空当前进程的代码段、数据段和堆栈**。 - 新程序的代码和数据从磁盘加载到内存,覆盖原进程的内存空间: ```c execl("/usr/bin/ls", "ls", "-l", "-a", NULL); // 替换为 ls 程序 ``` - 进程 ID(PID)保持不变,但执行的指令和数据完全更新[^2][^3]。 #### 2. **执行流程重置** - 新程序从 `main()` 函数开始执行,原程序的执行流被永久中断。 - 若替换成功,`exec` 函数**不会返回**(原程序代码已被清除);失败时才返回错误码[^3][^4]。 #### 3. **资源继承与处理** - **保留的资源**: - 进程 PID、父进程 PID - 文件描述符(除非显式设置 `FD_CLOEXEC`) - 工作目录、信号处理设置 - **重置的资源**: - 挂起的信号被清除 - 内存锁(`mlock`)被释放 - 线程局部存储被销毁[^2][^4] #### 4. `exec` 函数族的分工 | 函数 | 参数传递方式 | 是否搜索 PATH | 环境变量处理 | |-------------|-------------------|---------------|-----------------| | `execl()` | 参数列表 (可变参数) | ❌ | 继承父进程 | | `execv()` | 参数数组 | ❌ | 继承父进程 | | `execvp()` | 参数数组 | ✅ | 继承父进程 | | `execle()` | 参数列表 | ❌ | 自定义环境变量 | > 示例:`execvp("ls", (char*[]){"ls", "-l", NULL})` 自动搜索 `PATH` 中的 `ls`[^4] #### 5. **与 `fork()` 的协同工作** 典型用法是先 `fork()` 创建子进程,再在子进程中调用 `exec()`: ```c pid_t pid = fork(); if (pid == 0) { // 子进程 execl("/bin/ls", "ls", NULL); perror("exec failed"); // 仅 exec 失败时执行 exit(1); } else { // 父进程 waitpid(pid, NULL, 0); } ``` 这种组合实现了**创建新进程并执行不同程序**的能力[^2][^3]。 #### 关键特点总结 - **零进程创建开销**:仅替换内存内容,不创建新进程 - **原子性**:替换操作不可中断,要么完全成功要么失败 - **环境可控**:可通过 `execle()` 等函数定制新程序的环境变量 - **失败回退**:若替换失败(如路径错误),原进程继续执行后续代码[^3][^4] > 应用场景:Shell 命令执行(如 `bash` 执行 `ls`)、守护进程重启自身、安全沙盒等[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值