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

《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.mrgr.cn/news/61994.html

相关文章:

  • 含泪总结东南大学国际顶会报销流程
  • ffmpeg视频滤镜:膨胀操作-dilation
  • WPF拖拽交互全攻略及实现自定义拖拽控件及数据交换技巧解析
  • 人工智能技术在冷链物流行业的应用前景如何?
  • 深入计算机语言之C++:类与对象(中)
  • JS | CommonJS、AMD、CMD、ES6-Module、UMD五种JS模块化规范
  • MATLAB——入门知识
  • Vue3 学习笔记(十三)Vue组件详解
  • Windows高级技巧:轻松实现多进程窗口的连接与管理
  • 轻松实现金蝶与旺店通数据无缝对接的完整解决方案
  • Linux文件系统_inode
  • 兽音译器的编码原理
  • 真香!Python十大文件操作整理,收藏起来以后有用!!
  • 为什么不建议使用黑帽SEO手法?
  • 阿里云VPC机器如何访问公网
  • 【总目录】
  • 浏览器指纹:了解这个神秘的技术
  • node学习记录-process
  • 【flink】之kafka到kafka
  • Flask
  • Python 中 jieba 模块详解
  • Frida使用
  • ✨云桥计划✨
  • 最小均方估计贝叶斯估计
  • 《解锁思维潜能:高效思考的八大模型》
  • @Configuration+@Bean 和 @Component 的区别