首页
/ Linux DRBD-9 内核对象模型详解:resource、connection、device 与 peer_device 矩阵结构

Linux DRBD-9 内核对象模型详解:resource、connection、device 与 peer_device 矩阵结构

2026-09-06 17:57:42作者:宗隆裙

DRBD(Distributed Replicated Block Device)是 Linux 内核中一个"共享无、同步复制"的块设备驱动,常被视作"网络 RAID 1",是构建高可用集群时替代共享存储的基础构件。本文以内核文档 data-structure-v9.rst 为核心,完整讲解 DRBD-9 在内核中的四类核心对象(resource / connection / device / peer_device)的组织方式、它们之间的矩阵式互联关系,以及双链表与 idr 两套索引机制,并结合 drivers/block/drbd 目录下的真实源码实现(结构体定义、全局链表、遍历宏)逐层印证文档所述的设计。读完后,你将能够理解 DRBD 设备树的内核对象布局、各对象的引用计数规则,以及在代码中如何按卷号(volume number)或次设备号(minor number)快速定位对象。

1. DRBD 的对象层级:resource → device / connection → peer_device

文档开篇给出的基本模型是:一个节点上可以有多个 DRBD resource(资源);每个 resource 下有多个 device(设备,也称 volume,卷),以及到其它节点("peer nodes",对端节点)的 connection(连接)。每个 DRBD device 在本地都表现为一个块设备(即用户空间看到的 /dev/drbdX)。

用一句话概括层级关系:

  • resource 是顶层容器,对应一组逻辑上关联的卷和一条(或多条)对端连接;
  • device 表示本节点的一个卷,对外是一个块设备,由次设备号 minor 唯一标识;
  • connection 表示本节点与某个对端节点之间的一条网络连接;
  • peer_device 表示"本设备的某个卷"在"某条连接的远端"的映像,它坐落在 device 与 connection 的每一个交叉点上。

这个模型在 Kconfig 帮助文本中也有呼应:每个 minor 设备有 'primary' 或 'secondary' 角色,主节点上的应用通过 /dev/drbdX 访问数据,每次写入都会发送到本地"下层块设备",并同时通过网络发往处于 'secondary' 状态的对端节点,可参见 drivers/block/drbd/Kconfig

2. 矩阵结构:peer_device 位于 device × connection 的交叉点

文档最核心的部分是用一张矩阵图描述对象互联方式:

  /--------------+---------------+.....+---------------\
  |   resource   |    device     |     |    device     |
  +--------------+---------------+.....+---------------+
  |  connection  |  peer_device  |     |  peer_device  |
  +--------------+---------------+.....+---------------+
  :              :               :     :               :
  :              :               :     :               :
  +--------------+---------------+.....+---------------+
  |  connection  |  peer_device  |     |  peer_device  |
  \--------------+---------------+.....+---------------/

解读这张图需要把握三条规则(均为文档原文的要点,且可逐条在源码中得到印证):

  1. 横向(同一行):device 可以从其 resource 按**卷号(volume number)**索引到;同理,peer_device 可以从其 connection 按卷号索引到。
  2. 纵向(同一列/行方向):对象之间通过双向链表互联。
  3. 回指针(back pointers):peer_device 指回它所属的 connection 和 device;connection 和 device 都指回它们的 resource。

与文档对照 drivers/block/drbd/drbd_int.hstruct drbd_peer_device 的定义,回指针的说法完全成立:

struct drbd_peer_device {
	struct list_head peer_devices;	/* 挂入 device->peer_devices 链表 */
	struct drbd_device *device;		/* 回指针:指向本地 device */
	struct drbd_connection *connection;	/* 回指针:指向所属 connection */
	struct work_struct send_acks_work;
	...
};

而"横向按卷号索引"在源码中由 idr(ID Radix tree) 实现:

  • struct drbd_resource 中有 struct idr devices; /* volume number to device mapping */,见 drbd_int.h#L595
  • struct drbd_connection 中有 struct idr peer_devices; /* volume number to peer device mapping */,见 drbd_int.h#L630

查找入口是 drbd_int.h 中的内联函数 conn_peer_device()

static inline struct drbd_peer_device *
conn_peer_device(struct drbd_connection *connection, int volume_number)
{
	return idr_find(&connection->peer_devices, volume_number);
}

这正是文档所说"peer_devices can be accessed from connections by their volume number"的代码级实现。

3. 全局索引:drbd_resources 链表与 drbd_devices idr

