Linux execve函数详解

2024-03-13 12:20
文章标签 linux 函数 详解 execve

本文主要是介绍Linux execve函数详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 1 基本介绍
    • 2 execve实例
      • 2.1 自定义argv和envp
      • 2.2 fork后再通过子进程执行execve

1 基本介绍

#include <unistd.h>
int execve(const char *pathname, char *const argv[], char *const envp[]);
  • 描述

    execve()执行由pathname引用的程序。这会导致当前由调用进程运行的程序被一个新程序替换,该新程序具有新初始化的堆栈、堆和(已初始化和未初始化)数据段。

    pathname必须是二进制可执行文件或以形式为#!interpreter [optional-arg]开头的脚本。

    argv是传递给新程序作为其命令行参数的字符串指针数组。按照惯例,这些字符串中第一个(即argv[0])应包含与正在执行文件相关联的文件名。argv数组必须以NULL指针结尾。(因此,在新程序中,argv[argc]将为NULL)。

    envp是传递给新程序环境变量的字符串指针数组。该数组包含了环境变量。每个环境变量都是一个 char* 指针,格式为 “name=value”。envp数组同样必须以NULL指针结尾。

    char *envp[] = {"PATH=/bin","HOME=/home/user","USER=user",NULL // 终止环境变量数组
    };
    

    argvenvp可以从新程序的主函数访问。例如我们编写的C程序,实际上是由操作系统通过execve()系统调用执行(这里是操作系统先执行fork系统调用,创建一个新的子进程,然后在新的子进程中,操作系统执行execve()系统调用),它会传递这些参数给新程序的主函数,即 main 函数。这些参数定义了新程序执行时的环境和命令行参数,在程序启动时由操作系统设置,并在整个程序执行期间保持不变。这使得程序能够根据传递给它的参数和环境变量来执行不同的任务或调整其行为。

  • 返回值

    成功时,execve() 不返回任何值,当 execve 成功替换当前进程的映像并开始执行新的程序时,原来的进程(即调用 execve 的进程)已经不再存在,因此无法返回任何值。

    在出错时返回 -1,并设置适当的 errno

  • 重点

    1. execve实际上就是将当前运行的状态机重置成另一个程序的初始状态
    2. 允许对新状态机设置参数 argv (v) 和环境变量 envp (e)
    3. 在程序启动时,操作系统首先执行 fork 系统调用,创建一个新的子进程。然后,操作系统在子进程中执行 execve 系统调用,以替换子进程的程序映像并开始执行新的程序。原来的父进程继续执行 fork 之后的代码。
    4. 在调用 execve 之前,确保释放所有不再需要的资源,如打开的文件描述符、锁等。
    5. 在调用 execve 之前,确保子进程已经处理了所有待处理的信号,除非你希望信号处理程序在新程序中执行。
    6. 如果 execve 失败,子进程通常应该终止。
    7. 在父进程中,通常会在 fork 之后立即调用 wait 或 waitpid 来等待子进程结束,以确保父进程不会过早退出,从而导致子进程的僵尸进程。

2 execve实例

2.1 自定义argv和envp

#include <unistd.h>
#include <stdio.h>int main() {char * const argv[] = {"/bin/bash", "-c", "env", NULL,};char * const envp[] = {"HELLO=WORLD", NULL,};execve(argv[0], argv, envp);printf("Hello, World!\n");return 0;
}

在这段代码中,我们显式的设置了argvenvp,其中参数 "/bin/bash", "-c", "env", NULL,这里的参数实际上是在告诉 bash 执行一个命令(由 -c 后面的字符串指定),在这个例子中是 env,它打印当前的环境变量。

我们运行代码,得到如下输出:

image-20240313091345219

如果我们不传 -c 参数和随后的命令,即只传入 "/bin/bash", NULL 作为参数,bash 会默认进入交互式模式。在这种模式下,它不会执行任何命令并立即退出,而是会等待用户输入,表现为进入了 shell 环境。

我们发现,打印的当前环境变量除了自定的envp,还有一些其他的输出。这是因为除了我们设定的环境变量外,还有一些系统或者 shell 默认的环境变量会被添加到新进程中,例如 PWD 表示当前工作目录,SHLVL 表示 shell 层级,_ 是上一个执行的命令。这就是为什么我们会看到额外的环境变量出现在输出中。

