在 Linux 系统中,每个进程都有一个非负整型表示的唯一进程 ID,虽然在主机级别是唯一的,但是进程的 ID 可以重用,一个进程停止后,其它的进程可以复用该 ID 。
Linux 采用延迟重用的算法,在大部分场景下会使得新进程 ID 不同于最近终止进程所使用的 ID,以防止将新进程误认为是使用同一 ID 的某个已终止的先前进程。
这里主要讨论了 Linux 中分配进程 ID 的方法以及源码实现。
PID 分配
在 Linux 系统中,内核分配 PID 的范围是 (RESERVED_PIDS, PID_MAX_DEFAULT)
,因为需要支持 namespace ,实际上在每个 namespace 中是唯一的,PID 依次连续分配,也就意味着不同的 namespace 可能会出现相同的 PID 。
修改最大 PID
Linux 中设置了系统最大 PID 的大小,在内核中对应了 kernel/pic.c
文件中的全局变量 int pid_max
,默认通过宏 PID_MAX_DEFAULT
指定。
在用户空间可以通过如下的方式进行查看和修改。
----- 查看当前系统最大的PID
$ cat /proc/sys/kernel/pid_max
32768 # <-> 0x8000
$ sysctl kernel.pid_max
32768
----- 修改最大PID值,如下的方式只是临时生效
# echo 65536 > /proc/sys/kernel/pid_max
# sysctl -w kernel.pid_max=65536
如果需要持久化需要修改配置文件 /etc/sysctl.conf
,添加 kernel.pid_max=65536
配置即可。
内核实现
Linux 内核中通过位图实现 PID 的分配,每个页 (一般是4096Bytes) 作为一个 pidmap ,然后每个位用来标示一个进程 ID ,所以单个页可以支持 4096 * 8 = 32768
个进程,这也是默认的最大进程数。
因为支持 PID 的 namespace 功能,所以实际分配是针对的 ns 级别,每次尝试从最近一次 PID 开始分配,可以通过 cat /proc/loadavg
文件最后一个值查看。
调用流程
主要包含了两部分,初始化和真正的开始分配 PID 。
在系统启动时,会通过 pidmap_init()
函数做些初始化,包括了 PID 的最大、最小值等信息。
Linux 中在 fork 进程时,详细来说是在 copy_process()
过程中,当执行完父进程相关信息的复制之后,紧接着便是执行 alloc_pid()
方法去分配子进程的 PID 。
总结
- PID 分配上限可以通过
/proc/sys/kernel/pid_max
查询,一般默认为 32768。 - 对于
PID<300
的情况只允许分配一次,一般对应了系统线程,所以一般进程 PID 分配范围是(300, 32768)
。 - 每个 PID 分配成功后,会将当前的 PID 设置为
last_pid
,下次便从last_pid + 1
开始往下查找。 - 通过位图记录已分配和未分配 PID,单页为 4KB ,对应了默认的最大进程数 32768 。
注意,应为 last_pid
的作用,对于当大于 last_pid + 1
的进程被杀并回收改 PID 之后,如果再创建新进程,很有可能会复用 PID 。