结论:fork() 完整复制当前进程得到子进程(写时复制共享内存页),返回值区分父子(父进程拿到子 PID,子进程拿到 0);exec() 用新程序镜像替换当前进程的地址空间(代码、数据、栈全部换新),PID 不变。两步合起来实现"启动一个新程序"。

展开:拆成两步的设计精妙在 fork 与 exec 之间可以做事——shell 启动命令时在子进程里先重定向标准输入输出(dup2)、设置环境变量、切换用户、建立管道,再 exec 目标程序,这些设置被新程序天然继承。如果合成一个系统调用,就得给调用设计海量参数来表达这些定制。性能上,fork 靠写时复制(COW)避免复制内存:父子共享物理页,标记只读,任何一方写入时触发页错误才真正复制该页;紧跟 exec 时几乎零拷贝。易错点:fork 后文件描述符表被复制,父子共享文件偏移量;多线程程序 fork 后子进程只保留调用线程,且其他线程持有的锁状态被原样复制,可能造成死锁(pthread_atfork 就是为这个准备的)。

pid_t pid = fork();
if (pid == 0) {
    dup2(fd, STDOUT_FILENO);   // 重定向
    execlp("ls", "ls", NULL);  // 替换镜像
}
waitpid(pid, NULL, 0);

追问方向:vfork 与 clone 的区别、fork 炸弹的原理与防御(进程数限制)。