御剑安全扫描系统实战指南:从信息收集到渗透测试全流程解析
2026/6/17 10:31:09
子进程是父进程的 “复制品”,但 Linux 2.6 之后(如 Ubuntu 18/20,内核 5.4+)采用写时复制(COW)机制优化内存复制,大幅降低 fork () 的开销。
| 阶段 | 内存行为 | 核心说明 |
|---|---|---|
| fork () 刚执行完 | 子进程共享父进程所有内存空间(代码段、数据段、堆、栈),无独立内存 | 父子进程的页表指向同一块物理内存,仅标记为 “只读” |
| 父子进程未修改内存 | 始终共享内存,无额外开销 | 节省物理内存,提升 fork () 效率 |
| 任意进程修改内存(如变量、缓冲区) | 子进程为 “被修改的内存区域” 开辟独立物理内存 | 仅复制修改的部分,而非全量复制,兼顾效率与隔离性 |
进程终止分为 “正常终止” 和 “异常终止” 两类,不同终止方式的资源清理逻辑差异显著。
| 终止方式 | 核心说明 | 资源清理行为 |
|---|---|---|
| 1. main 函数 return | 仅 main 函数中 return 会终止进程,其他函数 return 仅结束函数 | 隐式调用 exit (),执行完整清理逻辑 |
| 2. exit ()(C 库函数) | 通用进程退出接口,推荐使用 | 1. 刷新标准 IO 缓冲区;2. 执行 atexit () 注册的清理函数;3. 关闭所有打开的文件流;4. 调用_exit () 终止进程 |
| 3. _exit ()/_Exit ()(系统调用) | 底层退出接口,无缓冲区操作 | 1. 关闭所有打开的文件描述符;2. 直接终止进程;3. 不执行 atexit () 清理函数、不刷新缓冲区 |
| 4. 主线程退出 | 多线程程序中,主线程退出导致整个进程终止 | 同 exit () 的清理逻辑(取决于线程库实现) |
| 5. 主线程调用 pthread_exit | 主线程退出,但其他线程仍运行时,进程不终止;仅所有线程退出后进程终止 | 仅终止主线程,不影响其他线程,进程资源需等所有线程退出后清理 |
| 终止方式 | 核心说明 | 终止原因 |
|---|---|---|
| 6. abort() | 主动触发异常终止 | 发送 SIGABRT 信号(6 号)给自身,强制终止,不执行清理逻辑 |
| 7. 信号终止(kill pid /signal) | 被动异常终止 | 如 kill -9 pid(SIGKILL,9 号信号)、Ctrl+C(SIGINT,2 号信号),直接终止,无清理 |
| 8. 最后一个线程被 pthread_cancel | 多线程程序的异常终止 | 线程被取消导致进程终止,无主动清理逻辑 |
c
运行
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { printf("测试缓冲区(无换行)"); // 无换行,标准IO缓冲区不会自动刷新 // exit(0); // 执行此句:会刷新缓冲区,输出内容 _exit(0); // 执行此句:不刷新缓冲区,无输出 return 0; }进程终止后并非 “完全消失”,父 / 子进程的退出顺序会导致两种特殊状态,其中僵尸进程是系统稳定性的核心风险。
父进程创建子进程后,子进程先终止:
bash
运行
# 查看僵尸进程(STAT列为Z) ps aux | grep Z # 实时监控(%z列显示僵尸进程数) top父进程创建子进程后,父进程先终止:
| 类型 | 产生条件 | 状态标识 | 系统风险 | 资源回收方 |
|---|---|---|---|---|
| 僵尸进程 | 子进程先终止,父进程未回收 | STAT=Z | 高(耗尽内核内存) | 需父进程主动回收 |
| 孤儿进程 | 父进程先终止,子进程仍运行 | STAT=S/R(正常状态) | 无 | init/systemd 自动回收 |
子进程终止后,父进程必须通过wait()/waitpid()获取子进程退出状态并回收 PCB,否则子进程会变成僵尸进程。
c
运行
#include <sys/wait.h> pid_t wait(int *status);status:存储子进程退出状态的指针(不关心则传 NULL);c
运行
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <stdlib.h> int main() { pid_t pid = fork(); if (pid > 0) { // 父进程 int status; pid_t ret = wait(&status); // 阻塞等待子进程退出 printf("回收子进程PID:%d\n", ret); if (WIFEXITED(status)) { // 判断是否正常终止 printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status)); } if (WIFSIGNALED(status)) { // 判断是否信号终止 printf("子进程被信号终止,信号编号:%d\n", WTERMSIG(status)); } } else if (pid == 0) { // 子进程 printf("子进程PID:%d\n", getpid()); exit(1); // 正常退出,退出码1 // abort(); // 异常终止(SIGABRT) } else { perror("fork"); return 1; } return 0; }c
运行
pid_t waitpid(pid_t pid, int *status, int options);pid:指定回收的子进程 PID(-1 表示任意子进程);options:WNOHANG(非阻塞,无子进程退出则立即返回 0);通过以下宏解析wait()的status参数,判断子进程终止方式:
| 宏 | 功能 | 配套使用 |
|---|---|---|
| WIFEXITED(status) | 判断子进程是否正常终止(return/exit/_exit) | 是:用 WEXITSTATUS (status) 获取退出码 |
| WEXITSTATUS(status) | 获取正常终止的退出码(仅 WIFEXITED 为真时有效) | 退出码范围 0-255(exit (n) 的 n 仅低 8 位有效) |
| WIFSIGNALED(status) | 判断子进程是否信号异常终止 | 是:用 WTERMSIG (status) 获取信号编号 |
| WTERMSIG(status) | 获取终止子进程的信号编号(仅 WIFSIGNALED 为真时有效) | 如 9=SIGKILL、6=SIGABRT |
系统定义了标准化退出码,推荐使用:
c
运行
#include <stdlib.h> EXIT_SUCCESS; // 0(正常退出) EXIT_FAILURE; // 1(异常退出)父进程创建子进程后,通过wait()阻塞回收,适合子进程生命周期短的场景:
c
运行
pid_t pid = fork(); if (pid > 0) { wait(NULL); // 阻塞回收,无僵尸进程 }父进程循环非阻塞检查子进程状态,不阻塞业务逻辑:
c
运行
while (1) { pid_t ret = waitpid(-1, NULL, WNOHANG); if (ret == 0) break; // 无子进程退出 if (ret == -1) break; // 所有子进程已回收 printf("回收子进程PID:%d\n", ret); }子进程终止时会向父进程发送 SIGCHLD 信号,父进程捕获信号后回收:
c
运行
#include <signal.h> void sigchld_handler(int sig) { // 循环回收所有终止的子进程 while (waitpid(-1, NULL, WNOHANG) > 0); } int main() { signal(SIGCHLD, sigchld_handler); // 注册信号处理函数 // 创建子进程逻辑... while (1) sleep(1); // 父进程长期运行 return 0; }