2.2 fork后再通过子进程执行execve

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>int main() {pid_t pid = fork();if (pid == 0) {// 子进程char * const argv[] = {"/bin/echo", "Hello, World!", NULL};char * const envp[] = {NULL};execve("/bin/echo", argv, envp);} else if (pid > 0) {// 父进程wait(NULL); // 等待子进程结束printf("Child process finished.\n");} else {// fork失败perror("fork");return 1;}return 0;
}

这段代码演示了如何使用 fork() 系统调用创建一个新的子进程,然后在子进程中执行 execve() 系统调用。这是在 Unix-like 系统中常见的操作模式,因为 execve() 系统调用有一些关键的限制:

  • 一次机会:execve() 系统调用只能用于替换当前进程的映像一次。如果一个进程已经调用了 execve(),它就不能再调用 fork() 或再次执行 execve()
  • 无返回值:execve() 成功执行时,原来的进程映像被新程序映像替换,原来的进程不再存在,因此无法返回任何值。如果在 execve() 执行之前有任何返回值,那么这个返回值是在 fork() 调用之后,由父进程获得的。

因此,在实际应用中,我们通常会先使用 fork() 创建一个子进程,然后在子进程中调用 execve() 执行新的程序。父进程在 fork() 之后会继续执行,并通过调用 wait(NULL) 来等待子进程结束。这样,父进程可以知道子进程已经成功执行了 execve(),并且可以继续执行其他任务或退出。

在多线程程序中,如果一个线程执行了 fork() 并尝试在子进程中执行 execve(),那么其他线程将继续执行,不受 fork()execve() 的影响。只有调用 fork() 的线程会进入子进程,而其他线程则继续在父进程中运行。

得到的运行结果:

image-20240313102827423

这篇关于Linux execve函数详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/804861

相关文章

Java中的getBytes()方法使用详解

《Java中的getBytes()方法使用详解》:本文主要介绍Java中getBytes()方法使用的相关资料,getBytes()方法有多个重载形式,可以根据需要指定字符集来进行转换,文中通过代... 目录前言一、常见重载形式二、示例代码三、getBytes(Charset charset)和getByt

Spring框架中@Lazy延迟加载原理和使用详解

《Spring框架中@Lazy延迟加载原理和使用详解》:本文主要介绍Spring框架中@Lazy延迟加载原理和使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、@Lazy延迟加载原理1.延迟加载原理1.1 @Lazy三种配置方法1.2 @Component

python+OpenCV反投影图像的实现示例详解

《python+OpenCV反投影图像的实现示例详解》:本文主要介绍python+OpenCV反投影图像的实现示例详解,本文通过实例代码图文并茂的形式给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录一、前言二、什么是反投影图像三、反投影图像的概念四、反向投影的工作原理一、利用反向投影backproj

Kotlin运算符重载函数及作用场景

《Kotlin运算符重载函数及作用场景》在Kotlin里,运算符重载函数允许为自定义类型重新定义现有的运算符(如+-…)行为,从而让自定义类型能像内置类型那样使用运算符,本文给大家介绍Kotlin运算... 目录基本语法作用场景类对象数据类型接口注意事项在 Kotlin 里,运算符重载函数允许为自定义类型重

嵌入式Linux之使用设备树驱动GPIO的实现方式

《嵌入式Linux之使用设备树驱动GPIO的实现方式》:本文主要介绍嵌入式Linux之使用设备树驱动GPIO的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、设备树配置1.1 添加 pinctrl 节点1.2 添加 LED 设备节点二、编写驱动程序2.1

嵌入式Linux驱动中的异步通知机制详解

《嵌入式Linux驱动中的异步通知机制详解》:本文主要介绍嵌入式Linux驱动中的异步通知机制,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、异步通知的核心概念1. 什么是异步通知2. 异步通知的关键组件二、异步通知的实现原理三、代码示例分析1. 设备结构

Linux搭建单机MySQL8.0.26版本的操作方法

《Linux搭建单机MySQL8.0.26版本的操作方法》:本文主要介绍Linux搭建单机MySQL8.0.26版本的操作方法,本文通过图文并茂的形式给大家讲解的非常详细,感兴趣的朋友一起看看吧... 目录概述环境信息数据库服务安装步骤下载前置依赖服务下载方式一:进入官网下载,并上传到宿主机中,适合离线环境

Python中Flask模板的使用与高级技巧详解

《Python中Flask模板的使用与高级技巧详解》在Web开发中,直接将HTML代码写在Python文件中会导致诸多问题,Flask内置了Jinja2模板引擎,完美解决了这些问题,下面我们就来看看F... 目录一、模板渲染基础1.1 为什么需要模板引擎1.2 第一个模板渲染示例1.3 模板渲染原理二、模板

Redis中6种缓存更新策略详解

《Redis中6种缓存更新策略详解》Redis作为一款高性能的内存数据库,已经成为缓存层的首选解决方案,然而,使用缓存时最大的挑战在于保证缓存数据与底层数据源的一致性,本文将介绍Redis中6种缓存更... 目录引言策略一:Cache-Aside(旁路缓存)策略工作原理代码示例优缺点分析适用场景策略二:Re

Java注解之超越Javadoc的元数据利器详解

《Java注解之超越Javadoc的元数据利器详解》本文将深入探讨Java注解的定义、类型、内置注解、自定义注解、保留策略、实际应用场景及最佳实践,无论是初学者还是资深开发者,都能通过本文了解如何利用... 目录什么是注解?注解的类型内置注编程解自定义注解注解的保留策略实际用例最佳实践总结在 Java 编程