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生产者,消费者问题

pthread_cond_wait() :用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。 pthread_cond_wait() 必须与pthread_mutex 配套使用。pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread

Linux 安装、配置Tomcat 的HTTPS

Linux 安装 、配置Tomcat的HTTPS 安装Tomcat 这里选择的是 tomcat 10.X ,需要Java 11及更高版本 Binary Distributions ->Core->选择 tar.gz包 下载、上传到内网服务器 /opt 目录tar -xzf 解压将解压的根目录改名为 tomat-10 并移动到 /opt 下, 形成个人习惯的路径 /opt/tomcat-10

RedHat运维-Linux文本操作基础-AWK进阶

你不用整理,跟着敲一遍,有个印象,然后把它保存到本地,以后要用再去看,如果有了新东西,你自个再添加。这是我参考牛客上的shell编程专项题,只不过换成了问答的方式而已。不用背,就算是我自己亲自敲,我现在好多也记不住。 1. 输出nowcoder.txt文件第5行的内容 2. 输出nowcoder.txt文件第6行的内容 3. 输出nowcoder.txt文件第7行的内容 4. 输出nowcode

【Linux进阶】UNIX体系结构分解——操作系统,内核,shell

1.什么是操作系统? 从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。我们通常将这种软件称为内核(kerel),因为它相对较小,而且位于环境的核心。  从广义上说,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并使计算机具有自己的特生。这里所说的其他软件包括系统实用程序(system utility)、应用程序、shell以及公用函数库等

解析 XML 和 INI

XML 1.TinyXML库 TinyXML是一个C++的XML解析库  使用介绍: https://www.cnblogs.com/mythou/archive/2011/11/27/2265169.html    使用的时候,只要把 tinyxml.h、tinystr.h、tinystr.cpp、tinyxml.cpp、tinyxmlerror.cpp、tinyxmlparser.

【操作系统】信号Signal超详解|捕捉函数

🔥博客主页: 我要成为C++领域大神🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞👍收藏⭐评论✍️ 本博客致力于知识分享,与更多的人进行学习交流 ​ 如何触发信号 信号是Linux下的经典技术,一般操作系统利用信号杀死违规进程,典型进程干预手段,信号除了杀死进程外也可以挂起进程 kill -l 查看系统支持的信号

通信系统网络架构_2.广域网网络架构

1.概述          通俗来讲,广域网是将分布于相比局域网络更广区域的计算机设备联接起来的网络。广域网由通信子网于资源子网组成。通信子网可以利用公用分组交换网、卫星通信网和无线分组交换网构建,将分布在不同地区的局域网或计算机系统互连起来,实现资源子网的共享。 2.网络组成          广域网属于多级网络,通常由骨干网、分布网、接入网组成。在网络规模较小时,可仅由骨干网和接入网组成

Windows/macOS/Linux 安装 Redis 和 Redis Desktop Manager 可视化工具

本文所有安装都在macOS High Sierra 10.13.4进行,Windows安装相对容易些,Linux安装与macOS类似,文中会做区分讲解 1. Redis安装 1.下载Redis https://redis.io/download 把下载的源码更名为redis-4.0.9-source,我喜欢跟maven、Tomcat放在一起,就放到/Users/zhan/Documents

java中查看函数运行时间和cpu运行时间

android开发调查性能问题中有一个现象,函数的运行时间远低于cpu执行时间,因为函数运行期间线程可能包含等待操作。native层可以查看实际的cpu执行时间和函数执行时间。在java中如何实现? 借助AI得到了答案 import java.lang.management.ManagementFactory;import java.lang.management.Threa

Linux系统稳定性的奥秘:探究其背后的机制与哲学

在计算机操作系统的世界里,Linux以其卓越的稳定性和可靠性著称,成为服务器、嵌入式系统乃至个人电脑用户的首选。那么,是什么造就了Linux如此之高的稳定性呢?本文将深入解析Linux系统稳定性的几个关键因素,揭示其背后的技术哲学与实践。 1. 开源协作的力量Linux是一个开源项目,意味着任何人都可以查看、修改和贡献其源代码。这种开放性吸引了全球成千上万的开发者参与到内核的维护与优化中,形成了