当前位置: 首页 > article >正文

《Linux系统编程篇》exec族函数——基础篇

文章目录

  • 引言
    • 探索 `exec()` 系列函数:Linux 进程替换
      • 1. 什么是 `exec()` 系列函数?
      • 2. `exec()` 系列函数的函数原型
      • 3. `exec()` 系列函数的使用
      • 4. `exec()` 系列函数的工作原理
      • 5. `exec()` 系列函数的常见用法
      • 6. `exec()` 与 `fork()` 的配合
      • 7. 常见的错误与注意事项
  • 结论

当你知道越来越多的时候,随着你的知识量的增加,所而做的事以及解决问题的方法就越多,越丰富!

——家驹

引言

《Linux系统编程篇》——基础篇首页传送门

当我们介绍完fork之后,你会发现虽然我可以同时跑俩个程序,但是还是太过于局限了,而且细心的学员们发现,进程直接是完全不互通的,好像fork之后什么也做不了,是的,只学完fork就是这样的,所以我们再来介绍新的知识。exec族函数。

探索 exec() 系列函数:Linux 进程替换

在 Linux 中,exec() 系列函数是用于进程的重载(或替换)的系统调用。与 fork() 不同,exec() 不会创建新的进程,而是将当前进程的代码和数据替换为一个新的程序,从而实现进程功能的“转变”。exec() 在构建多任务系统、实现子进程执行新任务中非常重要。本文将介绍 exec() 系列函数的使用方式和常见应用场景。


1. 什么是 exec() 系列函数?

exec() 系列函数提供了在当前进程上下文中执行其他程序的能力。当调用 exec() 函数时,当前进程的代码和内存会被新程序替换,但进程 ID(PID)保持不变。这意味着在进程级别,它仍然是原进程,但其任务和行为已经完全改变。

当我的程序调用exec的时候,他就不在执行exec以下的代码了,而是去执行exec系列函数指定程序,可以理解为进程替换。

生活中,比如我今天想安安稳稳敲一天的代码,这本来是我今天应该执行的程序,但是我的大脑又想干其他的事情,此时你完全可以不敲代码,改变我今天的行程,当我决定改变日程的时候,其实就相当于在linux系统中调用了exec(),比如我不想敲代码,我想打一天游戏,那么我可以这么做execl(打一天游戏)。是的,就是这样,那么我也不用在思考敲代码的事情了,专心打游戏就可以了。

我们就会想,既然fork出来一个崭新子进程,我可以让这个子进程,通过调用exec系列去执行其他程序,而不是局限和父进程执行一样的东西了。

exec() 系列函数主要包括以下几种变体:

  1. execl():适用于已知固定参数的情况,参数通过变长列表传递。
  2. execv():适用于参数数量动态的情况,参数以数组形式传递。
  3. execlp()execvp():与前两者类似,但会在 PATH 环境变量指定的路径中寻找可执行文件。
  4. execle()execve():允许传递特定的环境变量。

2. exec() 系列函数的函数原型

以下是 exec() 系列函数的原型及其含义:

// 常见的 exec 系列函数
int execl(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]);

参数说明:

  • path:要执行的程序路径(绝对路径或相对路径)。
  • arg:程序的参数列表(arg[0] 通常是程序的名称)。
  • argv:参数数组,最后一个元素需为 NULL
  • envp:环境变量数组,NULL 表示使用当前环境变量。

3. exec() 系列函数的使用

不同的 exec() 变体在调用时各有特点。下面的例子不仅仅能调用系统的命令(系统命令本质也是程序代码编写而来),可以调用我们的其它已经写好的代码。

以下是几种常见的用法:

(1)execl()

  • execl() 需要将参数以变长列表的形式传递,适合参数固定的情况。
#include <unistd.h>
#include <stdio.h>

int main() {
    printf("Running execl...\n");
    execl("/bin/ls", "ls", "-l", "/home", NULL); // 指定路径执行 ls 命令
    perror("execl failed");
    return 1;
}

执行上面的代码就发现,我好像发命令ls -l 函数?,是的做一层封装,就是你的ls函数,通过argc,argv来配合,这就由学员自由发挥了。

(2)execv()

  • execv() 适合参数数量动态的情况,参数通过数组传递。
#include <unistd.h>
#include <stdio.h>

int main() {
    char *args[] = {"ls", "-l", "/home", NULL};
    printf("Running execv...\n");
    execv("/bin/ls", args); // 执行 ls 命令
    perror("execv failed");
    return 1;
}

(3)execlp()execvp()

  • execlp()execvp() 会在环境变量 PATH 中查找可执行文件。
  • 如果不知道命令的完整路径(例如 ls),可以使用 execlp()execvp()
#include <unistd.h>
#include <stdio.h>

int main() {
    printf("Running execlp...\n");
    execlp("ls", "ls", "-l", "/home", NULL); // 使用 PATH 查找 ls 命令
    perror("execlp failed");
    return 1;
}

