Linux|Linux系统的exec函数族浅浅解析

2024-06-02 15:20

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

exec 函数族是 Linux 和其他类 Unix 操作系统中的一组系统调用,用于在当前进程的上下文中执行新的程序。这些函数包括 execl, execle, execlp, execv, execve, execvp 和 execvpe 等。使用这些函数可以替换当前进程的地址空间,使其执行一个新的程序

基础背景

什么是exec函数族?

exec 函数族是一组系统调用,它们可以在当前进程的上下文中执行新的程序。这意味着新的程序会完全替换当前进程的地址空间,并从新程序的入口点开始执行。exec 函数族并不会创建新进程,而是用新的程序替换当前进程,因此新的程序会继承旧程序的进程 ID 和环境变量等信息。

这里是一个示例:

#include <unistd.h>
#include <stdio.h>int main() {char *args[] = {"/bin/ls", "-l", "/home", NULL}; // 要执行的程序及其参数printf("Before execvp\n");// 使用 execvp 替换当前进程if (execvp(args[0], args) == -1) {perror("execvp failed"); // 错误处理}printf("This will not be printed if execvp succeeds\n");return 0;
}
  1. execvp 函数调用替换了当前进程的地址空间,用新的程序(即 ls -l /home)替换当前进程。
  2. 如果 execvp 成功执行,当前进程的地址空间将被 ls 程序替换,因此 “This will not be printed if execvp succeeds” 这行代码不会被执行。
  3. 如果 execvp 失败,将会执行 perror 打印错误信息。

那么为什么使用 exec 函数族?

使用 exec 函数族的主要目的是启动一个新的程序,同时保持当前进程的属性(例如进程 ID)。他最典型的应用就是编写shell。这种方法在编写 shell 或其他需要启动外部程序的系统软件时特别有用。例如,在实现 shell 命令解释器时,exec 函数族可以用来执行用户输入的命令。

主要函数成员

  1. execl:
int execl(const char *path, const char *arg, ... /* (char *) NULL */);

使用指定路径执行新程序,并通过一系列可变参数传递给新程序。

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

与 execl 类似,但允许传递一个新的环境变量数组。

  1. execlp:
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);

类似于 execl,但可以在 PATH 环境变量指定的目录中搜索可执行文件。

  1. 重要----execv:
int execv(const char *path, char *const argv[]);

使用指定路径执行新程序,并通过数组传递参数。

  1. execve
int execve(const char *filename, char *const argv[], char *const envp[]);

类似于 execv,但允许传递一个新的环境变量数组。

  1. ⭐️execvp:
int execvp(const char *file, char *const argv[]);

类似于 execv,但可以在 PATH 环境变量指定的目录中搜索可执行文件

  1. execvpe:
int execvpe(const char *file, char *const argv[], char *const envp[]);

类似于 execve 和 execvp,但允许传递新的环境变量数组并搜索 PATH。

主要应用场景

1. 实现 Shell

Shell 通过 fork 一个子进程来处理用户命令。子进程可以使用 exec 函数族之一来执行具体的命令。例如,ls 命令可以通过 execvp(“ls”, args) 来执行。
这里和上面的举例类似

2. 服务器程序

在服务器编程中,exec 函数族常用于执行外部程序或脚本。例如,在处理 CGI(Common Gateway Interface)请求时,Web 服务器需要执行外部脚本来生成动态内容。exec 函数族可以在现有进程中执行这些外部程序,避免创建新的进程,提升效率。

简单的 CGI 处理

假设我们有一个简单的 HTTP 服务器,它需要处理 CGI 请求,并执行一个外部的 CGI 脚本。下面是一个基本的实现示例,在这个示例中,服务器监听端口 8080。当收到一个请求时,如果请求 URL/cgi-bin/script.cgi,服务器会 fork 一个子进程来执行 script.cgi 脚本。子进程使用 execv 函数执行脚本,脚本的输出会直接发送给客户端。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define PORT 8080void handle_request(int client_socket) {char buffer[1024];read(client_socket, buffer, sizeof(buffer) - 1);printf("Received request: %s\n", buffer);// 简单地处理 GET 请求if (strncmp(buffer, "GET /cgi-bin/script.cgi", 23) == 0) {pid_t pid = fork();if (pid == 0) {// 子进程执行 CGI 脚本char *args[] = {"script.cgi", NULL};execv("/path/to/cgi-bin/script.cgi", args);perror("execv"); // 如果 execv 返回,说明出现错误exit(EXIT_FAILURE);} else if (pid > 0) {wait(NULL); // 等待子进程结束} else {perror("fork");}}close(client_socket);
}int main() {int server_fd, client_fd;struct sockaddr_in address;int addrlen = sizeof(address);// 创建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 绑定端口address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听连接if (listen(server_fd, 3) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}printf("Server listening on port %d\n", PORT);// 接受客户端连接并处理请求while ((client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) >= 0) {handle_request(client_fd);}close(server_fd);return 0;
}