文档指出,除了矩阵内部关系外还有两套全局索引:

  • 所有 resource 都挂在 drbd_resources 这条双向链表中;
  • 所有 device 可以通过次设备号(minor device number)经 drbd_devices 这个 idr 查找到。

源码中这两个全局对象在 drivers/block/drbd/drbd_main.c 定义,并在 drbd_int.h 中导出,注释明确写明了并发约束——RCU 保护、更新需持有 genl_lock()

/* drbd_int.h */
extern struct idr drbd_devices; /* RCU, updates: genl_lock() */
extern struct list_head drbd_resources; /* RCU, updates: genl_lock() */

按 minor 查找 device 的入口是 drbd_int.h#L942-L945

static inline struct drbd_device *minor_to_device(unsigned int minor)
{
	return (struct drbd_device *)idr_find(&drbd_devices, minor);
}

resource 的创建与入链过程在 drbd_main.cdrbd_create_resource() 中可以看到全貌:分配对象、kref_init() 初始化引用计数、idr_init(&resource->devices) 初始化卷号索引、INIT_LIST_HEAD(&resource->connections) 初始化连接链表,最后 list_add_tail_rcu(&resource->resources, &drbd_resources) 把新 resource 以 RCU 方式挂到全局链表尾部——与文档"All resources are in the drbd_resources double-linked list"一一对应。

此外,DRBD 代码提供了一整套遍历宏来沿这些结构游走,均定义在 drbd_int.h#L958-L983

#define for_each_resource(resource, _resources) \
	list_for_each_entry(resource, _resources, resources)

#define for_each_resource_rcu(resource, _resources) \
	list_for_each_entry_rcu(resource, _resources, resources)

#define for_each_connection(connection, resource) \
	list_for_each_entry(connection, &resource->connections, connections)

#define for_each_peer_device(peer_device, device) \
	list_for_each_entry(peer_device, &device->peer_devices, peer_devices)

