news 2026/3/16 11:03:27

Linux 进程控制核心:exec 族函数、waitpid 与 system 全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 进程控制核心:exec 族函数、waitpid 与 system 全解析

一、exec 族函数:进程的 “程序替换” 神器

1.1 核心功能

exec 族函数的核心作用是替换当前进程的代码段、数据段、堆、栈—— 执行 exec 后,进程的 PID 不变,但运行的程序会被完全替换为新的可执行文件;若 exec 执行成功,原进程的后续代码不会执行;若执行失败,才会继续执行原进程代码。

exec 族函数通常与 fork 搭配使用:父进程 fork 创建子进程,子进程执行 exec 替换为目标程序,既保证父进程不被替换,又能通过子进程执行任意可执行文件。

1.2 内存视角的变化

阶段进程内存状态
exec 执行前进程运行原程序的代码段、数据段、堆、栈
exec 执行后原内存区域被新程序覆盖,仅保留 PID、文件描述符等内核态信息
新程序执行结束整个进程终止(无需返回原程序)

1.3 exec 族 4 个核心函数

exec 族函数有多个变体,核心差异在于参数形式程序路径查找方式,以下是最常用的 4 个函数:

函数原型核心特点参数说明
int execl(const char *path, const char *arg, ...);l=list(参数列表),需指定程序绝对 / 相对路径path:程序路径 + 文件名(如/bin/ls);arg:参数列表,以 NULL 结尾(如execl("/bin/ls", "ls", "-l", NULL)
int execlp(const char *file, const char *arg, ...);l=list,p=PATH(自动从环境变量 PATH 查找程序)file:程序名(如ls),无需写路径;arg:参数列表,以 NULL 结尾(如execlp("ls", "ls", "-l", NULL)
int execv(const char *path, char *const argv[]);v=vector(数组),需指定程序路径path:程序路径 + 文件名;argv:参数数组,最后一个元素为 NULL(如char *argv[] = {"ls", "-l", NULL}; execv("/bin/ls", argv)
int execvp(const char *file, char *const argv[]);v=vector,p=PATH(自动查找程序)file:程序名;argv:参数数组,以 NULL 结尾(如char *argv[] = {"ls", "-l", NULL}; execvp("ls", argv)
共性说明
  • 返回值:仅执行失败时返回 - 1(成功则无返回);
  • 参数规则:第一个参数(arg/argv [0])通常是程序名(与 file/path 一致),后续为程序的运行参数;
  • 路径规则:若要执行自定义可执行程序(非系统命令),4 个函数的第一个参数都需填写路径 + 文件名(如./myprog)。
示例:execlp 执行 ls 命令

c

运行

#include <unistd.h> #include <stdio.h> int main() { // 子进程执行ls -l,自动从PATH查找ls if (fork() == 0) { execlp("ls", "ls", "-l", NULL); perror("execlp failed"); // 仅exec失败时执行 return 1; } return 0; }

二、waitpid:子进程资源的 “回收者”

当子进程终止(正常 / 异常),其用户空间内存会释放,但内核中的 PCB(进程控制块)需父进程主动回收,否则会变成僵尸进程。waitpid 是回收子进程资源的核心函数,也是 wait 的增强版。

2.1 函数原型与参数

c

运行

#include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options);
参数取值与含义
pid-1:回收所有子进程;>0:回收指定 PID 的子进程;0:回收同组的子进程;<-1:回收指定进程组的子进程
status存储子进程退出状态(不关心则传 NULL);可通过宏解析状态(见 2.3)
options0:阻塞模式(父进程等待子进程终止);WNOHANG:非阻塞模式(无子进程终止则立即返回)

2.2 返回值说明

返回值含义
>0成功回收的子进程 PID
0WNOHANG 模式下,无子进程终止(需再次尝试回收)
-1回收失败(如无待回收的子进程、系统错误)

2.3 退出状态解析宏

通过以下宏可解析 status 参数,判断子进程终止方式:

功能配套使用
WIFEXITED(status)判断是否正常终止(return/exit/_exit)是:WEXITSTATUS (status) 获取退出码
WEXITSTATUS(status)获取正常终止的退出码(0-255)仅 WIFEXITED 为真时有效
WIFSIGNALED(status)判断是否被信号异常终止(如 kill -9)是:WTERMSIG (status) 获取信号编号
WTERMSIG(status)获取终止子进程的信号编号仅 WIFSIGNALED 为真时有效

2.4 核心用法示例

示例 1:阻塞回收指定子进程

c

运行

#include <sys/wait.h> #include <unistd.h> #include <stdio.h> int main() { pid_t pid = fork(); if (pid == 0) { execlp("ls", "ls", NULL); exit(1); } // 阻塞等待pid对应的子进程终止 int status; waitpid(pid, &status, 0); if (WIFEXITED(status)) { printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status)); } return 0; }
示例 2:非阻塞回收所有子进程

c

运行

#include <sys/wait.h> #include <unistd.h> #include <stdio.h> int main() { // 创建多个子进程... while (1) { pid_t ret = waitpid(-1, NULL, WNOHANG); if (ret == 0) { printf("暂无子进程退出,稍后重试\n"); usleep(100000); // 100ms后重试 } else if (ret == -1) { printf("所有子进程已回收\n"); break; } else { printf("回收子进程PID:%d\n", ret); } } return 0; }
等价关系

waitpid(-1, status, 0)完全等价于wait(status)—— 阻塞回收任意子进程。

三、system 函数:fork+exec 的 “便捷封装”

system 函数是对 fork+exec+waitpid 的封装,可直接执行 shell 命令,无需手动处理进程创建和回收。

3.1 函数原型与功能

c

运行

#include <stdlib.h> int system(const char *command);
  • 功能:执行指定的 shell 命令(如system("ls -l"));
  • 实现逻辑:
    1. fork 创建子进程;
    2. 子进程执行 exec 调用 shell(如 /bin/sh)执行 command;
    3. 父进程 waitpid 等待子进程终止。

3.2 返回值

  • -1:fork/exec 失败;
  • 其他值:子进程的退出状态(可通过 waitpid 的宏解析)。

3.3 使用限制

system 执行的命令无法修改父进程状态(如 cd 命令)—— 因为命令在子进程中执行,子进程的目录切换、环境变量修改等操作不会影响父进程。

适合场景:执行信息输出、文件操作等无状态修改的命令(如 ls、cp、cat);不适合场景:需要修改父进程状态的操作(如 cd、export)。

3.4 示例:system 执行 cp 命令

c

运行

#include <stdlib.h> int main() { // 执行cp 1.txt 2.txt int ret = system("cp 1.txt 2.txt"); if (ret == -1) { perror("system failed"); } return 0; }

四、工作路径控制:getcwd 与 chdir

在 Shell、进程管理场景中,获取 / 修改当前工作路径是高频操作,核心依赖 getcwd 和 chdir 函数。

4.1 获取当前工作路径:getcwd

c

运行

#include <unistd.h> char *getcwd(char *buf, size_t size);
  • 功能:将当前工作目录的绝对路径存入 buf;
  • 参数:
    • buf:存储路径的字符数组;
    • size:buf 的最大长度(避免越界);
  • 返回值:成功返回 buf 指针,失败返回 NULL。
示例:打印当前工作路径

c

运行

#include <unistd.h> #include <stdio.h> int main() { char buf[512]; if (getcwd(buf, sizeof(buf)) != NULL) { printf("当前工作路径:%s\n", buf); } else { perror("getcwd failed"); } return 0; }

4.2 切换工作路径:chdir

c

运行

#include <unistd.h> int chdir(const char *path);
  • 功能:修改当前进程的工作路径;
  • 参数:path 为目标路径(绝对 / 相对路径);
  • 返回值:成功返回 0,失败返回 - 1。
示例:切换到 /tmp 目录

c

运行

#include <unistd.h> #include <stdio.h> int main() { if (chdir("/tmp") == 0) { printf("切换到/tmp成功\n"); // 验证:打印新路径 char buf[512]; getcwd(buf, sizeof(buf)); printf("新工作路径:%s\n", buf); } else { perror("chdir failed"); } return 0; }

关键注意点

chdir 仅修改当前进程的工作路径:

  • 若在子进程中执行 chdir,父进程的路径不会变化;
  • Shell 的 cd 命令必须在主进程执行(而非子进程),否则路径切换不生效。

五、实战:fork+exec+waitpid 实现 MiniShell 核心

结合以上知识点,实现支持 cd/ls/cp/cat 的 MiniShell 核心逻辑(无 system,纯 fork+exec):

c

运行

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/wait.h> #define MAX_LINE 1024 #define MAX_ARGS 10 int parse_cmd(char *line, char **argv) { int argc = 0; char *token = strtok(line, " \n"); while (token != NULL && argc < MAX_ARGS-1) { argv[argc++] = token; token = strtok(NULL, " \n"); } argv[argc] = NULL; return argc; } void exec_cmd(char **argv) { pid_t pid = fork(); if (pid < 0) { perror("fork failed"); return; } if (pid == 0) { execvp(argv[0], argv); perror("command not found"); exit(1); } else { waitpid(pid, NULL, 0); } } int main() { char line[MAX_LINE], *argv[MAX_ARGS], cwd[512]; while (1) { // 打印带当前路径的提示符 getcwd(cwd, sizeof(cwd)); printf("%s > ", cwd); fflush(stdout); if (fgets(line, MAX_LINE, stdin) == NULL) break; int argc = parse_cmd(line, argv); if (argc == 0) continue; // 内置命令:exit if (!strcmp(argv[0], "exit")) exit(0); // 内置命令:cd(主进程执行) if (!strcmp(argv[0], "cd")) { char *dir = argc>1 ? argv[1] : getenv("HOME"); if (chdir(dir) == -1) perror("cd failed"); continue; } // 外部命令:ls/cp/cat(fork+exec) exec_cmd(argv); } return 0; }

六、核心总结

  1. exec 族:程序替换核心,fork+exec 是 Linux 进程编程的经典组合,exec 成功则进程被替换,失败才返回;
  2. waitpid:子进程资源回收的唯一方式,阻塞 / 非阻塞模式适配不同场景,避免僵尸进程;
  3. system:便捷但受限,无法修改父进程状态,底层是 fork+exec+waitpid;
  4. 路径控制:getcwd 获取当前路径,chdir 修改路径(仅影响当前进程);
  5. 核心原则:需修改父进程状态的操作(如 cd)必须在主进程执行,无需修改状态的命令(如 ls/cp)可通过 fork+exec 在子进程执行。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/15 9:33:50

AdGuard Home配置优化指南:构建高效DNS服务器的网络优化实践

AdGuard Home作为一款功能强大的网络级DNS服务器&#xff0c;能够有效拦截广告和追踪程序&#xff0c;提升网络体验。本文将从五个核心配置维度出发&#xff0c;提供详细的实践指南和问题规避策略&#xff0c;帮助用户构建稳定高效的DNS服务环境。 【免费下载链接】AdGuardHome…

作者头像 李华
网站建设 2026/3/14 15:59:15

R语言在气象数据分析中的应用(季节性分解核心技术大公开)

第一章&#xff1a;R语言在气象数据分析中的应用概述R语言作为一种专为统计计算与数据可视化设计的编程环境&#xff0c;在气象科学领域展现出强大的应用潜力。其丰富的扩展包生态和灵活的数据处理能力&#xff0c;使其成为分析时间序列气象数据、空间气候模型输出以及极端天气…

作者头像 李华
网站建设 2026/3/15 9:11:55

Wechaty微信机器人开发完全指南:从零到一的智能消息处理实战

Wechaty微信机器人开发完全指南&#xff1a;从零到一的智能消息处理实战 【免费下载链接】wechaty 项目地址: https://gitcode.com/gh_mirrors/wec/wechaty 在当今数字化时代&#xff0c;微信作为中国最大的社交平台&#xff0c;其自动化处理需求日益增长。Wechaty作为…

作者头像 李华
网站建设 2026/3/15 9:12:07

应收账款周转238天:仓储机器人企业的钱,都被谁“压“住了?

导语大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。新书《智能物流系统构成与技术实践》新书《智能仓储项目出海-英语手册》新书《智能仓储自动化项目&#xff1a;避坑手册》新书《智能仓储项目实施指南&#xff1a;甲方必读》设备卖出去…

作者头像 李华
网站建设 2026/3/15 8:08:19

12、深入了解Samba:文件系统差异与权限管理

深入了解Samba:文件系统差异与权限管理 1. 文件系统差异处理 在使用Samba时,需要解决Unix和非Unix文件系统之间的差异问题,这涉及到符号链接、隐藏文件、点文件以及文件权限等方面。 1.1 隐藏和禁止访问文件 隐藏点文件( hide dot files ) :这是一个布尔选项。当设…

作者头像 李华