3. 编写守护进程(Daemon)

守护进程(Daemon)是一种在后台运行的服务进程,通常在系统启动时启动,并持续运行以提供服务。守护进程可能需要在运行过程中重新启动自己以应用配置更改或更新。这时可以使用 exec 函数族来替换当前进程,从而重新启动进程并应用新版本或新配置。

下面是一个守护进程的示例,它在接收到特定信号时重新启动自己,在这个示例中:

  1. 守护进程在启动时通过 fork 创建一个子进程并终止父进程,使其在后台运行。
  2. 守护进程设置了一个信号处理器来处理 SIGHUP 信号。当接收到 SIGHUP 信号时,调用 restart_daemon 函数。
  3. restart_daemon 函数使用 execv 函数重新启动守护进程自身。这样可以在不改变进程 ID 的情况下重新启动守护进程,并应用新的配置或更新。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>void restart_daemon() {char *args[] = {"mydaemon", NULL};execv("/path/to/mydaemon", args);perror("execv");exit(EXIT_FAILURE);
}void handle_signal(int sig) {if (sig == SIGHUP) {restart_daemon();}
}int main() {pid_t pid, sid;// 创建子进程,终止父进程pid = fork();if (pid < 0) {exit(EXIT_FAILURE);}if (pid > 0) {exit(EXIT_SUCCESS);}// 设置文件权限掩码umask(0);// 创建新会话sid = setsid();if (sid < 0) {exit(EXIT_FAILURE);}// 改变工作目录if ((chdir("/")) < 0) {exit(EXIT_FAILURE);}// 关闭标准文件描述符close(STDIN_FILENO);close(STDOUT_FILENO);close(STDERR_FILENO);// 打开日志文件int log_fd = open("/var/log/mydaemon.log", O_RDWR | O_CREAT, 0600);if (log_fd < 0) {exit(EXIT_FAILURE);}dup2(log_fd, STDOUT_FILENO);dup2(log_fd, STDERR_FILENO);// 设置信号处理器signal(SIGHUP, handle_signal);// 守护进程的主循环while (1) {// 执行守护进程的任务sleep(10);}close(log_fd);return 0;
}

这篇关于Linux|Linux系统的exec函数族浅浅解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

深度解析Python中递归下降解析器的原理与实现

《深度解析Python中递归下降解析器的原理与实现》在编译器设计、配置文件处理和数据转换领域,递归下降解析器是最常用且最直观的解析技术,本文将详细介绍递归下降解析器的原理与实现,感兴趣的小伙伴可以跟随... 目录引言:解析器的核心价值一、递归下降解析器基础1.1 核心概念解析1.2 基本架构二、简单算术表达

深度解析Java @Serial 注解及常见错误案例

《深度解析Java@Serial注解及常见错误案例》Java14引入@Serial注解,用于编译时校验序列化成员,替代传统方式解决运行时错误,适用于Serializable类的方法/字段,需注意签... 目录Java @Serial 注解深度解析1. 注解本质2. 核心作用(1) 主要用途(2) 适用位置3

JWT + 拦截器实现无状态登录系统

《JWT+拦截器实现无状态登录系统》JWT(JSONWebToken)提供了一种无状态的解决方案:用户登录后,服务器返回一个Token,后续请求携带该Token即可完成身份验证,无需服务器存储会话... 目录✅ 引言 一、JWT 是什么? 二、技术选型 三、项目结构 四、核心代码实现4.1 添加依赖(pom

Java MCP 的鉴权深度解析

《JavaMCP的鉴权深度解析》文章介绍JavaMCP鉴权的实现方式,指出客户端可通过queryString、header或env传递鉴权信息,服务器端支持工具单独鉴权、过滤器集中鉴权及启动时鉴权... 目录一、MCP Client 侧(负责传递,比较简单)(1)常见的 mcpServers json 配置

从原理到实战解析Java Stream 的并行流性能优化

《从原理到实战解析JavaStream的并行流性能优化》本文给大家介绍JavaStream的并行流性能优化:从原理到实战的全攻略,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的... 目录一、并行流的核心原理与适用场景二、性能优化的核心策略1. 合理设置并行度:打破默认阈值2. 避免装箱