Linux 内核 DRBD 状态机与数据流程图详解:从 figures 文档到块设备复制内核实现
本篇技术文章围绕 Linux 内核仓库中的 DRBD 图形文档 展开。该文档通过 5 张图(2 张数据流/写包时序 SVG、3 张状态转移 Graphviz 源文件)刻画了 DRBD——内核内置的共享无、同步复制块设备——的连接/磁盘/对端三层状态机与网络数据包交互流程。读完本文,你将能够完整解读 DRBD 的 Connection、Disk、Peer 三种状态机的每一条迁移边及其触发条件,理解 resync 与写包路径中的关键数据包(如 CsumRSRequest、RSDataReply、DataRequest)在时序图中的位置,并把这些图与当前内核 drivers/block/drbd/ 源码中的状态枚举、字符串表和接收处理函数一一对应起来。
DRBD 文档体系与 figures.rst 的坐标
在 DRBD 文档目录 中,官方对 DRBD 的定义是:
DRBD is a shared-nothing, synchronously replicated block device. It is designed to serve as a building block for high availability clusters and in this context, is a "drop-in" replacement for shared storage. Simplistically, you could see it as a network RAID 1.
即 DRBD 是一台“网络 RAID 1”:两台(或多台)主机各自持有本地块设备副本,DRBD 在块层面把每一次写同步到对端,从而让 HA 集群可以在对端节点上接管故障节点上的根文件系统或 LVM。
figures.rst 开头的注释说明其目的非常明确:“The here included files are intended to help understand the implementation”(这里包含的文件旨在帮助理解实现)。它通过 Sphinx 的 kernel-figure 指令嵌入了 5 个文件:
| 文件 | 类型 | 主题 |
|---|---|---|
| DRBD-8.3-data-packets.svg | Inkscape 矢量图 | 8.3 版数据流:resync 过程中的函数调用与数据包时序 |
| DRBD-data-packets.svg | Inkscape 矢量图 | 数据流:写请求相关的数据包 |
| conn-states-8.dot | Graphviz digraph | 连接状态机(Connection State)子图 |
| disk-states-8.dot | Graphviz digraph | 磁盘状态机(Disk State)子图 |
| peer-states-8.dot | Graphviz digraph | 对端状态机(Peer State)子图 |
这三张状态机子图对应的正是 DRBD 面向用户的核心三态模型:/proc/drbd(当前内核中由 drbd_proc.c 实现)里每个 resource 的 role:cs:ds:pe 三元组,例如 role:Primary/Secondary:cs:Connected:ds:UpToDate/ds:UpToDate:pe:0。以下小节逐图完整转述状态迁移表,并结合当前内核源码给出佐证。
需要说明的适用前提:文件名中的 -8 与图内函数名(见下文)表明这些图绘制于 DRBD 8.3 时代,而同目录的 data-structure-v9.rst 则描述自 Linux v3.14 起采用的 DRBD-9 内核数据结构。状态机的“骨架”(状态名与迁移语义)在两个代际中保持一致,但函数级细节已演化,这一点后文会具体指出。
连接状态机(Connection State Machine)
conn-states-8.dot 是一张 digraph conn_states,完整迁移关系如下(忠实转述全部 17 条边):
| 当前状态 | 迁移条件(边标签) | 目标状态 |
|---|---|---|
| StandAllone | ioctl_set_net() |
WFConnection |
| WFConnection | unable to bind() |
Unconnected |
| WFConnection | in connect() after accept |
WFReportParams |
| WFReportParams | checks in receive_param() |
StandAllone |
| WFReportParams | in receive_param() |
Connected |
| WFReportParams | sync_handshake() |
WFBitMapS |
| WFReportParams | sync_handshake() |
WFBitMapT |
| WFBitMapS | receive_bitmap() |
SyncSource |
| WFBitMapT | receive_bitmap() |
SyncTarget |
| SyncSource | (resync 完成) | Connected |
| SyncTarget | (resync 完成) | Connected |
| SyncSource | (管理员暂停) | PausedSyncS |
| SyncTarget | (管理员暂停) | PausedSyncT |
| PausedSyncS | (恢复) | SyncSource |
| PausedSyncT | (恢复) | SyncTarget |
| Connected | * on network error |
WFConnection |
读图要点:
- WF 前缀 = Wait For:
WFConnection(等待连接建立)、WFReportParams(等待对端报告参数)、WFBitMapS/T(等待位图交换,S=Source 源端、T=Target 目标端)。WFReportParams -> Connected这条边表示双方参数握手后确认“数据完全一致、无需 resync”,可直接进入 Connected;WFReportParams -> StandAllone表示握手中发现本质性分歧(peer 端回传参数检查失败)而断开回退。 - 双向的 WFBitMap 分支:哪一方数据更新,哪一方就成为 SyncSource(发送位图),另一方为 SyncTarget(接收位图并接收重同步数据)。
sync_handshake()依据双方 sync UUID 决定方向。 - PausedSyncS/T:resync 可以被管理动作暂停/恢复,这在多 TB 级设备上线不停服维护时非常有用。
Connected -> WFConnection on network error:任何一侧的网络错误都会让连接退回 WFConnection 重新尝试;反复失败最终落入 Unconnected。
同目录还有一张更完整的 drbd-connection-state-overview.dot(未嵌入 figures.rst,属于配套素材),它把通信故障显式建模为一个 CommTrouble 簇(Timeout / BrokenPipe / NetworkFailure 三个子态),并补充了 Unconnected、TearDown、WFSyncUUID 等中间状态以及 “drbdadm connect / disconnect” 触发标签,可以视为 8 代状态机在 DRBD-9 时代的扩展版,二者可对照阅读。
当前内核源码佐证:在 drbd_strings.c 中可以确认这些状态名仍被内核使用,例如第 25 行 [C_WF_CONNECTION] = "WFConnection"、第 28 行 [C_CONNECTED] = "Connected",即图中 WFConnection、Connected 等节点名与内核枚举到字符串的映射一一对应;错误字符串如 [[-SS_CONNECTED_OUTDATES]] = "Refusing to be Outdated while Connected"(第 67 行)则反映了状态迁移中的一致性约束。状态机的判定与迁移逻辑集中在 drbd_state.c、drbd_state.h 与 drbd_state_change.h;网络错误回到 WFConnection 的重连处理在 drbd_worker.c 中。
磁盘状态机(Disk State Machine)
disk-states-8.dot 的 digraph disk_states 完整迁移表(全部 14 条边):
| 当前状态 | 迁移条件(边标签) | 目标状态 |
|---|---|---|
| Diskless | ioctl_set_disk() |
Inconsistent |
| Diskless | ioctl_set_disk() |
Consistent |
| Diskless | ioctl_set_disk() |
Outdated |
| Consistent | receive_param() |
Outdated |
| Consistent | receive_param() |
UpToDate |
| Consistent | start resync |
Inconsistent |
| Outdated | start resync |
Inconsistent |
| UpToDate | ioctl_replicate |
Inconsistent |
| Inconsistent | resync completed |
UpToDate |
| Consistent | io completion error |
Failed |
| Outdated | io completion error |
Failed |
| UpToDate | io completion error |
Failed |
| Inconsistent | io completion error |
Failed |
| Failed | sending notify to peer |
Diskless |
读图要点:
- 语义区分:
Consistent表示“本副本自上次 resync 以来未再变更、可参与协商”;UpToDate表示“已追上源端,可作为 Primary 的最新副本”;Inconsistent表示“存在与源端不一致的区间,等待/正在 resync”;Outdated是协议 A/B 中落后的旧代副本(在receive_param()握手中由对端参数判定降级)。 ioctl_set_disk()的三分支对应drbdadm attach时的三种初始判定:全新盘(Inconsistent)、空且已抹零(Consistent)、带 outdated 标记的旧盘(Outdated)。UpToDate --ioctl_replicate--> Inconsistent:本地降级为 Secondary 复制模式时重新进入不一致窗口;与之对称的是--resync completed--> UpToDate。Failed是唯一的坏盘归宿:任何状态只要 IO 完成错误即可跌入 Failed,随后通过“向 peer 发送 notify”进入Diskless(节点失去本地盘,仅剩网络角色),这是 DRBD 作为 HA 构件吸收磁盘故障的关键路径。
当前内核中磁盘状态同样在 drbd_strings.c 的字符串表里以 DS_* 枚举映射为 Consistent、UpToDate、Inconsistent、Outdated、Failed 等用户可见名称,状态判定函数位于 drbd_state.c,与图中状态集合一致。
对端状态机(Peer State Machine)
peer-states-8.dot 最为精简,digraph peer_states 的全部 6 条边:
| 当前状态 | 迁移条件(边标签) | 目标状态 |
|---|---|---|
| Secondary | recv state packet |
Primary |
| Primary | recv state packet |
Secondary |
| Primary | connection lost |
Unknown |
| Secondary | connection lost |
Unknown |
| Unknown | connected |
Primary |
| Unknown | connected |
Secondary |
要点:
- Peer 状态描述的是对端节点在本节点视角下的角色(Primary/Secondary),而不是本节点角色。
- Primary/Secondary 的切换完全由“收到对端发来的 state packet”驱动:对端升级/降级时本节点的 peer 视图随之翻转。
- 连接丢失后对端角色不可知(
Unknown,/proc/drbd中显示为N/A);重新connected后按握手结果重新确定。 - 当前内核中 peer 状态由
PS_*枚举表达,映射字符串见 drbd_strings.c;Unknown的判定逻辑同样在 drbd_state.c 的状态计算函数中。
数据流与数据包时序图
figures.rst 的第一部分题为 “Data flows that Relate some functions, and write packets”,嵌入两张时序图。它们不是抽象架构图,而是把函数调用与线上数据包画在同一条时间轴上的实现级时序图。
DRBD-8.3-data-packets.svg:resync 路径的函数与包
解析 DRBD-8.3-data-packets.svg(Inkscape XML)中的文本元素,可以看到图中标注的函数与包名,例如:
w_make_resync_request()—— worker 端发起重同步请求;CsumRSRequest—— 校验和 resync 请求包(随包携带源端数据块的 CRC,目标端可借此跳过已一致的块,省得读盘比对);receive_DataRequest()/drbd_endio_read_sec()—— 对端接收写/读请求、本地盘 IO 完成后的收尾函数;w_e_end_csum_rs_req()—— 处理 CsumRSRequest 的收尾(决定是否真的发送数据);receive_RSDataReply()/RSDataReply—— 重同步数据应答包及其接收端处理。
这些名字中的 w_/receive_ 前缀对应 DRBD 的两个处理线程模型:worker 线程(处理请求队列、协议状态机)与 receiver 线程(网络收包分发)。在 DRBD 8.3 中它们分别位于 drbd_worker.c/drbd_receiver.c;在当前内核里,这两者已经重构为 drbd_req.c(请求处理,替代旧 worker 的角色)与 drbd_receiver.c(收包分发),因此 8.3 图中的函数名不能在当前源码中直接检索到——从源码结构看,图中语义(worker 发起 → receiver 收包 → 本地盘 IO 完成回包)仍然成立,只是命名空间变了。
DRBD-data-packets.svg:写请求的数据包
DRBD-data-packets.svg 描绘写路径上 Primary 与 Secondary 之间 DataRequest 请求-应答包对的流动。当前内核中协议包的完整枚举定义在 drbd_protocol.h(enum pkt_type:P_DATA_REQUEST、P_DATA_REPLY、P_RS_REQUEST、P_RS_DATA、P_BITMAP、P_SYNC_UUID 等),收包入口在 drbd_receiver.c,Primary 侧请求组装在 drbd_req.c,位图(决定 resync 哪些 4KB 区间不一致)的维护在 drbd_bitmap.c。对照图上的包名读源码,可以验证“图中每个箭头 = 协议中的一个 pkt_type = receiver 中的一个 receive_* 分支”的对应关系。
三态模型与 DRBD-9 数据结构的关系
理解这三张状态机子图之后,建议接着读 data-structure-v9.rst:它说明自 Linux v3.14 起 DRBD 使用新的内核对象模型——节点包含若干 drbd_resource,每个 resource 拥有若干 drbd_device(即 volume,本地对应一个块设备)与若干 drbd_connection(到 peer node 的连接),而 drbd_peer_device 恰好落在 device × connection 矩阵的每个交叉点上;对象之间以双向链表互联,resource/device/connection 带引用计数。
这解释了状态机的“粒度”:Connection/Disk/Peer 三种状态实际都定义在 peer_device(设备×连接交叉点)这一层级上——同一个设备挂两条连接时会有两份连接状态;同一节点上的多个设备又各有各的磁盘状态。/proc/drbd 输出中 cs:、ds: 后斜杠分隔的多段值正是多 device/多 connection 的展开。资源管理入口可见 drbd_main.c,netlink 配置通道见 drbd_nl.c。
如何复用这些图
- 三张状态机是标准 Graphviz DOT 源码(
.dot),可直接用dot -Tpng conn-states-8.dot一类命令重新渲染,或在 IDE 中预览,便于在自己的文档/幻灯中复用; - 两张时序图是 Inkscape SVG,XML 文本中保留了全部函数名/包名标注(见上文引用示例),即使不便渲染也可按文本元素提取内容;
- 文档构建侧,
kernel-figure指令由内核文档框架处理 SVG/dot 的渲染与 alt 文本注入,参考 Documentation 的 sphinx 配置。
小结
figures.rst 虽然只有寥寥数行指令,但它索引的 5 个图形文件构成理解 DRBD 内核实现的“地图”:连接状态机回答了“握手、位图交换、resync 挂起与网络错误回退”的完整生命周期;磁盘状态机回答了“attach 初判、resync 前后、坏盘降级”的介质生命周期;对端状态机回答了“远端角色如何随状态包翻转”;两张数据包时序图则把 worker/receiver 双线程模型与 CsumRSRequest、DataRequest、RSDataReply 等线上协议包落到具体函数调用上。配合 drivers/block/drbd/ 下的 drbd_state.c、drbd_strings.c、drbd_protocol.h、drbd_receiver.c、drbd_req.c 以及 data-structure-v9.rst,即可把“图上的每一条边”映射到“源码中的每一次状态赋值”,这正是该文档 “help understand the implementation” 承诺的阅读路径。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00