Note
此文件的目的是为让中文读者更容易阅读和理解,而不是作为一个分支。 因此, 如果您对此文件有任何意见或更新,请先尝试更新原始英文文件。 如果您发现本文档与原始文件有任何不同或者有翻译问题,请发建议或者补丁给 该文件的译者,或者请求中文文档维护者和审阅者的帮助。
- Original:
Documentation/filesystems/gfs2-glocks.rst
- 翻译:
邵明寅 Shao Mingyin <shao.mingyin@zte.com.cn>
- 校译:
杨涛 yang tao <yang.tao172@zte.com.cn>
Glock 内部加锁规则¶
本文档阐述 glock 状态机内部运作的基本原理。每个 glock(即
fs/gfs2/incore.h 中的 struct gfs2_glock)包含两把主要的内部锁:
自旋锁(gl_lockref.lock):用于保护内部状态(如 gl_state、gl_target)和持有者列表(gl_holders)
非阻塞的位锁(GLF_LOCK):用于防止其他线程同时调用 DLM 等操作。若某线程获取此锁,则在释放时必须调用 run_queue(通常通过工作队列),以确保所有待处理任务 得以完成。
gl_holders 列表包含与该 glock 关联的所有排队锁请求(不 仅是持有者)。若存在已持有的锁,它们将位于列表开头的连 续条目中。锁的授予严格遵循排队顺序。
glock 层用户可请求三种锁状态:共享(SH)、延迟(DF)和 排他(EX)。它们对应以下 DLM 锁模式:
Glock 模式 |
DLM |
锁模式 |
|---|---|---|
UN |
IV/NL |
未加锁(无关联的 DLM 锁)或 NL |
SH |
PR |
受保护读(Protected read) |
DF |
CW |
并发写(Concurrent write) |
EX |
EX |
排他(Exclusive) |
因此,DF 本质上是一种与“常规”共享锁模式(SH)互斥的共 享模式。在 GFS2 中,DF 模式专用于直接 I/O 操作。Glock 本质上是锁加缓存管理例程的组合,其缓存规则如下:
Glock 模式 |
缓存元数据 |
缓存数据 |
脏数据 |
脏元数据 |
|---|---|---|---|---|
UN |
否 |
否 |
否 |
否 |
DF |
是 |
否 |
否 |
否 |
SH |
是 |
是 |
否 |
否 |
EX |
是 |
是 |
是 |
是 |
这些规则通过为每种 glock 定义的操作函数实现。并非所有 glock 类型都使用全部的模式,例如仅 inode glock 使用 DF 模 式。
glock 操作函数及类型常量说明表:
字段 |
用途 |
|---|---|
go_sync |
远程状态变更前调用(如同步脏数据) |
go_xmote_bh |
远程状态变更后调用(如刷新缓存) |
go_inval |
远程状态变更需使缓存失效时调用 |
go_instantiate |
获取 glock 时调用 |
go_held |
每次获取 glock 持有者时调用 |
go_dump |
为 debugfs 文件打印对象内容,或出错时将 glock 转储至日志 |
go_callback |
若 DLM 发送回调以释放此锁时调用 |
go_unlocked |
当 glock 解锁时调用( |
go_type |
glock 类型, |
go_flags |
若 glock 关联地址空间,则设置GLOF_ASPACE 标志 |
每种锁的最短持有时间是指在远程锁授予后忽略远程降级请求 的时间段。此举旨在防止锁在集群节点间持续弹跳而无实质进 展的情况,此现象常见于多节点写入的共享内存映射文件。通 过延迟响应远程回调的降级操作,为用户空间程序争取页面取 消映射前的处理时间。
未来计划将 glock 的 “EX” 模式设为本地共享,使本地锁通 过 i_mutex 实现而非 glock。
glock 操作函数的加锁规则:
操作 |
GLF_LOCK 位锁持有 |
gl_lockref.lock 自旋锁持有 |
|---|---|---|
go_sync |
是 |
否 |
go_xmote_bh |
是 |
否 |
go_inval |
是 |
否 |
go_instantiate |
否 |
否 |
go_held |
否 |
否 |
go_dump |
有时 |
是 |
go_callback |
有时(N/A) |
是 |
go_unlocked |
是 |
否 |
Note
若入口处持有锁则操作期间不得释放位锁或自旋锁。 go_dump 和 do_demote_ok 严禁阻塞。 仅当 glock 状态指示其缓存最新数据时才会调用 go_dump。
GFS2 内部的 glock 加锁顺序:
i_rwsem(如需要)
重命名 glock(仅用于重命名)
Inode glock (父级优先于子级,同级 inode 按锁编号排序)
Rgrp glock(用于(反)分配操作)
事务 glock(通过 gfs2_trans_begin,非读操作)
i_rw_mutex(如需要)
页锁(始终最后,至关重要!)
每个 inode 对应两把 glock:一把管理 inode 本身(加锁顺 序如上),另一把(称为 iopen glock)结合 inode 的 i_nlink 字段决定 inode 生命周期。inode 加锁基于单个 inode,rgrp 加锁基于单个 rgrp。通常优先获取本地锁再获 取集群锁。
Glock 统计¶
统计分为两类:超级块相关统计和单个 glock 相关统计。超级 块统计按每 CPU 执行以减少收集开销,并进一步按 glock 类 型细分。所有时间单位为纳秒。
超级块和 glock 统计收集相同信息。超级块时序统计为 glock 时序统计提供默认值,使新建 glock 具有合理的初始值。每个 glock 的计数器在创建时初始化为零,当 glock 从内存移除时 统计丢失。
统计包含三组均值/方差对及两个计数器。均值/方差对为平滑 指数估计,算法与网络代码中的往返时间计算类似(参见《 TCP/IP详解 卷1》第21.3节及《卷2》第25.10节)。与 TCP/IP 案例不同,此处均值/方差未缩放且单位为整数纳秒。
三组均值/方差对测量以下内容:
DLM 锁时间(非阻塞请求)
DLM 锁时间(阻塞请求)
请求间隔时间(指向 DLM)
非阻塞请求指无论目标 DLM 锁处于何种状态均能立即完成的请求。 当前满足条件的请求包括:(a)锁当前状态为互斥(如锁降级)、 (b)请求状态为空置或解锁(同样如锁降级)、或(c)设置”try lock” 标志的请求。其余锁请求均属阻塞请求。
- 两个计数器分别统计:
锁请求总数(决定均值/方差计算的数据量)
glock 代码顶层的持有者排队数(通常远大于 DLM 锁请求数)
为什么收集这些统计数据?我们需深入分析时序参数的动因如下:
更精准设置 glock “最短持有时间”
快速识别性能问题
改进资源组分配算法(基于锁等待时间而非盲目 “try lock”)
因平滑更新的特性,采样量的阶跃变化需经 8 次采样(方差需 4 次)才能完全体现,解析结果时需审慎考虑。
通过锁请求完成时间和 glock 平均锁请求间隔时间,可计算节 点使用 glock 时长与集群共享时长的占比,对设置锁最短持有 时间至关重要。
我们已采取严谨措施,力求精准测量目标量值。任何测量系统均 存在误差,但我期望当前方案已达到合理精度极限。
超级块状态统计路径:
/sys/kernel/debug/gfs2/<fsname>/sbstats
Glock 状态统计路径:
/sys/kernel/debug/gfs2/<fsname>/glstats
(假设 debugfs 挂载于 /sys/kernel/debug,且 <fsname> 替 换为对应 GFS2 文件系统名)
输出缩写说明:
srtt |
非阻塞 DLM 请求的平滑往返时间 |
srttvar |
srtt 的方差估计 |
srttb |
(潜在)阻塞 DLM 请求的平滑往返时间 |
srttvarb |
srttb 的方差估计 |
sirt |
DLM 请求的平滑请求间隔时间 |
sirtvar |
sirt 的方差估计 |
dlm |
DLM 请求数(glstats 文件中的 dcnt) |
queue |
排队的 glock 请求数(glstats 文件中的 qcnt) |
sbstats文件按glock类型(每种类型8行)和CPU核心(每CPU一列) 记录统计数据集。glstats文件则为每个glock提供统计集,其格式 与glocks文件类似,但所有时序统计量均采用均值/方差格式存储。
gfs2_glock_lock_time 跟踪点实时输出目标 glock 的当前统计 值,并附带每次接收到的dlm响应附加信息:
status |
DLM 请求状态 |
flags |
DLM 请求标志 |
tdiff |
该请求的耗时 |
(其余字段同上表)