struct pid
Linux 内核中的 struct pid 结构体,它是内核全局统一管理进程 PID 信息的核心枢纽,负责汇总一个进程在所有所属 PID 命名空间中的本地 PID 标识,是连接各个 PID 命名空间、本地 PID 与进程实体(struct task_struct)的关键桥梁。
核心定位与关联关系
在学习 struct pid 时,先明确它与其他相关结构体的核心关联,避免混淆:
- 与
struct pid_namespace:一对多,一个struct pid对应进程所属的所有 PID 命名空间,每个命名空间内都有该进程的唯一本地 PID。 - 与
struct upid:包含关系,struct pid通过柔性数组存放多个struct upid,每个upid对应「一个命名空间 + 一个本地 PID」的绑定。 - 与
struct task_struct:多对一(进程主线程)/ 一对多(线程组),进程的核心控制块task_struct中包含指向struct pid的指针,通过它获取进程的所有 PID 信息。
核心价值:避免内核为同一个进程在不同命名空间中重复创建 PID 相关数据,实现全局统一管理,同时支撑 PID 命名空间的隔离性。
完整简化版定义(含辅助结构体)
struct pid 定义在 include/linux/pid.h,以下是去除调试、兼容字段后的简化版,保留核心逻辑,便于新手理解:

核心字段深度解析
1. 辅助结构体 struct upid
它是「本地 PID」与「PID 命名空间」的绑定载体,没有独立的生命周期,依附于 struct pid 存在。
nr:本地 PID 编号,无全局唯一性,仅在ns指向的命名空间内唯一(比如容器 A 和容器 B 的upid->nr都可以是 1)。ns:反向关联到所属的struct pid_namespace,通过它可以获取该命名空间的idr、child_reaper等信息,是实现「本地 PID → 命名空间」关联的关键。pid_chain:用于将upid挂载到内核全局哈希表pid_hash中。内核通过「upid->nr+upid->ns」可以快速计算哈希值,进而找到对应的upid,再通过upid反向找到struct pid(因为upid是struct pid的成员,可通过容器指针转换获取struct pid地址)。
2. 核心结构体 struct pid
它是全局唯一的,一个进程(或线程组)对应一个 struct pid,核心字段的关键细节如下:
-
count:引用计数(新版内核用refcount_t替代旧版atomic_t,更安全)。- 引用场景:进程自身、其他进程通过
kill()等系统调用引用、内核模块查询进程信息时,都会让计数自增。 - 释放场景:当所有引用都解除(进程完全退出,无其他内核组件引用),计数归 0,内核调用
kfree()释放struct pid及其柔性数组的内存。 - 核心作用:防止进程在被引用过程中,其
struct pid被意外释放,导致内核空指针异常。
- 引用场景:进程自身、其他进程通过
-
level:PID 命名空间层级深度,决定了柔性数组numbers的长度。- 层级计算规则:从进程当前所属的子命名空间,向上遍历到根命名空间,遍历的次数就是
level值(根命名空间level=0,子命名空间level=1,孙子命名空间level=2)。 - 核心作用:确定柔性数组
numbers需要存放的upid数量(level + 1),因为进程会隶属于从当前子命名空间到根命名空间的所有层级,每个层级都需要一个upid。
- 层级计算规则:从进程当前所属的子命名空间,向上遍历到根命名空间,遍历的次数就是
-
numbers[1]:柔性数组(Flexible Array Member),这是内核中优化内存使用的常用技巧,也是理解struct pid的关键。- 语法细节:定义时写
[1],但实际分配内存时,不会只分配 1 个struct upid的空间,而是根据level + 1计算总大小:sizeof(struct pid) + level * sizeof(struct upid)。 - 存储规则:数组中的
upid按「命名空间层级从高到低」排列(先存当前子命名空间,再存父命名空间,最后存根命名空间),方便内核遍历查找。 - 示例:一个进程在
level=2的孙子命名空间中,numbers数组有 3 个upid,分别对应「孙子命名空间(本地 PID)、子命名空间(本地 PID)、根命名空间(本地 PID)」。
- 语法细节:定义时写
核心工作流程
场景 1:创建进程时,struct pid 的初始化流程
当通过 fork()/clone() 创建新进程时,内核会初始化 struct pid,步骤如下:
- 确定命名空间层级:从当前进程的
pid_namespace出发,向上遍历到根命名空间,获取level值。 - 分配内存:根据
level + 1计算柔性数组所需空间,调用kzalloc()分配struct pid内存(初始化所有字段为 0)。 - 初始化基础字段:将
count引用计数设为 1,将level设为获取到的层级深度。 - 为每个命名空间分配本地 PID 并初始化
upid:- 从当前子命名空间到根命名空间,依次调用
alloc_local_pid()(内部通过idr_alloc()分配)获取本地 PID。 - 逐个初始化
numbers数组中的upid,填充nr(本地 PID)和ns(对应命名空间)。 - 将每个
upid通过pid_chain挂载到全局哈希表pid_hash中。
- 从当前子命名空间到根命名空间,依次调用
- 绑定到进程实体:将
struct pid指针存入新进程的task_struct->pid字段,完成关联。
场景 2:通过本地 PID 查找 struct pid(如 kill 命令)
当在某个命名空间内执行 kill 123 时,内核通过 struct pid 找到目标进程,步骤如下:
- 获取参数与命名空间:解析目标本地 PID(123),获取当前调用进程的
pid_namespace。 - 两种查找方式(内核二选一):
- 方式 1(快速查找):通过
pid_namespace->idr,调用idr_find(),直接通过本地 PID 找到struct pid。 - 方式 2(全局查找):通过「本地 PID + 命名空间」计算哈希值,遍历全局哈希表
pid_hash,找到对应的upid,再通过容器指针转换获取struct pid。
- 方式 1(快速查找):通过
- 验证有效性:检查
struct pid的count引用计数,确保其未被释放。 - 找到进程实体:通过
struct pid找到对应的task_struct,发送信号(如SIGTERM),完成kill操作。
关键要点
-
struct pid与struct task_struct的区别:struct pid:仅负责 PID 相关信息(各命名空间本地 PID、命名空间关联),体积小,全局唯一,生命周期与进程的 PID 资源绑定。struct task_struct:进程的「完整控制块」,包含运行状态、内存布局、文件描述符、信号等所有信息,体积大,生命周期与进程的运行状态绑定。- 关联:
task_struct->pid指向struct pid,struct pid可通过内核函数pid_task()找到对应的task_struct。
-
柔性数组
numbers为什么不直接定义为动态数组:- 内核中尽量避免二次内存分配(先分配
struct pid,再分配upid数组),柔性数组可以一次性分配连续内存,提高访问速度,同时减少内存碎片。 - 连续内存布局让内核在遍历
upid时,无需跨内存块,提升缓存命中率,优化性能。
- 内核中尽量避免二次内存分配(先分配
-
struct pid对线程组的支持:- 线程组中,所有线程共享同一个
struct pid(对应线程组的主线程 PID,即「进程 PID」)。 - 每个线程有自己的
struct task_struct,但它们的task_struct->tgid(线程组 ID)都指向同一个struct pid,实现线程组的 PID 统一管理。
- 线程组中,所有线程共享同一个
总结
struct pid是 Linux 内核全局唯一的 PID 信息汇总结构体,核心作用是统一管理进程在所有 PID 命名空间中的本地 PID 标识。- 核心字段包括
count(引用计数)、level(命名空间层级)、numbers(柔性数组,存放upid),其中柔性数组是支撑多命名空间 PID 管理的关键。 - 它通过
struct upid绑定「本地 PID」与「PID 命名空间」,通过与pid_namespace->idr、全局哈希表的配合,实现 PID 的高效分配与查找,是容器技术 PID 隔离的核心支撑。








