Linux 内核块层持久化预留(Persistent Reservations):预留类型、ioctl 接口与内核实现全解 Linux 内核块层持久化预留Persistent Reservations预留类型、ioctl 接口与内核实现全解【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux本文基于 Linux 内核文档 Documentation/block/pr.rst 系统讲解块层 Persistent ReservationsPR持久化预留的用户空间接口六类预留类型的确切语义、六个 PR ioctl 命令的行为规则、PR_FL_IGNORE_KEY标志的适用范围并结合 include/uapi/linux/pr.h 与 block/ioctl.c 的源码实现说明这些 ioctl 在内核中的分发、权限检查与错误模型。读完后你将能够针对支持 PR 的共享存储设备如 SCSI 卷正确编程实现存储访问互斥控制。Persistent Reservations 解决什么问题在共享存储环境中多个主机initiator可能同时连接同一块存储设备。若不加以约束任一端都可以随意读写该设备这在集群数据库、共享磁盘等场景下是致命的。Persistent Reservations 提供了一组把块设备的访问权限制在特定 initiator的机制正如 Documentation/block/pr.rst 所述Persistent Reservations allow restricting access to block devices to specific initiators in a shared storage setup.内核为此提供一套面向用户空间的简化 ioctl 接口映射到支持该特性的块设备文档以 SCSI 为代表。文档还明确了两条所有实现都必须满足的行为约束这是理解 PR 语义的关键前提预留必须在断电后依然存活survive a power loss多路径multi path环境下预留必须覆盖设备的所有连接。值得注意的是这两条行为在 SPCSCSI Primary Commands标准中只是可选项optional但 Linux 会自动强制执行——也就是说即便底层硬件/固件未保证Linux 块层也会在软件层面把这两项作为普适行为施加给所有 PR 实现。文档同时指出本文档只给出 ioctl 接口的通用概览更细致的命令级参考应查阅 SPC 标准中 Reservations 章节以及PERSISTENT RESERVE IN/PERSISTENT RESERVE OUT两条命令的定义。六种预留类型语义与取值Documentation/block/pr.rst 列出了块层支持的全部六类预留。它们在用户空间 UAPI 头文件 include/uapi/linux/pr.h 中对应enum pr_type取值 16 与文档一一对应预留类型enum pr_type取值写权限读权限说明PR_WRITE_EXCLUSIVE1仅预留持有者任意 initiator最常用独占写、共享读PR_EXCLUSIVE_ACCESS2仅预留持有者仅预留持有者完全独占PR_WRITE_EXCLUSIVE_REG_ONLY3持有已注册 key 的 initiator任意 initiator写权开放给所有注册者PR_EXCLUSIVE_ACCESS_REG_ONLY4持有已注册 key 的 initiator持有已注册 key 的 initiator注册者之间共享访问PR_WRITE_EXCLUSIVE_ALL_REGS5持有已注册 key 的 initiator任意 initiator所有注册者都视为预留持有者reservation holderPR_EXCLUSIVE_ACCESS_ALL_REGS6持有已注册 key 的 initiator持有已注册 key 的 initiator所有注册者都视为预留持有者对*_ALL_REGS两类文档特别提醒若要使用这两种类型请参照 SPC 规范中reservation holder的定义因为持有者的语义与普通类型不同——这里不再展开属于 SPC 标准层面必须自行掌握的概念。ioctl 用户空间接口命令号、负载结构与行为规则所有 PR 命令通过ioctl()下发命令号与负载结构定义在 include/uapi/linux/pr.h。该头文件同时给出各命令的宏定义_IOW(p, 200~207, ...)ioctl命令号负载结构方向IOC_PR_REGISTER200struct pr_registration写IOC_PR_RESERVE201struct pr_reservation写IOC_PR_RELEASE202struct pr_reservation写IOC_PR_PREEMPT203struct pr_preempt写IOC_PR_PREEMPT_ABORT204struct pr_preempt写IOC_PR_CLEAR205struct pr_clear写IOC_PR_READ_KEYS206struct pr_read_keys读写IOC_PR_READ_RESERVATION207struct pr_read_reservation读其中文档逐一讲解的是前六个动作型命令IOC_PR_READ_KEYS与IOC_PR_READ_RESERVATION是 UAPI 中定义的只读查询命令分别用于读取当前已注册的 key 列表struct pr_read_keys含generation、num_keys、keys_ptr三个字段key 缓冲区上限由PR_KEYS_MAX定义为1u 16和读取当前预留的 key/代/类型。1. IOC_PR_REGISTER注册 / 更新 / 注销注册 keystruct pr_registration包含old_key、new_key、flags三个有效字段外加保留的__pad。文档规定的行为规则是new_key非零时注册一个新的预留 key若设备上不存在已注册 keyold_key必须为 0若要替换某个已存在的注册 keyold_key必须填入那个旧 key。new_key为 0 时该命令退化为注销unregisterold_key指定的注册 key。2. IOC_PR_RESERVE建立预留struct pr_reservation含key、type、flags三个字段。该命令使设备进入预留状态从而按type上文六种类型之一限制其他设备的访问。文档强调key必须是该设备上已存在的注册 key即必须先通过IOC_PR_REGISTER、IOC_PR_REGISTER_IGNORE对应PR_FL_IGNORE_KEY标志、IOC_PR_PREEMPT或IOC_PR_PREEMPT_ABORT获得否则无法成功预留。3. IOC_PR_RELEASE释放预留由key与flagsstruct pr_reservation指定的预留被释放其带来的访问限制随之移除。注意它与IOC_PR_CLEAR的区别release 只解除预留本身不清空已注册的 key。4. IOC_PR_PREEMPT抢占式替换预留struct pr_preempt含old_key、new_key、type、flags。语义是释放old_key所指的既有预留并立即以new_key和type建立一个新预留。这是集群软件在节点故障接管failover时最核心的命令——新接管节点无需等待旧持有者主动释放。5. IOC_PR_PREEMPT_ABORT抢占并中止在途命令行为与IOC_PR_PREEMPT相同但额外中止abort经由old_key所标识连接发出的所有未完成命令。对共享存储而言旧持有者在故障前可能仍有 I/O 在途PREEMPT_ABORT能保证这些命令不会在新持有者接管后继续执行避免数据竞争。6. IOC_PR_CLEAR彻底清空struct pr_clear含key与flags。该命令会注销key以及设备上所有其他已注册的预留 key并丢弃当前预留——即把设备的 PR 状态完全复位。它是破坏性最强的命令通常仅用于设备维护或故障后清理。Flags目前唯一支持的标志所有 PR ioctl 的负载结构中都带一个flags字段但内核目前只支持一个标志见 include/uapi/linux/pr.h 第 71 行#define PR_FL_IGNORE_KEY (1 0) /* ignore existing key */PR_FL_IGNORE_KEY忽略设备上已存在的注册 key。文档指出它通常用于IOC_PR_REGISTER即不检查old_key是否匹配即可注册部分实现也会允许在IOC_PR_RESERVE上使用该标志。任何未知标志内核一律返回-EOPNOTSUPP。结合 block/ioctl.c 的实现可以进一步看到标志校验的精确边界blkdev_pr_register()与blkdev_pr_reserve()中对标志的检查是if (flags ~PR_FL_IGNORE_KEY) return -EOPNOTSUPP;而blkdev_pr_release()、blkdev_pr_preempt()、blkdev_pr_clear()则直接要求flags为 0传入任何非零值都返回-EOPNOTSUPP。也就是说除 register/reserve 可选带PR_FL_IGNORE_KEY外其余命令根本不允许携带标志。内核实现视角从 ioctl 到 pr_opsPR 命令在内核中的处理入口是 block/ioctl.c。以blkdev_pr_register()约第 342–358 行为代表每个命令的处理流程高度一致共四步权限检查调用blkdev_pr_allowed(bdev, mode, PR_IN/PR_OUT)。该函数同文件第 330–340 行附近按命令性质区分打开模式——PR_IN只读查询类命令要求文件描述符以读方式打开PR_OUT修改设备状态类命令要求写方式打开否则返回-EPERM。能力检查取出bdev-bd_disk-fops-pr_ops若设备未实现pr_ops或对应回调如ops-pr_register返回-EOPNOTSUPP。参数拷贝与校验copy_from_user()把用户态结构拷入内核随后做标志位合法性检查。下发到驱动最终调用ops-pr_xxx(bdev, ...)把语义交给具体设备驱动如 SCSI 栈执行。各命令对应的内核函数为blkdev_pr_register/blkdev_pr_reserve/blkdev_pr_release/blkdev_pr_preemptPREEMPT 与 PREEMPT_ABORT 共用此函数以abort布尔参数区分/blkdev_pr_clear/blkdev_pr_read_keys/blkdev_pr_read_reservationIOC_PR_*到这些函数的分发表在block/ioctl.c第 723–737 行的case分支中。驱动侧需要填充的回调集合由 include/linux/pr.h 中的struct pr_ops定义包含pr_register、pr_reserve、pr_release、pr_preempt含abort参数、pr_clear以及pr_read_keys等函数指针其参数形态与用户空间结构体字段一一对应old_key/new_key/type/flags。该结构挂载在struct block_device_operations见 include/linux/blkdev.h 第 1600 行的const struct pr_ops *pr_ops上因此哪些块设备支持 PR完全取决于其驱动是否提供了pr_ops实现——文档中映射到支持这些特性的块设备like SCSI的说法正源于此。错误模型pr_status 状态码include/uapi/linux/pr.h 定义了enum pr_status供驱动实现回调时向上层返回细粒度状态状态码值含义PR_STS_SUCCESS0x0成功PR_STS_IOERR0x2通用设备故障PR_STS_RESERVATION_CONFLICT0x18预留冲突典型于抢占/注册竞争场景PR_STS_RETRY_PATH_FAILURE0xe0000临时路径故障可重试PR_STS_PATH_FAST_FAILED0xf0000因 fast failure 定时器导致失败PR_STS_PATH_FAILED0x10000路径不可达且已被标记失败头文件注释明确说明这些错误码沿用 SCSI 体系因为该接口最初就是为 SCSI 创建且已有存量用户。对多路径环境尤其重要的是后三个路径类错误码——它们区分了可重试与已永久失效是多路径软件如 device-mapper multipath做路径切换决策的依据。使用前提与实践要点小结设备前提目标块设备的驱动必须实现pr_opsinclude/linux/pr.h否则任何 PR ioctl 都返回-EOPNOTSUPP文档以 SCSI 设备为主要适用对象。打开模式对设备文件执行修改类 PR 命令需要写权限打开只读查询类命令如读取已注册 key读权限即可。key 生命周期REGISTER产生 key →RESERVE用 key 建立预留 →RELEASE解除预留 →PREEMPT/PREEMPT_ABORT用于故障接管 →CLEAR全量清空。跨断电与跨多路径路径的持久性是 Linux 对实现的强制要求而非硬件可选项。标准参考需要命令级的完整语义如 reservation holder 的精确定义、各 SPC 服务动作的行为矩阵时应以 SPC 标准的 Reservations 章节和 PERSISTENT RESERVE IN/OUT 命令定义为准本文档仅给出 Linux ioctl 层的映射概览。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考