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

相关文章

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

Linux内核参数配置与验证详细指南

《Linux内核参数配置与验证详细指南》在Linux系统运维和性能优化中,内核参数(sysctl)的配置至关重要,本文主要来聊聊如何配置与验证这些Linux内核参数,希望对大家有一定的帮助... 目录1. 引言2. 内核参数的作用3. 如何设置内核参数3.1 临时设置(重启失效)3.2 永久设置(重启仍生效

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

Java字符串处理全解析(String、StringBuilder与StringBuffer)

《Java字符串处理全解析(String、StringBuilder与StringBuffer)》:本文主要介绍Java字符串处理全解析(String、StringBuilder与StringBu... 目录Java字符串处理全解析:String、StringBuilder与StringBuffer一、St

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

C#中async await异步关键字用法和异步的底层原理全解析

《C#中asyncawait异步关键字用法和异步的底层原理全解析》:本文主要介绍C#中asyncawait异步关键字用法和异步的底层原理全解析,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录C#异步编程一、异步编程基础二、异步方法的工作原理三、代码示例四、编译后的底层实现五、总结C#异步编程

kali linux 无法登录root的问题及解决方法

《kalilinux无法登录root的问题及解决方法》:本文主要介绍kalilinux无法登录root的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录kali linux 无法登录root1、问题描述1.1、本地登录root1.2、ssh远程登录root2、

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

MySQL高级查询之JOIN、子查询、窗口函数实际案例

《MySQL高级查询之JOIN、子查询、窗口函数实际案例》:本文主要介绍MySQL高级查询之JOIN、子查询、窗口函数实际案例的相关资料,JOIN用于多表关联查询,子查询用于数据筛选和过滤,窗口函... 目录前言1. JOIN(连接查询)1.1 内连接(INNER JOIN)1.2 左连接(LEFT JOI