0

0

Linux 中 PID 是如何分配的?

冷炫風刃

冷炫風刃

发布时间:2026-01-25 19:43:36

|

295人浏览过

|

来源于php中文网

原创

Linux内核用位图找最小空闲PID:从1开始扫描首个为0的比特位,置1后返回编号;位图每页管32768个PID,对应默认pid_max值。

linux 中 pid 是如何分配的?

内核用位图找最小空闲 PID

Linux 分配 PID 的核心动作是:从 1 开始,扫描位图(bitmap)中第一个值为 0 的比特位,将其置 1,并返回对应编号。这个“最小可用号”策略保证了 PID 尽可能紧凑,也便于用户快速识别“刚起的进程”。位图按页组织,每页 4096 字节 → 可管理 32768 个 PID(4096 × 8),正好匹配默认 /proc/sys/kernel/pid_max 值。

常见错误现象:fork() 失败报 Resource temporarily unavailable,不一定是内存不足,很可能是位图里真没空位了——比如 pid_max=32768 时系统已有 32767 个活进程(PID 1systemd 占着),再 fork 就会失败。

  • last_pid 是全局变量,记录上次成功分配的 PID,下次从 last_pid + 1 开始找,避免每次都从头扫
  • RESERVED_PIDS(通常为 300)不参与动态分配,留给内核线程和关键守护进程
  • 位图本身不存进程信息,只管“编号是否被占”,真正关联进程靠 struct pidtask_struct 的双向指针

为什么 PID 1 总是 systemd,而 PID 2 不稳定?

PID 1 是硬编码保留的:内核启动后直接调用 kernel_thread() 创建第一个用户态进程,强制指定 PID 为 1,后续所有进程都由它派生。现代发行版几乎都用 systemd 占据该位置;若换成 sysvinitopenrc,PID 1 还是它,只是二进制不同。

PID 2 则没有这种保障:它是内核线程 kthreadd 的 PID,但该线程在 rest_init() 中创建,时机紧邻 PID 1,一旦启动顺序微调(如 init 进程初始化稍慢),PID 2 就可能被其他早期内核线程抢走。所以脚本里写死 kill -9 2 是危险操作。

  • 不要依赖 PID 2 指向特定线程,查 ps -o pid,comm -p 2 才能确认当前是什么
  • PID 0 固定属于 swapper(调度器空闲进程),永远不可见、不可 kill
  • 容器内看到的 PID 1 是命名空间局部 PID,其全局 PID 一定是另一个大数,可通过 /proc/1/statusNSpid 字段验证

pid_max 调大就能无限开进程?别信

调高 pid_max(比如 sysctl -w kernel.pid_max=4194304)只是放宽了编号池上限,并不等于系统能真的运行 400 万个进程。每个 PID 对应一个 task_struct 结构体,占用约 5–10 KB 内存;PID 池扩大四倍,仅这部分内存就多吃掉上 GB。更现实的瓶颈往往是 ulimit -u(单用户 nproc 限制)或 RLIMIT_AS(地址空间)。

易森网络企业版
易森网络企业版

如果您是新用户,请直接将本程序的所有文件上传在任一文件夹下,Rewrite 目录下放置了伪静态规则和筛选器,可将规则添加进IIS,即可正常使用,不用进行任何设置;(可修改图片等)默认的管理员用户名、密码和验证码都是:yeesen系统默认关闭,请上传后登陆后台点击“核心管理”里操作如下:进入“配置管理”中的&ld

下载

典型误判场景:监控显示 ps -eLf | wc -l 输出 3000,sysctl kernel.pid_max 是 32768,就以为还有 29000 个 PID 可用——其实大量 PID 已被僵尸进程(Z 状态)占着未回收,ps aux | awk '$8 ~ /Z/ {count++} END{print count+0}' 才是真实“卡住”的数量。

  • 调整 pid_max 后必须重启部分服务(如 systemd 的子进程不会自动感知新上限)
  • 容器环境要同时调大宿主机 pid_max 和容器内 pid cgroup 限额,否则 docker run --pids-limit 会优先触发
  • 高频 fork/exit 场景(如短命 CGI 进程)建议用 pid_max=65536 + echo 1 > /proc/sys/kernel/panic_on_oom 避免位图扫描拖慢分配

alloc_pid() 函数里最易忽略的三件事

看内核源码 kernel/pid.calloc_pid(),表面只是遍历命名空间层级分配数字,但有三个细节常被跳过:

  • 它先分配 struct pid 内存(来自 per-namespace 的 pid_cachep slab),再逐层调用 idr_alloc_cyclic() 填数字——如果某层命名空间的 IDR 树已满,整个分配就失败,不是重试而是直接 return ERR_PTR(-EAGAIN)
  • CLONE_NEWPID 创建新命名空间时,子空间的 level 比父空间 +1,且 numbers[] 数组长度 = level + 1,意味着嵌套 10 层容器后,每个进程要维护 11 个 PID 值(1 个全局 + 10 个局部)
  • 分配成功后,task_struct->pid 赋的是局部 PID(即当前命名空间视角的编号),而信号发送、kill() 系统调用底层用的却是全局 PID——这个转换在 find_vpid() 里完成,不是零成本

真正复杂的从来不是“怎么分”,而是“分完之后怎么让所有子系统(调度、cgroup、ptrace、信号)都认这个号”。PID 管理是 Linux 内核里少有的横跨 namespace、memory、scheduling 三大子系统的胶水逻辑,动它之前,先确认你改的到底是编号池,还是整个进程身份体系。

相关专题

更多
python中print函数的用法
python中print函数的用法

python中print函数的语法是“print(value1, value2, ..., sep=' ', end=' ', file=sys.stdout, flush=False)”。本专题为大家提供print相关的文章、下载、课程内容,供大家免费下载体验。

186

2023.09.27

resource是什么文件
resource是什么文件

Resource文件是一种特殊类型的文件,它通常用于存储应用程序或操作系统中的各种资源信息。它们在应用程序开发中起着关键作用,并在跨平台开发和国际化方面提供支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

152

2023.12.20

counta和count的区别
counta和count的区别

Count函数用于计算指定范围内数字的个数,而CountA函数用于计算指定范围内非空单元格的个数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

198

2023.11.20

全局变量怎么定义
全局变量怎么定义

本专题整合了全局变量相关内容,阅读专题下面的文章了解更多详细内容。

78

2025.09.18

python 全局变量
python 全局变量

本专题整合了python中全局变量定义相关教程,阅读专题下面的文章了解更多详细内容。

96

2025.09.18

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

200

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

190

2025.07.04

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

482

2023.08.10

c++ 根号
c++ 根号

本专题整合了c++根号相关教程,阅读专题下面的文章了解更多详细内容。

45

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 7.8万人学习

Git 教程
Git 教程

共21课时 | 3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号