(4)execle()execve()

  • execle()execve() 可以传入特定的环境变量,适合在新环境下运行程序。
#include <unistd.h>
#include <stdio.h>

int main() {
    char *args[] = {"ls", "-l", "/home", NULL};
    char *env[] = {"PATH=/usr/bin:/bin", "USER=guest", NULL};

    printf("Running execve...\n");
    execve("/bin/ls", args, env); // 在指定环境下执行 ls 命令
    perror("execve failed");
    return 1;
}

4. exec() 系列函数的工作原理

调用 exec() 系列函数后,当前进程会被新程序替换:

  1. 替换执行:当前进程的代码段、数据段和堆栈被清空,加载新程序。
  2. PID 不变:虽然代码被替换,但 PID 保持不变。因此,它仍然被认为是原来的进程。
  3. 关闭不再使用的文件描述符:默认情况下,父进程中的打开文件描述符会被继承。如果不需要,可以设置 FD_CLOEXEC 标志来关闭文件描述符。

5. exec() 系列函数的常见用法

  • 执行外部程序:常用于在 shell 或父进程中调用外部程序。
  • 多进程程序的子进程初始化:与 fork() 配合使用,通过 fork() 创建子进程,然后使用 exec() 在子进程中执行不同的任务。
  • 服务器编程:许多服务器会使用 fork()+exec() 来处理每个客户端请求,以便并行执行不同的任务。

6. exec()fork() 的配合

fork() 用于创建新进程,而 exec() 用于执行新任务。这种组合在 shell 和服务器编程中非常常见。以下示例展示了一个父进程通过 fork() 创建子进程,并在子进程中调用 exec() 执行其他程序的过程:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork failed");
        return 1;
    } else if (pid == 0) { // 子进程
        printf("In child process...\n");
        execlp("ls", "ls", "-l", NULL); // 子进程执行 ls 命令
        perror("exec failed");
        return 1;
    } else { // 父进程
        wait(NULL); // 等待子进程结束
        printf("Child process finished.\n");
    }
    return 0;
}

在这个例子中,父进程创建子进程,子进程在调用 exec() 后执行 ls 命令。exec() 执行成功后,子进程的代码被替换,继续执行新任务。父进程则通过 wait() 等待子进程结束。

7. 常见的错误与注意事项

  1. exec() 执行失败:如果文件路径错误或权限不足,exec() 调用将失败,并返回 -1。
  2. 未结束的父进程:在 fork() 后不使用 exec() 会导致子进程复制了父进程的代码,可能引起资源浪费。
  3. 文件描述符继承exec() 调用后,父进程的文件描述符通常会被继承。使用 FD_CLOEXEC 标志可以避免此问题。

结论

exec() 系列函数为进程提供了执行新程序的能力,允许在进程上下文中运行不同的任务。它与 fork() 配合使用,为 Linux 多任务处理提供了灵活性。理解和使用 exec() 系列函数有助于构建高效的多任务应用,提高系统性能。在实际应用中,使用 exec() 系列函数时务必注意文件路径、参数格式及环境变量的正确传递,以确保程序运行的稳定性。


http://www.kler.cn/a/371966.html

相关文章:

  • Python 网络爬虫快速入门
  • 基于LangChain构建安全Agent应用实践(含代码)
  • SpringMVC执行流程(视图阶段JSP、前后端分离阶段)、面试题
  • 【Linux】文件切割排序 cut sort
  • 无人机避障——使用三维PCD点云生成的2D栅格地图PGM做路径规划
  • Java AQS 目录
  • _csv.Error: field larger than field limit (131072)
  • AI自动评论插件V1.3 WordPress插件 自动化评论插件
  • css之loading旋转加载
  • 大数据新视界 -- 大数据大厂之大数据和增强现实(AR)结合:创造沉浸式数据体验
  • NVR设备ONVIF接入平台EasyCVR视频融合平台智慧小区视频监控系统建设方案
  • java的Annotation使用
  • MYSQL全局锁、标级锁、行级锁
  • 房屋租赁系统
  • Linux 如何精准排除特定文件并批量替换字符串
  • rabbitmq自学总结
  • net start mysql 启动mysql服务,发生系统错误,拒绝访问
  • 浅谈站点可靠性工程之SRE
  • 【10天速通Navigation2】(三) :Cartographer建图算法配置:从仿真到实车,从原理到实现
  • 研二了,该想想做啥呢?
  • 【算法系列-二叉树】层序遍历
  • 【2024|滑坡数据集论文解读1】CAS滑坡数据集:用于深度学习滑坡检测的大规模多传感器数据集
  • Scala 特质(Traits)与类继承 #scala #Scala #Scala继承
  • Mac程序坞窗口预览的方法来了
  • lego-loam featureAssociation 源码注释(七)
  • 使用 Kafka 和 MinIO 实现人工智能数据工作流