:进程程序替换与手写微型 Shell)
一、 进程程序替换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你会发现没反应这就要用到内建命令了文档里有进阶讲解咱们先跑通核心逻辑。