4.1.3 进程创建与终止机制
进程创建主要通过系统调用 fork()、vfork() 与 clone() 完成。fork() 复制父进程大部分上下文,采用写时复制(COW)降低开销;vfork() 与父进程共享地址空间,直到子进程调用 exec() 或 _exit(),适用于极短路径但需谨慎;clone() 提供更细粒度的资源共享选项,是线程实现的基础。进程创建后通常通过 execve() 家族加载新程序映像,完成“复制+替换”的执行模型。
关键命令与解释#
# 1) 查看当前进程树,理解父子关系
ps -ef --forest | head -n 20
# -e:显示所有进程;-f:全格式;--forest:树状展示
# 2) 观察进程状态与父子PID
ps -o pid,ppid,stat,cmd -p $$
# $$ 为当前shell的PID
# stat中的Z表示僵尸,S睡眠,R运行
# 3) 观察系统调用(需安装strace)
strace -f -e trace=fork,vfork,clone,execve,exit,wait4 /bin/ls >/dev/null
# -f 跟踪子进程;-e 只显示关键系统调用
示例:从 fork 到 exec 的完整演示#
// 文件:/tmp/fork_exec_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork(); // 创建子进程
if (pid < 0) {
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程:执行新程序
execlp("echo", "echo", "Hello from child", NULL);
// execlp 失败才会走到这里
perror("exec failed");
_exit(127);
} else {
// 父进程:回收子进程,避免僵尸
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("child exit code=%d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("child killed by signal=%d\n", WTERMSIG(status));
}
}
return 0;
}
# 编译与运行
gcc -O2 -o /tmp/fork_exec_demo /tmp/fork_exec_demo.c
/tmp/fork_exec_demo
# 预期输出:
# Hello from child
# child exit code=0
退出机制与信号示例#
# 启动一个后台sleep进程
sleep 300 &
echo $! # 记录PID
# 优雅终止:先SIGTERM
kill -TERM <PID>
# 若进程未退出,升级为SIGKILL
kill -KILL <PID>
# 查看退出原因
wait <PID> 2>/dev/null
echo $? # 返回0-255的退出码
排错与故障定位#
# 1) 僵尸进程排查
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /Z/ {print}'
# 看到Z说明父进程未wait回收
# 2) 定位僵尸的父进程并处理
ps -o pid,ppid,cmd -p <ZOMBIE_PID>
# 如果父进程异常,考虑重启父进程或发送SIGCHLD处理
# 3) 进程异常退出(段错误)定位
dmesg | tail -n 50 | grep -i segfault
# 结合日志与core文件定位
练习#
- 编写一个程序先
fork()再execve()执行/bin/sleep 5,父进程waitpid()并输出退出码。 - 人为制造僵尸进程:子进程立即退出,父进程故意不
wait,观察ps中的Z状态。 - 使用
strace -f跟踪一个简单程序,识别fork/exec/exit调用链。
小结要点#
fork()+exec()是最常见的创建执行模型;wait()/waitpid()是回收与避免僵尸的关键。exit()会做用户态清理,_exit()直接退出,适用于fork()后子进程失败路径。- 优雅退出优先
SIGTERM,必要时再用SIGKILL,并结合日志与系统调用追踪排查异常终止原因。