Linux --进程控制

📅 2026/8/26 16:06:17
Linux --进程控制
进程的诞生fork与vforkfork函数初识在Linux中fork是创建新进程的唯一方式从用户态视角看。它通过复制调用进程父进程来生成一个全新的进程子进程。#include unistd.h pid_t fork(void);返回值子进程返回0。父进程返回子进程的PID大于0。出错返回-1。为什么这样设计返回值父进程可能有多个子进程需要通过PID来区分和管理每个子进程。子进程只需要知道自己是否是子进程返回0即可它可以通过getppid()获取父进程PID。fork的执行流程当调用fork时内核会执行以下操作分配新的内存块和内核数据结构task_struct给子进程。将父进程的部分数据结构内容拷贝到子进程。将子进程添加到系统进程列表中。fork返回开始调度器调度。关键图解fork之前父进程独立执行。fork之后父子进程各自独立执行执行顺序由调度器决定无法预测。示例代码int main() { printf(Before: pid is %d\n, getpid()); pid_t pid fork(); if (pid -1) perror(fork), exit(1); printf(After: pid is %d, fork return %d\n, getpid(), pid); sleep(1); return 0; }输出Before: pid is 43676 After: pid is 43676, fork return 43677 After: pid is 43677, fork return 0注意子进程没有打印 “Before”因为fork之前父进程独立执行子进程是从fork返回处开始执行的。写时拷贝父子进程在创建时共享代码和数据为了效率。只有当任意一方试图修改数据时才会以写时拷贝的方式为修改方复制一份副本。优点提高内存使用效率延迟分配。保证进程独立性互不干扰。扩展理解COW 是虚拟内存管理的重要优化它使得fork非常轻量因为不需要立即复制全部内存。进程的终点进程终止进程退出的三种场景代码运行完毕结果正确退出码为0。代码运行完毕结果不正确退出码非0。代码异常终止被信号杀死如SIGKILL、SIGSEGV。常见退出方法正常终止return从main返回。exit()标准库函数会执行清理工作。_exit()系统调用立即终止。异常终止收到信号如CtrlCSIGINT。退出码Exit Code0成功。非0失败具体含义可自定义或参考系统约定。常见退出码Shell中退出码含义0成功1通用错误2命令使用不当126权限不足或无法执行127命令未找到130被SIGINTCtrlC终止143被SIGTERM终止注意退出码只有低8位有效0~255如果传-1实际看到的是255。为什么exit(-1)会显示 2551. 本质原因数据截断exit()函数的参数是int类型通常占4字节但内核只保留了该整数的低8位1字节作为进程的退出状态。int 类型假设32位 -1 的二进制表示11111111 11111111 11111111 11111111 ↑ 只取低8位11111111 转换成十进制255为什么只取低8位退出码范围限制在 0-255足够表达各种退出状态。内核用int的status字段既要存退出码又要存终止信号等信息通过位图方式。status的低16位布局只研究status低16比特位因为高16位在进程等待中通常不使用。status 的低16位从高位到低位 ┌──────────────┬────────────┬──────────────┐ │ bit 15-8 │ bit 7 │ bit 6-0 │ │ (8位) │ (1位) │ (7位) │ ├──────────────┼────────────┼──────────────┤ │ 退出码 │ core dump │ 终止信号 │ │ (exit code)│ 标志 │ (term sig) │ └──────────────┴────────────┴──────────────┘详细拆解位范围长度含义说明bit 15-88位退出码进程正常退出时的返回值0-255bit 71位core dump标志是否产生了核心转储文件bit 6-07位终止信号编号进程被哪个信号杀死的int ret wait(st); if (ret 0 (st 0x7F) 0) { // 正常退出 printf(child exit code:%d\n, (st 8) 0xFF); } else if (ret 0) { // 异常退出 printf(sig code : %d\n, st 0x7F); }st 0x7F—— 提取终止信号0x7F 的二进制0000 0000 0111 1111 ↑ 只保留低7位 st 0x7F 的作用提取 status 的 bit 6-0终止信号示例如果进程被SIGKILL (9)杀死st 0x7F 9如果进程被SIGINT (2)杀死st 0x7F 2如果进程正常退出st 0x7F 0(st 8) 0xFF—— 提取退出码假设 st 0x0000 0A00 (子进程 exit(10)) st 8右移8位把退出码移到低8位 0x0000 0A00 8 0x0000 000A (st 8) 0xFF取低8位 0x0000 000A 0xFF 10示例子进程exit(10)→ 退出码 10子进程exit(255)→ 退出码 255判断是否正常退出if ((st 0x7F) 0) { // 终止信号为0 → 进程正常退出 }exitvs_exit_exit()exit()直接系统调用库函数最终调用_exit()不刷新缓冲区刷新所有缓冲区关闭流不执行清理函数执行atexit注册的清理函数printf(hello); // 没有换行 exit(0); // 输出 hello _exit(0); // 不输出缓冲区未刷新扩展知识return在main中等同于exit因为main是由启动例程调用的启动例程会接收返回值并传递给exit。父进程的“收尸”进程等待为什么要等待子进程防止僵尸进程子进程退出后如果父进程不回收子进程变成僵尸Z状态占用内核资源。获取退出信息父进程需要知道子进程任务完成情况成功/失败/异常。无法杀死僵尸kill -9对僵尸进程无效只能靠父进程wait。wait和waitpid#include sys/wait.h pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options);wait阻塞等待任意一个子进程。waitpid可指定等待哪个子进程并支持非阻塞。返回值总览函数返回值含义wait() 0成功返回被回收的子进程PID-1失败没有子进程或出错errno被设置waitpid() 0成功返回被回收的子进程PID0非阻塞模式WNOHANG下没有子进程退出-1失败出错errno被设置参数解析pid-1等待任意子进程等同于wait。0等待指定PID的子进程。status输出型参数存放退出信息按位图编码。options0阻塞等待。WNOHANG非阻塞如果没有子进程退出则立即返回0。常用宏WIFEXITED(status)是否正常终止。WEXITSTATUS(status)提取退出码需先判断WIFEXITED。WTERMSIG(status)提取终止信号。if (WIFEXITED(status)) { printf(退出码: %d\n, WEXITSTATUS(status)); } else { printf(终止信号: %d\n, WTERMSIG(status)); }阻塞 vs 非阻塞等待阻塞父进程挂起直到子进程退出。非阻塞父进程轮询检查子进程状态期间可执行其他任务。非阻塞示例伪代码do { ret waitpid(-1, status, WNOHANG); if (ret 0) { // 执行临时任务 do_other_work(); } } while (ret 0);程序“换脑”进程程序替换什么是程序替换子进程fork后如果想执行一个全新的程序而非父进程代码就需要程序替换。原理将新程序的代码和数据加载到当前进程的地址空间。替换原有的代码段、数据段、堆栈。进程ID不变只是“内容”换了。exec函数族Linux 提供了 6 个exec系列函数它们都以exec开头但用法不同。int 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[]);命名规律l(list)参数以列表形式传递。v(vector)参数以数组形式传递。p(path)自动在PATH中搜索程序路径。e(env)自定义环境变量。注意调用成功则不返回。调用失败返回-1。只有execve是系统调用其他都是库函数。示例// 使用列表参数指定完整路径 execl(/bin/ls, ls, -l, NULL); // 使用数组参数自动搜索PATH char *argv[] {ls, -l, NULL}; execvp(ls, argv);