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使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

Python itertools中accumulate函数用法及使用运用详细讲解

《Pythonitertools中accumulate函数用法及使用运用详细讲解》:本文主要介绍Python的itertools库中的accumulate函数,该函数可以计算累积和或通过指定函数... 目录1.1前言:1.2定义:1.3衍生用法:1.3Leetcode的实际运用:总结 1.1前言:本文将详

在不同系统间迁移Python程序的方法与教程

《在不同系统间迁移Python程序的方法与教程》本文介绍了几种将Windows上编写的Python程序迁移到Linux服务器上的方法,包括使用虚拟环境和依赖冻结、容器化技术(如Docker)、使用An... 目录使用虚拟环境和依赖冻结1. 创建虚拟环境2. 冻结依赖使用容器化技术(如 docker)1. 创

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

轻松上手MYSQL之JSON函数实现高效数据查询与操作

《轻松上手MYSQL之JSON函数实现高效数据查询与操作》:本文主要介绍轻松上手MYSQL之JSON函数实现高效数据查询与操作的相关资料,MySQL提供了多个JSON函数,用于处理和查询JSON数... 目录一、jsON_EXTRACT 提取指定数据二、JSON_UNQUOTE 取消双引号三、JSON_KE

MySQL数据库函数之JSON_EXTRACT示例代码

《MySQL数据库函数之JSON_EXTRACT示例代码》:本文主要介绍MySQL数据库函数之JSON_EXTRACT的相关资料,JSON_EXTRACT()函数用于从JSON文档中提取值,支持对... 目录前言基本语法路径表达式示例示例 1: 提取简单值示例 2: 提取嵌套值示例 3: 提取数组中的值注意

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型