进程控制初识

发布时间:2026/6/26 1:39:58
进程控制初识 一、进程创建1.1 fork 函数在 Linux 中 fork 函数是非常重要的系统调用它从已存在进程中创建一个新进程。新进程称为子进程原进程称为父进程。#include unistd.hpid_t fork(void);返回值子进程中返回 0父进程中返回子进程的 PID出错返回 -1。进程调用 fork 后内核会做以下工作分配新的内存块和内核数据结构给子进程将父进程部分数据结构内容拷贝至子进程添加子进程到系统进程列表fork 返回开始调度器调度。fork 之前父进程独立执行fork 之后父子两个执行流分别执行。谁先执行完全由调度器决定。子进程的 task_struct 以父进程为模板。#include stdio.h #include unistd.h int main() { int data 100; printf(Before fork, data %d\n, data); pid_t pid fork(); printf(After fork, PID %d, fork return %d\n, getpid(), pid); return 0; }运行结果会打印三行输出一行 Before两行 After。子进程没有打印 before因为 fork 之前的代码只有父进程执行。1.2 fork 返回值设计fork 有两个返回值由内核实现机制决定。fork 返回时内核已在父子进程中分别建立各自的 task_struct各自从返回指令处开始执行。子进程返回 0父进程返回子进程 PID。通过 if-else 对返回值分流让父子进程在不同分支执行不同逻辑。#include stdio.h #include unistd.h int main() { pid_t pid fork(); if(pid 0) { perror(fork); return 1; } else if(pid 0) { printf(子进程运行中, PID %d\n, getpid()); } else { printf(父进程运行中, 子进程 PID %d\n, pid); } return 0; }1.3 写时拷贝父子代码共享数据在未写入时也共享。任意一方试图写入时以写时拷贝方式各自拥有一份副本。这是一种延时申请技术提高整机内存使用率保证进程独立性。1.4 fork 常规用法两种典型用法父进程复制自己处理不同任务如服务器生成子进程处理请求子进程从 fork 返回后调用 exec 执行全新程序。1.5 fork 调用失败的原因系统中有太多进程实际用户进程数超过限制。二、进程终止2.1 进程退出场景三种退出场景代码运行完毕结果正确、结果不正确、代码异常终止。代码运行完毕结果正确代码运行完毕结果不正确代码异常终止如收到信号2.2 退出码退出码 0 表示成功非 0 表示错误。可通过 echo $? 查看最近一次退出码。退出码 1 表示不被允许的操作130/143 属于 128n 信号。常用退出码退出码 0命令执行无误退出码 1不被允许的操作退出码 130/143128n 信号return 0 的含义是退出码被系统获得用于辨别执行情况。代码出异常后退出码无意义应关注是什么信号导致了退出。2.3 exit 函数#include stdlib.hvoid exit(int status);exit 执行用户定义的清理函数关闭所有流并刷新缓冲区最后调用 _exit。#include stdio.h #include stdlib.h int main() { printf(Welcome to Linux process); exit(0); }2.4 _exit 函数#include unistd.hvoid _exit(int status);_exit 直接进入内核终止不执行清理不刷新缓冲区。status 仅低 8 位有效。#include stdio.h #include unistd.h int main() { printf(Welcome to Linux process); _exit(0); }2.5 return 退出main 中 return n 等同于 exit(n)。return 会刷新缓冲区。2.6 终止方式对比方法说明刷新缓冲区main 中 return nn 表示退出码自动传入 exit会刷新exit(n)标准库函数执行清理后调 _exit会刷新_exit(n)系统调用直接进入内核终止不会刷新三、进程等待3.1 进程等待的必要性子进程退出后若父进程不管不顾会造成僵尸进程导致内存泄漏。kill -9 也无法杀死僵尸进程。父进程通过进程等待回收子进程资源并获取退出信息。另一种方式父进程将 SIGCHLD 设为 SIG_IGN子进程终止时自动清理。3.2 wait 函数#include sys/types.h#include sys/wait.hpid_t wait(int *status);成功返回被等待进程 PID失败返回 -1。status 是输出型参数不关心时设为 NULL。阻塞等待直到有子进程退出。#include stdio.h #include stdlib.h #include sys/wait.h #include unistd.h int main() { pid_t pid fork(); if(pid 0) { sleep(5); exit(42); } else { int status; wait(status); printf(子进程已回收\n); } return 0; }3.3 waitpid 函数waitpid 是进程等待的最佳实践本质是获取子进程 task_struct 内的属性数据。pid_t waitpid(pid_t pid, int *status, int options);pid 参数取值pid 值含义 0等待指定 PID 的特定子进程-1等待任意子进程最常用0等待同进程组的任意子进程 -1等待 PGID 为该绝对值的任意子进程options 参数值含义0阻塞等待WNOHANG非阻塞等待无子进程退出则返回 03.4 获取子进程 statusstatus 低 16 位0-6 位退出信号8-15 位退出码。退出信号退出码含义00运行正常结果正确0非 0运行正常结果错误非 0无意义代码异常终止WIFEXITED(status): 正常终止则为真WEXITSTATUS(status): 提取子进程退出码#include stdio.h #include stdlib.h #include sys/wait.h #include unistd.h int main() { pid_t pid fork(); if(pid 0) { sleep(20); exit(10); } else { int status; wait(status); if((status 0x7F) 0) printf(正常退出, 退出码 %d\n, (status 8) 0xFF); else printf(异常退出, 信号编号 %d\n, status 0x7F); } return 0; }3.5 阻塞与非阻塞等待阻塞等待传 0父进程等待直到子进程退出。非阻塞等待传 WNOHANG父进程立即返回可轮询子进程状态。// 阻塞等待pid_t ret waitpid(-1, status, 0);// 非阻塞轮询do {ret waitpid(-1, status, WNOHANG);if(ret 0)printf(子进程仍在运行...\n);} while(ret 0);四、进程程序替换4.1 替换原理fork 创建子进程后执行的是和父进程相同的程序。若需执行全新程序通过程序替换实现。exec 函数将用户空间代码和数据完全替换从新程序启动例程开始执行进程 ID 不变。程序替换成功后后续代码不再执行exec 只有出错返回值-1。子进程替换不影响父进程写时拷贝保证数据独立性。4.2 exec 函数族#include unistd.hint execl(const char *path, const char *arg, ...);int execlp(const char *file, const char *arg, ...);int execle(const char *path, const char *arg, ..., char *const envp[]);int execv(const char *path, char *const argv[]);int execvp(const char *file, char *const argv[]);int execve(const char *path, char *const argv[], char *const envp[]);4.3 命名规律字母含义说明llist参数采用列表形式vvector参数用数组形式ppath自动搜索 PATH 环境变量eenv自己维护环境变量只有 execve 是真正的系统调用man 第 2 节其余五个最终调用 execveman 第 3 节。4.4 调用示例char *const args[] {ls, -l, /home, NULL};char *const env[] {PATH/bin:/usr/bin, TERMconsole, NULL};execl(/bin/ls, ls, -l, /home, NULL);execlp(ls, ls, -l, /home, NULL);execle(/bin/ls, ls, -l, /home, NULL, env);execv(/bin/ls, args);execvp(ls, args);execve(/bin/ls, args, env);五、自主 Shell 命令行解释器5.1 设计目标通过自主实现微型 Shell 理解命令行解释器运行原理。要求处理普通命令和内建命令。5.2 实现原理Shell 核心循环获取命令行、解析命令行、fork 创建子进程、execvp 替换子进程、父进程 wait 等待。内建命令cd、export、env、echo由 Shell 自身执行因为子进程无法改变父进程环境。5.3 main 函数的三个参数在实现 Shell 时main 函数的三个参数非常重要int main(int argc, char *argv[], char *envp[]);参数说明argc命令行参数个数包含程序名至少为 1argv命令行参数字符串数组argv[0] 是程序名envp环境变量字符串数组格式 KEYVALUE以 NULL 结尾fork/exec 相当于 callexit/wait 相当于 return。这种将函数调用模式扩展到进程间通信的设计思想是结构化程序设计的基础。六、总结速查表操作命令或函数创建进程fork()正常终止进程exit(status) / return status直接终止进程_exit(status)查看退出码echo $?等待任意子进程wait(status)等待指定子进程waitpid(pid, status, 0)非阻塞等待waitpid(pid, status, WNOHANG)程序替换列表execl / execlp / execle程序替换数组execv / execvp / execve判断正常退出WIFEXITED(status)提取退出码WEXITSTATUS(status)