每一组都有普通 / _rcu / _safe 三个变体,分别对应直接遍历、RCU 读侧遍历和遍历中允许删除的安全遍历。这解释了文档所说的"objects are connected by double linked lists"在工程上是如何被安全地消费的:读路径(如 drbd_main.cfor_each_resource_rcu())无锁,写路径(如 drbd_main.c#L2333for_each_resource_safe())持锁删除。

4. 引用计数与对象生命周期

文档最后一段明确了四类对象的生命周期规则,这是理解整个对象模型"谁拥有谁"的关键:

drbd_resource、drbd_connection、drbd_device 对象是引用计数的;peer_device 对象仅用于在 device 与 connection 之间建立链接,其生命周期由它所引用的 device 和 connection 的生命周期决定。

对应源码证据:

  • struct drbd_resource 内嵌 struct kref kref;drbd_int.h#L594);
  • struct drbd_connection 内嵌 struct kref kref;drbd_int.h#L629);
  • struct drbd_device 内嵌 struct kref kref;drbd_int.h#L768);
  • struct drbd_peer_device没有 kref 成员,它只有 list_head、两个回指针和一个工作项(drbd_int.h#L739-L747)。

也就是说,peer_device 是一个"关系对象":只要某个 device 还存在、某条 connection 还连着重,它们交叉点上的 peer_device 就存在;device 或 connection 任一被销毁,peer_device 随之消失。这种设计让矩阵的规模(卷数 × 连接数)不需要额外的生命周期管理代码。

5. 结构体要点速查:从源码看每类对象持有什么

为便于把文档概念落到代码上,下面按文档骨架列出四类对象的关键成员(摘录自 drivers/block/drbd/drbd_int.h):

5.1 struct drbd_resource(drbd_int.h#L586-L610

成员 作用
char *name 资源名
struct kref kref 引用计数
struct idr devices 卷号 → device 的映射(矩阵"横向"索引)
struct list_head connections 该资源的连接双向链表(矩阵"纵向")
struct list_head resources 挂入全局 drbd_resources 链表的节点
struct res_opts res_opts 资源级配置
struct mutex conf_update / adm_mutex 配置热更新、管理操作串行化
unsigned susp:1; susp_nod:1; susp_fen:1; 三种挂起状态:用户挂起、无数据挂起、fence 处理挂起
enum write_ordering_e write_ordering 写排序策略(创建时默认 WO_BDEV_FLUSH,见 drbd_main.c#L2528
cpumask_var_t cpu_mask 该资源线程绑定的 CPU 掩码

5.2 struct drbd_connection(drbd_int.h#L621-L707

成员 作用
struct list_head connections 挂入 resource->connections 链表的节点
struct drbd_resource *resource 回指针:指向所属资源
struct kref kref 引用计数
struct idr peer_devices 卷号 → peer_device 的映射(矩阵"横向"索引)
enum drbd_conns cstate 连接状态(文档说明取值范围为 C_STANDALONE 至 C_WF_REPORT_PARAMS)
struct net_conf *net_conf RCU 保护的网络配置
struct drbd_socket data / meta 双通道套接字:数据/屏障/状态包 与 ping/ack 元数据包分离
struct list_head transfer_log 尚未完全处理完的请求(Transfer Log)
struct drbd_thread receiver / worker / ack_receiver 接收线程、工作线程、ack 接收线程
struct drbd_work_queue sender_work 发送侧工作队列

可以看到一条 connection 同时承载了"矩阵纵列"的角色(connections 链表 + resource 回指针 + peer_devices idr)与完整的网络收发基础设施。

5.3 struct drbd_device(drbd_int.h#L749-L839

成员 作用
struct drbd_resource *resource 回指针:指向所属资源
struct list_head peer_devices 该设备上所有 peer_device 的双向链表(矩阵"纵向")
unsigned int vnr 卷号(volume number within the connection)
unsigned int minor 次设备号,是全局 drbd_devices idr 的键
struct kref kref 引用计数
struct drbd_backing_dev *ldev 下层块设备(drbdsetup 配置)
struct request_queue *rq_queue / struct gendisk *vdisk 暴露给块层的请求队列与虚拟磁盘(即 /dev/drbdX 背后的 gendisk)
union drbd_dev_state state 磁盘状态(D_ 前缀)集合
atomic_t ap_pending_cnt 等一组原子计数 in-flight 请求统计(网络在途、等待本地完成等)
struct rb_root read_requests / write_requests 未完成本地请求的区间树

5.4 struct drbd_peer_device

如第 2 节所示,仅四个实质成员:peer_devices 链表节点、device 回指针、connection 回指针、send_acks_work。它是纯粹的连接关系载体。

6. 把文档模型落到运行时的三条查找路径

综合以上,文档描述的矩阵模型对应三条最常用运行路径,均在源码中可直接验证:

  1. 用户态 → device:内核接口拿到 minor 后调用 minor_to_device(minor),经全局 drbd_devices idr 直接定位(O(log N)),见 drbd_int.h#L942-L945
  2. resource → device(按卷号)idr_find(&resource->devices, volume_number) 语义的 idr 查找;resource->devicesdrbd_create_resource() 中初始化(drbd_main.c#L2526)。
  3. connection → peer_device(按卷号)conn_peer_device(connection, volume_number)drbd_int.h#L952-L956)。

反向的"游走"则靠双向链表与遍历宏:从 device 出发 for_each_peer_device() 找到它在所有连接上的映像,从 resource 出发 for_each_connection() 找到所有对端连接。例如 drbd_nl.c#L3755drbd_main.c#L2763 都采用了"先定位 device、再 for_each_peer_device 遍历其对端"的模式,正是矩阵纵列遍历的典型用法。

7. 小结:这套数据结构解决了什么问题

回到文档的动机——"Starting with Linux v3.14 we are reorganizing DRBD to use this data structure"(从 Linux v3.14 起,DRBD 重组为使用这套数据结构)。DRBD-9 的对象模型可以用三点总结:

  1. 矩阵化:device 与 connection 的笛卡尔积由 peer_device 承载,天然支持多卷(多 volume)× 多对端(含 dual-Primary、多节点组网)的场景;
  2. 双索引:横向用 idr 按卷号 O(log N) 定位,纵向用双向链表(RCU 友好)遍历,另加全局 drbd_devices idr 支持按 minor 直接寻址;
  3. 生命周期分层:resource / connection / device 三级引用计数保证共享安全,peer_device 无计数、随宿主对象存亡,避免了对矩阵交叉点的额外引用管理负担。

文档本身很短,但它勾勒的骨架是阅读 drivers/block/drbd 全部约 25 个源文件的"地图":先建立这四类对象与矩阵关系的心智模型,再去读 drbd_main.c(对象创建与生命周期)、drbd_nl.c(netlink 管理接口如何遍历这些结构)以及 drbd_int.h(结构与遍历宏),就能把 DRBD 的每个请求处理路径(写数据、resync、fencing)放到正确的对象上理解。

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