news 2026/1/11 8:45:01

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/1/7 5:51:50

阅宝黄金获授《黄金以旧换新经营服务规范》团体标准起草单位,以专业之力助推行业规范化发展

阅宝黄金作为中国黄金协会常务理事单位和广东省黄金协会会长单位&#xff0c;凭借在行业内的专业影响力&#xff0c;积极参与《黄金以旧换新经营服务规范》团体标准的起草&#xff0c;获得《黄金以旧换新经营服务规范》团体标准起草单位授牌。阅宝黄金集团总经理王伟彬先生代表…

作者头像 李华
网站建设 2026/1/6 22:38:27

制造业老师傅的工艺经验,可通过国产CAD软件系统化传承

在制造车间里&#xff0c;工艺经验的传承一直是个现实难题。老师傅们多年的实践经验&#xff0c;往往停留在手写笔记或口头传授上。一旦老师傅退休&#xff0c;这些宝贵的经验很容易随之流失&#xff0c;新来的员工需要很长时间重新摸索&#xff0c;直接影响生产效率和产品质量…

作者头像 李华
网站建设 2026/1/1 17:56:56

PGModeler:让PostgreSQL数据库建模变得像搭积木一样简单

PGModeler&#xff1a;让PostgreSQL数据库建模变得像搭积木一样简单 【免费下载链接】pgmodeler Open-source data modeling tool designed for PostgreSQL. No more typing DDL commands. Let pgModeler do the work for you! 项目地址: https://gitcode.com/gh_mirrors/pg/…

作者头像 李华
网站建设 2026/1/1 20:38:52

游戏资源安全防护完整指南:从风险评估到系统化实施

游戏资源安全防护完整指南&#xff1a;从风险评估到系统化实施 【免费下载链接】cocos-engine Cocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-pe…

作者头像 李华
网站建设 2026/1/1 15:39:13

Tsuru租户隔离架构深度解析:构建企业级安全PaaS平台

Tsuru租户隔离架构深度解析&#xff1a;构建企业级安全PaaS平台 【免费下载链接】tsuru Open source and extensible Platform as a Service (PaaS). 项目地址: https://gitcode.com/gh_mirrors/ts/tsuru 在当今多云和容器化时代&#xff0c;租户隔离已成为企业级PaaS平…

作者头像 李华
网站建设 2026/1/10 17:54:05

C++结构体完全指南:从基础到高级应用

C结构体完全指南&#xff1a;从基础到高级应用 一、为什么需要结构体&#xff1f; 在C编程中&#xff0c;数组虽然可以存储多个元素&#xff0c;但所有元素的类型必须相同。当我们需要存储不同类型的数据时&#xff0c;比如篮球运动员的信息&#xff08;姓名、身高、体重、得分…

作者头像 李华