原文链接:ewanvalentine.io,翻译已获作者 Ewan Valentine 授权。
本文完整代码:GitHub
进程 ID(Process ID)是操作系统中唯一标识进程的非负整数,取值范围:0 ~ 32767(signed short int最大值)
其中0 ~299(macOS 是 100)为系统预留给守护进程使用,递增向上分配,进程终止后其 PID 会被回收利用。
PID | 名字 | 描述 |
---|---|---|
0 | idle 进程 | 调度进程,是系统创建的第一个进程,运行在内核态 |
1 | init 进程 | 由 idle 进程创建,完成 Unix 系统的初始化,是一个有 root 权限的普通用户进程,是后续系统中所有孤儿进程的父进程 |
1 | #include<unistd.h> // 以下标识函数没有出错返回 |
1 | // 在当前线程中创建新进程,对子进程返回 0,对父进程返回子进程的 PID,若出错则返回 -1 |
特点:子进程只有一个父进程,能轻松通过 getppid()
获取父进程 PID,但父进程有多个子进程,没有函数能获取它所有子进程的 PID
fork 出的子进程会共享父进程的代码正文段,在同一份代码中区分二者:
1 | // 都会执行的代码 |
exec
函数去执行其他的程序COW 是常见的内存优化手段,只在真正需要使用时才分配资源,来看 PHP 应用 COW 的例子:
1 | <?php |
内存占用:
在 fork()
后,子进程与父进程共享用户空间、代码正文段、堆和栈,内核将四块区域的权限设置为只读,只有在二者中任一进程想修改内存中的数据时,才将要修改的内存区域复制为两份,添加可写权限后处理修改请求。这种延迟复制的实现,减少了进程创建的时间开销,也节省了内存。
1 | #include "apue.h" |
可以看到,在子进程内将全局变量 globalVar
的值修改为 7,将局部变量 var
的值修改为 89,父进程的两个变量值均未改变:
输出流重定向多了一行 “before fork” 的原因:
./a.out
的标准输出指向终端,是行缓冲的,在第 15 行遇到 \n
后会调用 write()
输出并冲洗缓冲区./a.out > temp.out
的标准输出指向文件,是全缓冲的,所以在 fork()
子进程时会将缓冲区一同复制1 | #include <unistd.h> |
返回 PID 指向的子进程直接在父进程的地址空间中运行,没有内存复制的概念,会比 COW 更快。
区别项 | fork | vfork |
---|---|---|
隔离性 | 子进程在写时复制父进程的数据作为副本,数据(变量)的修改互不影响 | 子进程运行在父进程的地址空间中,数据的修改对二者是同步修改的 |
执行顺序 | 父子进程调用顺序不定 | 保证子进程先运行,它调用 exec() 或 exit() 后,父进程才继续运行 |
1 | int globalVar = 6; |
可看到,子进程内对 2 个变量的自增操作同步到了父进程:
因为子进程与父进程的运行是异步的,异常终止时,会产生 2 种异常的子进程:
一般情况下,子进程在退出时,内核为它保存了如 PID、终止状态和运行时间等信息,其父进程可使用 wait
或 waitpid
来获取这些信息,进而释放子进程的资源。
特殊情况下,子进程运行完毕终止后,父进程对其不做处理,则其将变成僵死进程,如:
1 | #include <stdio.h> |
STAT 一栏 Z 标识 PID 为 7154 的子进程为僵死进程:
内核为僵死进程保留的信息得不到处理,PID 等资源会被一直占用,如果系统中大量的僵死进程耗尽了可用的 PID,将无法再创建进程。
父进程比子进程先终止,则这些子进程将会被 PID 为 1 的 init 进程收养。作为 ”父进程“ 的 init 进程会时刻监视着它们,任一个孤儿进程终止,init 都会主动调用 wait 处理并释放它占用的资源。
因为 init 会帮孤儿进程善后,相比僵死进程,它就没什么危害。
1 | #include <sys/wait.h> |
Unix 中有专门检查 status 退出状态的函数宏,如:
WIFEXITED(status)
:检查子进程是否是正常退出的,是则返回真
WEXITSTATUS(status)
:若子进程正常退出,则取得子进程 exit() 的状态码,如果是非正常退出则返回 0
1 | #include <stdlib.h> |
《Unix 环境高级编程》 第七章读书笔记,转载请注明来源。
使用 PHP 实现的 B 站直播间弹幕和礼物爬虫、弹幕分析与精彩时刻自动剪辑。