一、 进程程序替换:exec 家族
如果我们想让子进程去执行一个全新的程序(比如系统的ls),而不是执行父进程的代码,就需要用到exec族函数。
它的工作原理是:加载新程序到当前进程的地址空间中,完全覆盖原有的代码和数据,进程 PID 保持不变。
特性:如果exec执行成功,它没有返回值(代码被覆盖了);只有失败才返回-1。
二、 exec 命名规律
l(list):命令行参数是列表形式(一个个写出来,以NULL结尾)。v(vector):参数是一个数组。p(path):带 p 的(如execlp),自动去PATH环境变量里找程序,不用写绝对路径。e(env):允许传入自定义的环境变量。
三、 实战:手写微型 Shell
天天用的 Bash 终端,底层的核心逻辑是什么?就是一个死循环:
打印提示符,获取用户输入。
解析输入(把字符串按空格切分成命令和参数数组)。
调用
fork()创建子进程。子进程调用
execvp()进行程序替换,执行命令。父进程调用
wait()阻塞等待子进程结束,防止僵尸进程。循环往复。
四、 代码演示:exec 替换测试
printf("变身前的进程: %d\n", getpid()); // 替换执行 ls -l 命令 execl("/usr/bin/ls", "ls", "-l", NULL); perror("execl error"); // 如果能运行到这里,说明替换失败总结
进程替换是 Shell 原理的基石。结合了fork、exec、wait和字符串处理,我们就能在 Linux 系统中实现一个最基础的自定义命令解释器。
自我实战操作
实战 1:体验一次“灵魂出窍”(exec 替换)
cd ~/linux_study/proc_test vim exec_test.c(输入以下代码)
#include <stdio.h> #include <unistd.h> int main() { printf("我是原来的程序[%d],接下来我要变身了...\n", getpid()); // 用 execl 替换成系统的 ls 命令 // 参数1: 程序的绝对路径 // 参数2: 命令名称 // 参数3: 命令参数 (比如 -l) // 参数4: 必须以 NULL 结尾 execl("/usr/bin/ls", "ls", "-l", NULL); // 如果代码走到这里,说明 execl 执行失败了 perror("execl error"); return 1; }编译并运行:
gcc exec_test.c -o exec_test ./exec_test👉现象:会看到打印出“我是原来的程序...”,紧接着打印出了当前目录的详细列表。原来的程序“变”成了ls。
实战 2:手写一个“微型 Shell”
这其实是文档里那个长篇大论的简化版,把核心逻辑剥出来。
vim myshell.c(输入以下代码)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #define MAX_ARGS 10 int main() { char buf[1024]; // 存用户输入 char *argv[MAX_ARGS]; // 存解析后的参数 while (1) { // 1. 打印提示符 printf("myshell> "); fflush(stdout); // 2. 获取用户输入 fgets(buf, sizeof(buf), stdin); buf[strcspn(buf, "\n")] = 0; // 去掉末尾的换行符 // 如果是 exit,直接退出 if (strcmp(buf, "exit") == 0) break; // 3. 解析命令 (按空格切分,比如 "ls -l" 切分成 "ls" 和 "-l") int argc = 0; argv[argc] = strtok(buf, " "); while (argv[argc] != NULL) { argc++; argv[argc] = strtok(NULL, " "); } if (argc == 0) continue; // 4. 创建子进程,替换并执行 pid_t pid = fork(); if (pid == 0) { // 子进程 execvp(argv[0], argv); // execvp 的 p 会自动去 PATH 里找,不需要写绝对路径 perror("exec error"); // 走到这里说明命令敲错了 exit(1); } else if (pid > 0) { // 父进程等待回收,防止僵尸进程 wait(NULL); } } return 0; }编译并运行:
gcc myshell.c -o myshell ./myshell👉现象:你会看到一个像模像样的myshell>提示符。你在里面敲ls、pwd、ping,它都能执行。但是如果你敲cd,你会发现没反应(这就要用到内建命令了,文档里有进阶讲解,咱们先跑通核心逻辑)。