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文件定位

练习#

  1. 编写一个程序先 fork()execve() 执行 /bin/sleep 5,父进程 waitpid() 并输出退出码。
  2. 人为制造僵尸进程:子进程立即退出,父进程故意不 wait,观察 ps 中的 Z 状态。
  3. 使用 strace -f 跟踪一个简单程序,识别 fork/exec/exit 调用链。

小结要点#

  • fork() + exec() 是最常见的创建执行模型;wait()/waitpid() 是回收与避免僵尸的关键。
  • exit() 会做用户态清理,_exit() 直接退出,适用于 fork() 后子进程失败路径。
  • 优雅退出优先 SIGTERM,必要时再用 SIGKILL,并结合日志与系统调用追踪排查异常终止原因。