在 Unix/Linux 编程中,fork() 创建一个子进程;成功时父进程得到子进程 PID,子进程得到 0,失败返回 -1。子进程从 fork 返回处继续执行,初始时拥有与父进程相似的地址空间视图、文件描述符等资源状态。现代 Linux 通常通过写时复制避免立刻复制所有物理页,但父子进程仍是独立进程,后续写入会获得各自的私有副本。
exec 系列调用把当前进程的用户程序映像替换为另一个程序:地址空间内容、代码和数据等被新映像取代,但进程身份通常保留,文件描述符默认也保留,除非设置了 close-on-exec。exec 成功不会返回旧程序;失败才返回错误。因此常见模式是 fork 子进程,然后子进程 exec 启动目标程序,父进程继续工作或等待。
wait() / waitpid() 让父进程取得子进程终止状态并回收其退出记录。子进程退出后,内核仍需暂存 PID、退出码等信息供父进程读取;父进程 wait 后这些记录才释放。父进程不一定必须立刻等待,但长期不回收会留下僵尸记录。
fork 后父子文件描述符可能指向同一个打开文件描述(共享文件偏移和状态标志);这不等于所有资源都完全共享。多线程进程 fork 的子进程只保留调用 fork 的线程,其他锁的状态处理复杂,因此子进程通常应尽快 exec,不能随意调用任意非异步信号安全的库函数。
常见追问
Q:fork 会把整个进程内存复制一遍吗?
A:语义上子进程获得独立的私有地址空间视图;实现通常采用写时复制,只有页面被写入时才复制,具体实现属于操作系统细节。
Q:exec 后 PID 会改变吗?
A:通常不会。exec 替换当前进程执行映像,不创建新进程;创建子进程的是 fork 等机制。
Q:为什么 shell 常用 fork 加 exec?
A:shell 需要保留自身以继续接收命令,因此让子进程运行新程序,父 shell 可等待前台子进程,也可继续处理后台任务。