首页
/ Linux 内核 DRBD 状态机与数据流程图详解:从 figures 文档到块设备复制内核实现

Linux 内核 DRBD 状态机与数据流程图详解:从 figures 文档到块设备复制内核实现

2026-09-06 18:02:27作者:毕习沙Eudora

本篇技术文章围绕 Linux 内核仓库中的 DRBD 图形文档 展开。该文档通过 5 张图(2 张数据流/写包时序 SVG、3 张状态转移 Graphviz 源文件)刻画了 DRBD——内核内置的共享无、同步复制块设备——的连接/磁盘/对端三层状态机与网络数据包交互流程。读完本文,你将能够完整解读 DRBD 的 Connection、Disk、Peer 三种状态机的每一条迁移边及其触发条件,理解 resync 与写包路径中的关键数据包(如 CsumRSRequestRSDataReplyDataRequest)在时序图中的位置,并把这些图与当前内核 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 ForWFConnection(等待连接建立)、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 三个子态),并补充了 UnconnectedTearDownWFSyncUUID 等中间状态以及 “drbdadm connect / disconnect” 触发标签,可以视为 8 代状态机在 DRBD-9 时代的扩展版,二者可对照阅读。

当前内核源码佐证:在 drbd_strings.c 中可以确认这些状态名仍被内核使用,例如第 25 行 [C_WF_CONNECTION] = "WFConnection"、第 28 行 [C_CONNECTED] = "Connected",即图中 WFConnectionConnected 等节点名与内核枚举到字符串的映射一一对应;错误字符串如 [[-SS_CONNECTED_OUTDATES]] = "Refusing to be Outdated while Connected"(第 67 行)则反映了状态迁移中的一致性约束。状态机的判定与迁移逻辑集中在 drbd_state.cdrbd_state.hdrbd_state_change.h;网络错误回到 WFConnection 的重连处理在 drbd_worker.c 中。

磁盘状态机(Disk State Machine)

disk-states-8.dotdigraph 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_* 枚举映射为 ConsistentUpToDateInconsistentOutdatedFailed 等用户可见名称,状态判定函数位于 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.cUnknown 的判定逻辑同样在 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.henum pkt_typeP_DATA_REQUESTP_DATA_REPLYP_RS_REQUESTP_RS_DATAP_BITMAPP_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 双线程模型与 CsumRSRequestDataRequestRSDataReply 等线上协议包落到具体函数调用上。配合 drivers/block/drbd/ 下的 drbd_state.cdrbd_strings.cdrbd_protocol.hdrbd_receiver.cdrbd_req.c 以及 data-structure-v9.rst,即可把“图上的每一条边”映射到“源码中的每一次状态赋值”,这正是该文档 “help understand the implementation” 承诺的阅读路径。

登录后查看全文
热门项目推荐
相关项目推荐