这是一次极度纯粹的 C 语言内核级内存推演。
屏蔽掉操作系统调度、屏蔽掉业务 SQL,直接进入 PostgreSQL 的共享内存(Shared Memory)深处。假设此时 PGPROC_1(业务进程 1)因为某种原因(例如申请 8 级强锁,或 Fast Path 已满),必须去全局哈希表中申请 LOCK_A(某张表的物理锁),并且最终申请成功(无冲突)。
将时间的流速放慢到 CPU 指令周期级别,分 5 个微观物理帧(Frame),把这三大结构体(PGPROC、LOCK、PROCLOCK)的内存指针是如何完成完美咬合并改变状态的过程,一行代码一行代码地推演出来。
第 1 帧:寻址与靶标锁定(LOCK 结构体的确认)
【物理起点】:PGPROC_1 带着一个目标(比如表 OID = 10000 的 8 级排他锁请求)冲进锁管理器。
- 哈希计算:CPU 提取表 OID 10000,经过哈希函数计算出一个 Hash Key。
- 第一张哈希表寻址:CPU 拿着 Hash Key,在
LockMethodLockHash(全局资源哈希表)中进行 $O(1)$ 查找。 - 靶标确立:
- 假设表不存在:如果这是系统第一次访问该表,内核会调用
ShmemAlloc()在共享内存中动态分配一个LOCK_A结构体,并将其初始化(核心字段grantMask = 0,代表没有任何人持有锁;procLocks链表头为空)。 - 假设表已存在:直接命中内存地址,获取到
LOCK_A结构体的指针。
- 假设表不存在:如果这是系统第一次访问该表,内核会调用
【当前内存图景】:PGPROC_1 的指针和 LOCK_A 的指针在内存中隔空相望,尚未发生任何物理联系。
第 2 帧:契约的物理铸造(PROCLOCK 结构体的诞生)
既然 PGPROC_1 想要 LOCK_A,必须在它们之间建立合法的“租赁契约”。
- 组合哈希键:CPU 将
PGPROC_1的内存地址指针和LOCK_A的内存地址指针,拼接在一起,作为联合 Hash Key。 - 第二张哈希表寻址:CPU 拿着这个联合 Key,冲向
LockMethodProcLockHash(契约哈希表)。 - 分配内存:由于这是
PGPROC_1第一次申请LOCK_A,哈希表中必定找不到记录。内核立刻在共享内存中malloc出一个全新的结构体:PROCLOCK_1A。
【底层指针的硬性绑定】:
在 PROCLOCK_1A 诞生的第一微秒,内核立刻对其内部的两个常量指针进行不可逆的赋值:
PROCLOCK_1A -> tag.myLock = LOCK_A(指向上游的资源靶标)PROCLOCK_1A -> tag.myProc = PGPROC_1(指向下游的所属进程)
此时,桥梁的物理墩柱已经打好,但桥面尚未通车(锁还没批下来)。
第 3 帧:纳秒级生死判决(状态掩码的位运算)
这是决定“申请成功”的最核心 CPU 指令。
- 提取请求掩码:
PGPROC_1申请的是 8 级锁,CPU 提取 8 级锁对应的内部二进制位(假设为10000000)。 - 读取资源状态:CPU 顺着指针看向
LOCK_A -> grantMask。因为此时没有别人持有冲突锁,假设grantMask为00000000。 - 按位与(Bitwise AND)判定:
10000000 (请求位) & 00000000 (当前状态位) = 0 - 判决结果:结果等于 0!内核判定:绝对兼容,无冲突,予以放行!
第 4 帧:状态的物理突变(掩码改写)
既然判决成功,内存中的结构体必须立刻留下物理痕迹,以防接下来的其他并发进程“硬闯”。
- 改写契约状态(局部宣誓):CPU 找到刚刚建好的
PROCLOCK_1A结构体,将其内部的holdMask(当前契约持有的锁级别)的第 8 个比特位强行拨为1。- 物理变化:
PROCLOCK_1A -> holdMask |= 10000000。
- 物理变化:
- 改写资源状态(全局宣誓):CPU 顺着指针回到
LOCK_A结构体,将其内部的grantMask(全局授权状态)的第 8 个比特位强行拨为1。- 物理变化:
LOCK_A -> grantMask |= 10000000。
- 物理变化:
【推演意义】:从这一微秒开始,如果全库有任何其他进程再来查询 LOCK_A,它们在进行位运算时,就会因为这个 grantMask 中的 1 而发生冲突并被物理拦截。
第 5 帧:双向十字链表的编织(拓扑图闭合)
结构体建好了,状态也改了,最后一步是把它们“串”起来,保证系统能在 $O(1)$ 时间内进行双向追溯。PG 在这里使用了极其经典的 SHM_QUEUE(共享内存双向环形链表)。
- 串入资源的链表(物找人):CPU 将
PROCLOCK_1A内部的lockLink节点,通过前后指针的修改,强行插入到LOCK_A -> procLocks这条双向链表的头部。- 效果:以后系统只要找到
LOCK_A,顺着链表一撸,就能精准拔出PROCLOCK_1A,进而找到PGPROC_1。
- 效果:以后系统只要找到
- 串入进程的链表(人找物):CPU 将
PROCLOCK_1A内部的procLink节点,通过前后指针的修改,强行插入到PGPROC_1 -> myProcLocks这条双向链表的头部。- 效果:等到事务
COMMIT时,PGPROC_1根本不需要去全局哈希表盲找,直接顺着自己身上的这条链表,就能瞬间找到PROCLOCK_1A并将其销毁。
- 效果:等到事务
物理推演终点(Success)
至此,PGPROC_1 申请 LOCK_A 成功的全部底层物理动作彻底结束。
终极物理图景:
在内存中,PGPROC_1 和 LOCK_A 通过中间的 PROCLOCK_1A,被两根双向链表极其牢固地死死锁在一起。同时,LOCK_A 的二进制掩码灯(grantMask)已被点亮,向整个数据库实例宣告了它的排他性主权。执行引擎拿到绿灯,长驱直入,开始向底层物理磁盘下发真实的 I/O 修改指令。
了解 www.876873.xyz 的更多信息
订阅后即可通过电子邮件收到最新文章。
PGPROC_1 申请 LOCK_A 成功。内存中生成契约 PROCLOCK_1A(状态为 granted = true)。底层c语言结构体配合工作的流程::等您坐沙发呢!