当前位置: 首页 > news >正文

FEMU—NVMe ZNS 核心实现的学习

底层逻辑

一、核心数据结构

NvmeNamespaceFemuCtrlNvmeZone

在 FEMU 的源代码(通常在 hw/block/femu/ 路径下)中,NvmeNamespaceFemuCtrlNvmeZone 是构成整个 NVMe 模拟栈的三个核心结构,分别对应 命名空间(Namespace)控制器(Controller)ZNS 区域(Zone Namespace) 的不同层次。


一、三者关系总览

元素所在文件主要作用上下级关系关键字段 / 指针关系
FemuCtrlfemu/nvme.cfemu/femu.h整个 NVMe 控制器的核心结构,管理所有命名空间、队列、寄存器、模拟设备状态顶层控制模块,包含多个 NvmeNamespaceFemuCtrl -> namespaces[NvmeNamespace]FemuCtrl -> zns_ctrl(若启用 ZNS)
NvmeNamespacefemu/nvme-ns.cfemu/nvme.h表示 NVMe 命名空间(即逻辑存储卷),负责 LBA 映射、数据管理、读写操作FemuCtrl 创建和管理;若为 ZNS 类型,则含有多个 NvmeZoneNvmeNamespace -> FemuCtrl *ctrl(反向指针);NvmeNamespace -> NvmeZone *zones(若启用 ZNS)
NvmeZonefemu/nvme-zns.cfemu/nvme.h表示 Zoned Namespace 模式下的单个 Zone 区域;管理其写指针、状态、容量等隶属于某个 NvmeNamespace,被统一调度NvmeZone -> NvmeNamespace *nsNvmeZone -> 状态、写指针、剩余容量等

二、结构层次示意图(逻辑关系)

+---------------------------------------------------+
|                   FemuCtrl                        |
|---------------------------------------------------|
| 控制寄存器 | 队列管理 | IO处理 | ZNS支持 |        |
|                                               ... |
|  +-------------------------------------------+    |
|  | NvmeNamespace (nsid=1)                    |    |
|  |-------------------------------------------|    |
|  | LBA->PPA映射表 | 媒体模拟 | ZNS结构体指针 |   |
|  |  +------------------------------+          |    |
|  |  | NvmeZone[0] ... NvmeZone[N] |<----------+    |
|  |  +------------------------------+               |
|  +-------------------------------------------+    |
|  | NvmeNamespace (nsid=2) ...                |    |
+---------------------------------------------------+

三、关键交互机制

交互类型说明FEMU源码中的体现
初始化阶段FemuCtrl 在初始化时调用 nvme_init_namespaces() 来创建并初始化 NvmeNamespace;若是 ZNS 模式,则进一步调用 nvme_zns_init_zones() 初始化 NvmeZonehw/block/femu/nvme.c 中的 nvme_realize()
I/O命令处理控制器接收 NVMe 命令后,由 nvme_io_cmd() 分发到对应命名空间(通过 nsid);若为 ZNS 命令,则由 nvme_zns_* 系列函数进一步处理 Zone 操作nvme_cmd.cnvme-zns.c
ZNS写入限制NvmeZone 通过写指针(wp)和状态(state)限制顺序写入和重置操作nvme_zns_write()nvme_zns_reset()
元数据管理NvmeNamespace 持有逻辑块信息、媒体模拟层接口和 Zone 信息,协调读写与元数据一致性nvme_ns.c

四、结构体核心字段对比(简化版)

结构体关键字段含义
FemuCtrlNvmeNamespace *namespaces;命名空间数组
uint32_t num_namespaces;命名空间数量
NvmeZnsCtrl *zns_ctrl;若为ZNS控制器,则管理Zone命令逻辑
NvmeNamespaceFemuCtrl *ctrl;指向控制器的反向指针
NvmeZone *zone_array;Zone信息数组
uint64_t nsze;命名空间大小(以LBA计)
NvmeZoneuint64_t zslba, zcap, wp;Zone起始地址、容量、写指针
uint8_t state;Zone当前状态(Empty/Open/Closed/Full)
NvmeNamespace *ns;所属命名空间指针

五、总结一句话关系:

FemuCtrl 是整个 NVMe 控制器的“大脑”;
NvmeNamespace 是它所管理的“逻辑卷”;
NvmeZone 是 ZNS 模式下命名空间的“物理分区”。
三者形成了自上而下的 控制器 → 命名空间 → 区域(Zone) 层级关系。

ZNS 的核心逻辑围绕 “区域(Zone)” 和 “命名空间(Namespace)” 的管理展开,关键数据结构如下:

1. 区域(Zone)的定义

zns.h中定义的NvmeZone结构体是单个 ZNS 区域的核心描述,包含区域状态、容量、位置等关键信息:

typedef struct NvmeZone {NvmeZoneDescr   d;         // 区域描述符(包含类型、状态、容量等元数据)uint64_t        w_ptr;     // 实际写指针(记录当前写入位置,单位LBA)int             mode;      // 存储模式(0:SLC,1:QLC)int             accessed;  // 访问标记(用于定期清理)QTAILQ_ENTRY(NvmeZone) entry;  // 链表节点(用于状态链表管理)int pos;                   // 区域在数组中的索引
} NvmeZone;
typedef struct QTailQLink {void *tql_next;struct QTailQLink *tql_prev;
} QTailQLink;#define QTAILQ_ENTRY(type)                                              \
union {                                                                 \struct type *tqe_next;        /* next element */                \QTailQLink tqe_circ;          /* link for circular backwards list */ \
}

QTAILQ_ENTRY(NvmeZone) entry;是一个与 QTAILQ 队列数据结构相关的声明。

QTAILQ 是 FreeBSD 内核中的一种队列数据结构,主要用于实现双向有尾链表6。QTAILQ_ENTRY是一个宏,用于定义队列中的元素结构5。

根据QTAILQ_ENTRY的定义,QTAILQ_ENTRY(NvmeZone)展开后会得到一个结构体,该结构体包含两个成员变量4:

  • tqe_next:这是一个指针,指向队列中的下一个元素。
  • tqe_prev:这是一个二级指针,指向队列中前一个元素的tqe_next指针的地址。

NvmeZone结构体中声明QTAILQ_ENTRY(NvmeZone) entry;,意味着NvmeZone结构体可以作为 QTAILQ 队列中的一个元素,通过entry这个成员变量来链接到队列中的其他元素,从而实现链表的功能。

其中,NvmeZoneDescr(区域描述符)是 NVMe 协议定义的标准结构,包含:

NvmeZoneDescr
typedef struct QEMU_PACKED NvmeZoneDescr {uint8_t     zt;      // 区域类型(如NVME_ZONE_TYPE_SEQ_WRITE:顺序写入区域)uint8_t     zs;      // 区域状态(高4位有效,如EMPTY、OPEN、CLOSED等)uint8_t     za;      // 区域属性(如是否需要重置、扩展信息是否有效等)uint64_t    zcap;    // 区域容量(单位LBA)uint64_t    zslba;   // 区域起始LBA(逻辑块地址)uint64_t    wp;      // 协议层写指针(与w_ptr同步,用于对外暴露)
} NvmeZoneDescr;
NvmeZoneState

区域状态通过NvmeZoneState枚举定义,符合 NVMe 协议:

enum NvmeZoneState {NVME_ZONE_STATE_EMPTY            = 0x01,  // 空(未写入数据)NVME_ZONE_STATE_IMPLICITLY_OPEN  = 0x02,  // 隐式打开(由写入操作触发)NVME_ZONE_STATE_EXPLICITLY_OPEN  = 0x03,  // 显式打开(由Open命令触发)NVME_ZONE_STATE_CLOSED           = 0x04,  // 关闭(可重新打开)NVME_ZONE_STATE_FULL             = 0x0E,  // 满(无法再写入)NVME_ZONE_STATE_READ_ONLY        = 0x0D,  // 只读NVME_ZONE_STATE_OFFLINE          = 0x0F   // 离线
};

2. ZNS 命名空间的管理结构

ZNS 命名空间是 NVMe 设备上的一个分区,专门用于 ZNS 特性,其管理依赖以下结构:

NvmeNamespaceParamszns.h):存储 ZNS 命名空间的配置参数:

typedef struct NvmeNamespaceParams {bool     zoned;               // 是否启用ZNS特性bool     cross_zone_read;     // 是否允许跨区域读uint64_t zone_size_bs;        // 区域大小(字节)uint64_t zone_cap_bs;         // 区域容量(字节)uint32_t max_active_zones;    // 最大活动区域数uint32_t max_open_zones;      // 最大打开区域数
} NvmeNamespaceParams;

命名空间是存储设备的逻辑分区,而Zone是ZNS命名空间内的顺序写入区域。一个ZNS命名空间可以包含成百上千个Zone。

FemuCtrl
  • FemuCtrl(隐含在代码中):设备控制结构体,包含 ZNS 管理的核心数据:

  • FemuCtrl 是 Femu 模拟器中 NVMe 设备的 “大脑”,它整合了 ZNS 区域管理、命名空间配置、命令队列、中断处理、性能模拟等所有核心逻辑。通过该结构体,Femu 能够模拟 NVMe ZNS 设备的完整行为,包括区域状态转换、顺序写入控制、与主机的命令交互等,是理解 Femu 中 ZNS 底层实现的关键数据结构。

    // 从代码片段推断的关键字段
    struct FemuCtrl {NvmeZone *zone_array;         // 区域数组(所有区域的集合)uint32_t num_zones;           // 区域总数uint64_t zone_size;           // 区域大小(单位LBA)uint64_t zone_capacity;       // 区域容量(单位LBA)uint32_t max_open_zones;      // 最大打开区域数限制uint32_t max_active_zones;    // 最大活动区域数限制// 状态链表(按区域状态分类管理)QTAILQ_HEAD(, NvmeZone) exp_open_zones;  // 显式打开的区域QTAILQ_HEAD(, NvmeZone) imp_open_zones;  // 隐式打开的区域QTAILQ_HEAD(, NvmeZone) closed_zones;    // 关闭的区域QTAILQ_HEAD(, NvmeZone) full_zones;      // 满的区域
    };
    

typedef struct FemuCtrl { ... } 是 Femu(一个 NVMe 设备模拟器)中定义的核心控制器结构体,用于整合管理 NVMe 设备的所有硬件配置、运行状态、资源(如命名空间、队列、区域等)及功能逻辑。它是 Femu 模拟 NVMe 设备的 “总控中心”,几乎包含了设备运行所需的全部关键信息。

以下是其核心字段的分类解析(结合 NVMe 协议和 ZNS 特性):

1. 基础硬件与 PCI 设备属性
  • PCIDevice parent_obj:继承 QEMU 的 PCIDevice 结构体,表明该控制器是一个 PCI 设备,用于兼容 PCIe 总线规范(NVMe 设备基于 PCIe 接口)。
  • MemoryRegion iomemctrl_mem:内存区域对象,模拟 NVMe 设备的 I/O 映射空间(如控制器寄存器、BAR 空间等),供主机通过内存映射访问设备。
  • NvmeBar bar:NVMe 基地址寄存器(Base Address Register)配置,用于管理设备的内存映射地址范围。
2. ZNS 特性核心管理字段

(这部分与之前分析的 ZNS 逻辑直接关联)

  • struct zonessd *znsssd:指向 ZNS SSD 的底层存储控制结构,关联 ZNS 区域与物理存储介质的映射(FTL 层)。
  • bool zoned:标识该设备是否启用 ZNS 特性。
  • uint64_t zone_size_bs / zone_cap_bs:区域大小(字节)和区域容量(字节,ZNS 中容量可能小于大小,因存在元数据开销)。
  • uint32_t max_active_zones / max_open_zones:ZNS 协议限制的最大活动区域数和最大打开区域数(符合 NVMe ZNS 规范的 AOR 机制)。
  • NvmeZone *zone_array:所有 ZNS 区域的数组,每个元素对应一个区域的元数据(状态、写指针等)。
  • 状态链表头:exp_open_zones(显式打开的区域)、imp_open_zones(隐式打开的区域)、closed_zones(关闭的区域)、full_zones(满的区域),用于按状态高效管理区域。
  • uint32_t num_zones:总区域数量;uint64_t zone_size / zone_capacity:区域大小 / 容量(单位 LBA)。
3. 命名空间与存储配置
  • NvmeNamespace *namespaces:指向命名空间数组,NVMe 设备通过命名空间划分存储区域(类似磁盘分区),ZNS 特性通常绑定在特定命名空间上。
  • uint32_t num_namespaces:命名空间总数。
  • uint64_t ns_size:设备总存储大小;uint8_t flash_type:模拟的闪存类型(如 SLC/QLC 等,影响 ZNS 区域的读写性能)。
4. 队列管理(NVMe 核心机制)

NVMe 通过提交队列(SQ)和完成队列(CQ)处理命令,相关字段包括:

  • NvmeSQueue **sq / NvmeCQueue **cq:I/O 提交队列和完成队列数组(管理多个 I/O 队列)。
  • admin_sq / admin_cq:管理命令的提交队列和完成队列(用于处理 NVMe 管理命令,如创建命名空间、查询区域状态等)。
  • uint32_t max_q_ents:队列最大条目数;uint16_t sqid / cqid:队列 ID 标识。
5. 中断与事件处理
  • int32_t virq:虚拟中断号,用于向主机报告设备事件(如命令完成、错误等)。
  • EventNotifier guest_notifier:通知机制,用于在命令完成时唤醒主机处理。
  • QSIMPLEQ_HEAD(aer_queue, NvmeAsyncEvent) aer_queue:异步事件队列(如区域状态变化、错误通知等,符合 NVMe AER 机制)。
6. 性能与调试
  • 延迟字段:upg_rd_lat_ns(上层页读延迟)、blk_er_lat_ns(块擦除延迟)等,模拟不同闪存页 / 块的操作耗时。
  • 统计字段:nr_tt_ios(总 I/O 数)、nr_tt_late_ios(延迟 I/O 数),用于性能分析。
  • femu_mode:模拟器运行模式(如 FEMU_ZNSSD_MODE 表示 ZNS 模式)。

3. FTL 层辅助结构(znsftl.h

zonessd结构体用于 ZNS 的闪存转换层(FTL)管理,关联物理存储与逻辑区域:

struct zonessd {struct zonessd_params sp;      // SSD硬件参数(如页大小、块大小等)struct ssd_channel *ch;        // 存储通道(物理存储介质)struct zone_block *maptbl;     // 区域-物理块映射表(逻辑区域到物理块的映射)zone *zone_array;              // FTL层的区域数组(与NvmeZone对应)uint64_t zone_size_bs;         // 区域大小(字节)int zone_num;                  // 区域总数// 容量统计uint64_t total_cap;            // 总容量uint64_t used_cap;             // 已用容量uint64_t used_slc_cap;         // SLC模式已用容量uint64_t used_qlc_cap;         // QLC模式已用容量
};

struct zonessd 是FEMU中用于模拟Zoned Namespace SSD的核心数据结构,它代表了整个ZNS SSD设备的内部状态和组织结构。

主要成员变量说明
  • sp: zonessd_params 结构体,存储SSD的基本参数配置,如页面大小、块大小、通道数量等物理特性

  • ch: ssd_channel 结构体指针,指向SSD的通道数组,每个通道包含多个LUN(Logic Unit Number)

  • maptbl: zone_block 结构体指针,作为zone到物理block的映射表,用于跟踪每个zone在SSD中的物理位置

  • zone_array: zone 结构体指针,指向zone数组,管理所有zone的状态和写指针

  • zone_size_bs: 每个zone的大小(以字节为单位)

  • zone_num: SSD中zone的总数量

  • zone_size: 每个zone包含的页面数量

  • zone_size_log2: zone大小的对数表示,用于快速计算,如果zone大小不是2的幂则为0

HZNS相关字段
  • total_cap: 总容量
  • used_cap: 已使用容量
  • used_slc_cap: 已使用的SLC模式容量
  • used_qlc_cap: 已使用的QLC模式容量

这个结构体连接了逻辑的ZNS接口和物理的SSD存储结构,是实现ZNS功能的关键数据结构。

二、核心函数(zns.c

ZNS 的核心逻辑通过区域的初始化、状态管理、IO 操作控制等函数实现:

1. 区域初始化与几何参数计算

zns_init_zone_geometry
  • zns_init_zone_geometry:初始化区域的几何参数(大小、容量、数量),并校验合法性:
static int zns_init_zone_geometry(NvmeNamespace *ns, Error **errp) {// 1. 确定区域大小(优先使用用户配置,否则用默认值NVME_DEFAULT_ZONE_SIZE)// 2. 确定区域容量(优先用户配置,否则等于区域大小)// 3. 校验:容量≤大小、区域大小≥LBA大小、容量≥LBA大小// 4. 计算区域数量(命名空间总LBA数 / 每个区域的LBA数)// 5. 校验最大打开/活动区域数不超过区域总数
}

具体解释:
zns_ns_lbads(ns) 获取命名空间的逻辑块地址移位值
1 << zns_ns_lbads(ns) 将1左移相应的位数
结果存储在 lbasz 变量中,表示逻辑块的实际大小(以字节为单位)
例如:如果 zns_ns_lbads(ns) 返回9,则 lbasz = 1 << 9 = 512 字节。

这是一个位运算操作,让我来解释一下计算过程:

位运算左移操作

1 << 9 表示将数字1向左移动9位,计算过程如下:

  • 初始值:1 (二进制: 00000001)
  • 左移1位:1 << 1 = 2 (二进制: 00000010)
  • 左移2位:1 << 2 = 4 (二进制: 00000100)
  • 左移3位:1 << 3 = 8 (二进制: 00001000)
  • 左移9位:1 << 9 = 512 (二进制: 1000000000)
数学原理

左移操作相当于乘以2的幂次:

  • 1 << n = 1 × 2^n = 2^n

因此:

  • 1 << 9 = 2^9 = 512
在代码中的意义

zns.c 中,这个计算用于确定逻辑块大小(LBA Size):

  • zns_ns_lbads(ns) 返回的是LBADS(Logical Block Address Shift)值
  • LBADS表示逻辑块地址移位的位数
  • 通过 1 << lbads 计算出实际的逻辑块大小(以字节为单位)

例如LBADS为9时,表示每个逻辑块大小为512字节,这是NVMe设备中常见的扇区大小。

zns_init_zoned_state
  • zns_init_zoned_state:初始化所有区域的状态和元数据:

    static void zns_init_zoned_state(NvmeNamespace *ns) {// 1. 分配区域数组(zone_array)和扩展信息内存// 2. 初始化状态链表(exp_open_zones等)// 3. 为每个区域设置初始状态://    - 类型:NVME_ZONE_TYPE_SEQ_WRITE(顺序写入)//    - 状态:NVME_ZONE_STATE_EMPTY(空)//    - 起始LBA(zslba)、写指针(wp=zslba)、容量(zcap)
    }
    

    zns_init_zoned_state 函数是 Femu 模拟器中 ZNS(Zoned Namespace)特性的核心初始化函数之一,主要作用是为 ZNS 命名空间初始化所有区域(Zone)的元数据和管理结构,确保区域符合 NVMe ZNS 协议规范并能被正确管理。

函数核心功能解析:
  1. 分配区域数据结构

    • 为控制器(FemuCtrl)的 zone_array 分配内存,用于存储所有区域的元数据(NvmeZone 结构体),数量为 n->num_zones(总区域数)。
    • 若配置了区域描述符扩展(zd_extension_size),则分配对应大小的内存(zd_extensions),用于存储区域的扩展信息。
  2. 初始化区域状态管理链表

    • 初始化 4 个双向链表(exp_open_zonesimp_open_zonesclosed_zonesfull_zones),这些链表用于按状态分类管理区域:
      • 显式打开的区域(exp_open_zones
      • 隐式打开的区域(imp_open_zones
      • 关闭的区域(closed_zones
      • 满的区域(full_zones
    • 这种分类管理符合 NVMe ZNS 协议对区域状态转换的要求,便于高效查询和操作特定状态的区域。
  3. 初始化每个区域的元数据

    遍历所有区域(n->num_zones),为每个区域设置初始属性:

    • pos:区域索引(唯一标识)。
    • d.zt:区域类型,固定为 NVME_ZONE_TYPE_SEQ_WRITE(顺序写入区域,ZNS 的核心特性)。
    • 初始状态:通过 zns_set_zone_state 设置为 NVME_ZONE_STATE_EMPTY(空状态,区域创建时的初始状态)。
    • d.zcap:区域容量(LBA 数),取自控制器配置的 zone_capacity
    • d.zslba:区域起始 LBA(逻辑块地址),从 0 开始按区域大小递增。
    • 写指针(d.wpw_ptr):初始化为区域起始 LBA(空区域的写指针未移动)。
    • mode:存储介质模式(SLC/QLC),由 hzns_mode(混合 ZNS 模式)决定,纯 QLC 模式下为 QLC,否则为 SLC。
  4. 计算区域大小的对数(优化索引计算)

    若区域大小(n->zone_size)是 2 的幂,计算其对数(zone_size_log2),用于后续通过位移操作快速计算区域索引(如 zns_zone_idx 函数中通过 slba >> zone_size_log2 定位区域),提升效率。

2. 区域状态管理

ZNS 通过状态链表(显式打开、隐式打开、关闭、满)管理区域,核心函数:

zns_assign_zone_state
  • zns_assign_zone_state:修改区域状态(核心状态转换逻辑):

    static void zns_assign_zone_state(NvmeNamespace *ns, NvmeZone *zone, NvmeZoneState state) {// 1. 从当前状态的链表中移除区域(如从closed_zones移除)// 2. 更新区域状态(zs字段)// 3. 将区域加入新状态的链表(如加入exp_open_zones)
    }
    

zns_assign_zone_state 函数的核心作用是 更新 ZNS 设备中区域(zone)的状态,并维护区域在对应状态队列中的归属关系。它会先将区域从当前所在的状态队列中移除,再将其添加到新状态对应的队列中,确保控制器能准确跟踪所有区域的状态(如空、打开、关闭、满等),符合 NVMe ZNS 协议对区域状态管理的要求。

使用场景

当需要对区域执行状态转换操作时(如打开、关闭、完成、重置等),都会调用该函数。例如:

  • 打开区域时,将状态从 “空” 或 “关闭” 转换为 “显式打开”;
  • 关闭区域时,将状态从 “显式打开” 或 “隐式打开” 转换为 “关闭”;
  • 区域写满时,将状态转换为 “满”;
  • 重置区域时,将状态从 “满” 或 “关闭” 转换为 “空”。
代码中实际使用的例子:打开区域(zns_open_zone 函数)

在处理 “打开区域” 命令时,会通过 zns_assign_zone_state 将区域状态更新为 “显式打开”,并加入对应的队列。

static uint16_t zns_open_zone(NvmeNamespace *ns, NvmeZone *zone,NvmeZoneState state, NvmeRequest *req)
{uint16_t status;// ... 省略参数检查和模式设置逻辑 ...switch (state) {case NVME_ZONE_STATE_EMPTY:// ... 省略资源检查逻辑 ...zns_aor_inc_active(ns);/* 继续处理下一个状态 */case NVME_ZONE_STATE_CLOSED:// ... 省略资源检查逻辑 ...zns_aor_inc_open(ns);/* 继续处理下一个状态 */case NVME_ZONE_STATE_IMPLICITLY_OPEN:// 关键调用:将区域状态更新为“显式打开”,并加入exp_open_zones队列zns_assign_zone_state(ns, zone, NVME_ZONE_STATE_EXPLICITLY_OPEN);/* 继续处理下一个状态 */case NVME_ZONE_STATE_EXPLICITLY_OPEN:return NVME_SUCCESS;default:return NVME_ZONE_INVAL_TRANSITION;}
}
场景说明:

当区域当前状态为 “空”“关闭” 或 “隐式打开” 时,执行 “打开区域” 命令后,需要将其状态转换为 “显式打开”。此时:

  1. zns_assign_zone_state 会先检查区域当前所在的队列(如 “空” 队列、“关闭” 队列或 “隐式打开” 队列),并将其从原队列中移除;
  2. 然后将区域状态设置为 NVME_ZONE_STATE_EXPLICITLY_OPEN
  3. 最后将区域加入 exp_open_zones 队列(显式打开区域队列),便于控制器跟踪和管理所有显式打开的区域。

通过这个函数,控制器能准确维护区域状态的一致性,确保符合 NVMe ZNS 协议对区域操作的约束(如最大打开 / 活动区域数限制)。

zns_clear_zone
  • zns_clear_zone:清空区域(重置写指针,调整状态,重置 ZNS 区域(zone)的状态并调整其在状态队列中的归属):

    static void zns_clear_zone(NvmeNamespace *ns, NvmeZone *zone) {// 1. 重置写指针(w_ptr = zslba)// 2. 根据是否有数据,设置状态为EMPTY或CLOSED// 3. 加入对应状态的链表
    }
    
    关键逻辑拆解:
    static void zns_clear_zone(NvmeNamespace *ns, NvmeZone *zone)
    {FemuCtrl *n = ns->ctrl;  // 获取NVMe控制器实例uint8_t state;// 重置区域的写指针(w_ptr)到描述符中记录的写指针位置(d.wp)zone->w_ptr = zone->d.wp;state = zns_get_zone_state(zone);  // 获取当前区域状态// 条件判断:区域是否有数据写入或包含有效扩展信息if (zone->d.wp != zone->d.zslba ||  // d.wp != 区域起始LBA(说明有数据写入)(zone->d.za & NVME_ZA_ZD_EXT_VALID)) {  // 区域描述符扩展有效// 若当前状态不是“关闭”,则强制设为“关闭”if (state != NVME_ZONE_STATE_CLOSED) {zns_set_zone_state(zone, NVME_ZONE_STATE_CLOSED);}// 增加活跃区域计数(活跃区域包括打开、关闭状态的区域)zns_aor_inc_active(ns);// 将区域加入“关闭区域队列”(closed_zones),便于管理QTAILQ_INSERT_HEAD(&n->closed_zones, zone, entry);} else {// 若区域无数据且无扩展信息,直接设为“空”状态zns_set_zone_state(zone, NVME_ZONE_STATE_EMPTY);}
    }
    
    重点说明:
    • 写指针重置zone->w_ptr = zone->d.wp 确保区域的实际写指针与描述符中记录的一致,避免状态不一致。
    • 状态转换条件
      • 当区域有数据(d.wp != zslba)或有有效扩展信息时,无论之前状态如何,最终都会转为 “关闭” 状态并加入关闭队列。
      • 当区域无数据且无扩展信息时,直接转为 “空” 状态(初始状态)。
    • 队列管理:通过 QTAILQ_INSERT_HEAD 将关闭的区域加入 closed_zones 队列,方便控制器跟踪所有关闭状态的区域。
zns_aor_inc_active 函数解析

该函数用于 安全地增加 “活跃区域”(Active Zones)的计数,是 ZNS 协议中 “活跃与打开资源管理(AOR, Active and Open Resources)” 的关键逻辑,确保区域数量不超过控制器配置的上限。

函数内容与解析:
static inline void zns_aor_inc_active(NvmeNamespace *ns)
{FemuCtrl *n = ns->ctrl;  // 获取控制器实例// 断言:当前活跃区域数必须非负(调试阶段检查逻辑错误)assert(n->nr_active_zones >= 0);// 若配置了最大活跃区域数(max_active_zones > 0)if (n->max_active_zones) {n->nr_active_zones++;  // 增加活跃区域计数// 断言:活跃区域数不能超过最大值(防止超出配置限制)assert(n->nr_active_zones <= n->max_active_zones);}
}
重点说明:
  • AOR 概念:ZNS 协议限制了 “活跃区域”(包括打开、关闭状态的区域)的最大数量(max_active_zones),用于控制设备资源占用。zns_aor_inc_active 是维护这一限制的核心函数。
  • assert 的作用
    • assert(n->nr_active_zones >= 0):确保活跃区域计数不会出现负数(逻辑错误检查,如异常的减操作)。
    • assert(n->nr_active_zones <= n->max_active_zones):确保活跃区域数不超过配置的最大值(防止超出设备能力)。
    • 注意assert 仅在调试阶段生效,用于捕获程序逻辑错误;发布版本中通常会被禁用,不会影响性能。
  • 条件执行:仅当 max_active_zones 配置为非 0 时(即启用限制),才会执行计数增加操作,否则不限制活跃区域数量。
总结
  • zns_clear_zone 负责重置区域状态并维护状态队列,确保区域状态与数据状态一致。
  • zns_aor_inc_active 负责安全地增加活跃区域计数,通过 assert 确保计数在有效范围内,符合 ZNS 协议对资源管理的要求。
zns_aor_check
  • zns_aor_check检查打开 / 激活区域是否超过限制(符合 NVMe AOR 机制),确保在执行区域状态转换(如打开、激活区域)时,不超过设备配置的最大活跃区域数(max_active_zones)和最大打开区域数(max_open_zones)。:

    static int zns_aor_check(NvmeNamespace *ns, uint32_t act, uint32_t opn) {// 检查活动区域数(nr_active_zones + act)是否超过max_active_zones// 检查打开区域数(nr_open_zones + opn)是否超过max_open_zones
    }
    
  • 参数 act:本次操作将新增的活跃区域数(活跃区域包括 “打开”“关闭” 状态的区域)。

  • 参数 opn:本次操作将新增的打开区域数(打开区域包括 “显式打开”“隐式打开” 状态的区域)。

  • 返回值:成功返回 NVME_SUCCESS;若活跃区域超限返回 NVME_ZONE_TOO_MANY_ACTIVE;若打开区域超限返回 NVME_ZONE_TOO_MANY_OPEN

实际应用例子:打开区域(zns_open_zone 函数)

在打开一个 ZNS 区域时,zns_aor_check 会被调用以确保操作不违反设备的资源限制。以下是具体场景:

假设设备配置:

  • 最大活跃区域数 max_active_zones = 5,当前活跃区域数 nr_active_zones = 4
  • 最大打开区域数 max_open_zones = 3,当前打开区域数 nr_open_zones = 2

现在要打开一个状态为 “关闭(CLOSED)” 的区域(从关闭到打开的转换)。

代码执行流程
static uint16_t zns_open_zone(NvmeNamespace *ns, NvmeZone *zone,NvmeZoneState state, NvmeRequest *req)
{uint16_t status;// ... 省略其他逻辑 ...switch (state) {case NVME_ZONE_STATE_EMPTY:// 从“空”状态打开:新增1个活跃区域(act=1),暂时不新增打开区域(opn=0)status = zns_aor_check(ns, 1, 0);if (status != NVME_SUCCESS) {return status; // 若活跃区域超限,返回错误}zns_aor_inc_active(ns); // 增加活跃区域计数/* 继续处理 */case NVME_ZONE_STATE_CLOSED:// 从“关闭”状态打开:不新增活跃区域(已算入活跃),新增1个打开区域(opn=1)status = zns_aor_check(ns, 0, 1);if (status != NVME_SUCCESS) {if (state == NVME_ZONE_STATE_EMPTY) {zns_aor_dec_active(ns); // 回滚活跃计数}return status; // 若打开区域超限,返回错误}zns_aor_inc_open(ns); // 增加打开区域计数/* 继续处理 */case NVME_ZONE_STATE_IMPLICITLY_OPEN:// 转换为“显式打开”,无需新增计数zns_assign_zone_state(ns, zone, NVME_ZONE_STATE_EXPLICITLY_OPEN);/* 继续处理 */case NVME_ZONE_STATE_EXPLICITLY_OPEN:return NVME_SUCCESS;default:return NVME_ZONE_INVAL_TRANSITION;}
}
具体分析
  1. 参数选择

    由于区域当前状态是 “关闭(CLOSED)”,属于 “活跃区域”(活跃区域包括打开、关闭状态),因此打开操作不会新增活跃区域(act=0),但会将其从 “关闭” 转为 “打开”,因此新增 1 个打开区域(opn=1)。

  2. 检查过程

    调用 zns_aor_check(ns, 0, 1) 时:

    • 活跃区域检查:nr_active_zones + 0 = 4 ≤ 5(未超限)。

    • 打开区域检查:nr_open_zones + 1 = 3 ≤ 3(未超限)。

      因此返回

      NVME_SUCCESS
      

      ,允许打开操作。

  3. 后续操作

    检查通过后,调用 zns_aor_inc_open(ns) 将打开区域数更新为 3,并通过 zns_assign_zone_state 将区域状态转为 “显式打开”,加入打开区域队列。

超限场景示例

若当前打开区域数已达 3(等于 max_open_zones),再调用 zns_aor_check(ns, 0, 1) 时:

  • 打开区域检查:3 + 1 = 4 > 3(超限),返回 NVME_ZONE_TOO_MANY_OPEN
  • 此时 zns_open_zone 会终止操作,返回错误码,避免违反设备限制。
总结

zns_aor_check 是 ZNS 设备资源管理的核心检查点,在区域状态转换(如打开、激活)前确保不超过配置的最大限制。上述打开区域的例子中,它通过验证 “新增活跃 / 打开区域数” 与 “当前计数 + 最大值” 的关系,防止资源滥用,符合 NVMe ZNS 协议对设备稳定性的要求。

3. IO 操作控制(写入 / 追加)

zns_check_zone_state_for_writ

ZNS 的写入操作严格限制为顺序写入,核心校验函数:

  • zns_check_zone_state_for_write:检查区域状态是否允许写入:

    static uint16_t zns_check_zone_state_for_write(NvmeZone *zone) {// 允许写入的状态:EMPTY、IMPLICITLY_OPEN、EXPLICITLY_OPEN、CLOSED// 拒绝写入的状态:FULL(返回NVME_ZONE_FULL)、READ_ONLY、OFFLINE等
    }
    
zns_check_zone_write
  • zns_check_zone_write 函数的核心作用是 验证对 ZNS 区域的写操作是否合法,确保符合 ZNS 协议的写入规则(如顺序写入、不超边界、状态允许等):

    static uint16_t zns_check_zone_write(FemuCtrl *n, NvmeNamespace *ns,NvmeZone *zone, uint64_t slba,uint32_t nlb, bool append) {// 1. 检查写入范围(slba + nlb)是否超过区域写边界(zslba + zcap)// 2. 检查区域状态是否允许写入
    }
    
重点检查逻辑(来自代码):
  1. 写边界检查:写入范围(slba + nlb)不能超过区域的最大可写边界(zns_zone_wr_boundary),否则返回 NVME_ZONE_BOUNDARY_ERROR
  2. 区域状态检查:仅允许对 “空(EMPTY)”“隐式打开”“显式打开”“关闭(CLOSED)” 状态的区域写入;若区域是 “满(FULL)”“离线(OFFLINE)”“只读(READ_ONLY)”,则返回对应错误。
  3. 写入顺序检查
    • 普通写入:起始 LBA(slba)必须等于区域当前写指针(w_ptr),否则返回 NVME_ZONE_INVALID_WRITE(保证顺序写入)。
    • 追加写入(append):起始 LBA 必须是区域起始 LBA(zslba),且写入大小符合限制。
实际应用例子:

假设场景:

  • 区域 A 的状态为 “显式打开(EXPLICITLY_OPEN)”,允许写入。
  • 区域起始 LBA(zslba)= 0,写指针(w_ptr)= 100,最大可写边界(zcap)= 500(即最多写到 LBA 499)。
例 1:合法写入

写入请求:slba=100,长度nlb=50(写到 LBA 149)。

  • 边界检查:100+50=150 ≤ 500 → 合法。
  • 状态检查:“显式打开” 允许写入 → 合法。
  • 顺序检查:slba=100 等于当前w_ptr=100 → 合法。
  • 结果:返回 NVME_SUCCESS,允许写入。
例 2:越界写入

写入请求:slba=480,长度nlb=30(写到 LBA 509)。

  • 边界检查:480+30=509 > 500 → 触发 NVME_ZONE_BOUNDARY_ERROR
  • 结果:拒绝写入,返回错误。
例 3:无序写入

写入请求:slba=120,长度nlb=20(当前w_ptr=100)。

  • 顺序检查:slba=120 ≠ w_ptr=100 → 触发 NVME_ZONE_INVALID_WRITE
  • 结果:拒绝写入,返回错误(ZNS 要求严格顺序写入)。
例 4:区域已满

区域 A 的状态变为 “满(FULL)”,写入请求:slba=100,长度nlb=10

  • 状态检查:“满” 状态不允许写入 → 触发 NVME_ZONE_FULL
  • 结果:拒绝写入,返回错误。
总结

zns_check_zone_write 是 ZNS 设备防止非法写入的 “守门人”,通过验证边界、状态和顺序,确保写入操作符合 ZNS 协议规范,避免数据混乱或设备异常。

4. 命名空间生命周期管理

  • zns_zoned_ns_shutdown:关闭命名空间时清理所有区域:

    static void zns_zoned_ns_shutdown(NvmeNamespace *ns) {// 遍历所有状态的区域,重置状态为EMPTY,释放资源
    }
    
  • zns_ns_cleanup:释放 ZNS 相关内存(区域数组、描述符等):

    void zns_ns_cleanup(NvmeNamespace *ns) {g_free(n->id_ns_zoned);   // 释放ZNS标识信息g_free(n->zone_array);    // 释放区域数组g_free(n->zd_extensions); // 释放扩展信息
    }
    

三、与 NVMe 协议的衔接

ZNS 作为 NVMe 协议的扩展特性,其逻辑通过以下方式嵌入 NVMe 命名空间管理流程:

1. NVMe 标识信息初始化

zns_init_zone_identify函数初始化 ZNS 相关的 NVMe 标识结构(符合 NVMe spec):

static void zns_init_zone_identify(FemuCtrl *n, NvmeNamespace *ns, int lba_index) {// 1. 初始化NvmeIdNsZoned结构(ZNS命名空间标识)://    - mar:最大活动区域数(减1,因协议为0-based)//    - mor:最大打开区域数(减1)//    - ozcs:是否支持跨区域读(0x01表示支持)//    - zsze:区域大小(单位LBA)// 2. 设置命名空间大小(nsze = 区域数 * 区域大小)// 3. 禁用不支持的特性(如DULBE,若区域大小不符合粒度要求)
}
NvmeIdNsZoned结构和NvmeIdNs的联系和区别
联系

两者均是 NVMe 协议中用于描述命名空间(Namespace)属性的结构体,用于向主机报告命名空间的配置、能力和限制。

  • 在 ZNS 设备中,NvmeIdNsZonedNvmeIdNs扩展补充,前者专注于 ZNS 特有的区域(Zone)管理特性,后者提供通用的命名空间基础信息。
  • 两者共同配合,完整描述一个 ZNS 命名空间的属性(基础信息 + 区域管理特性)。
区别
维度NvmeIdNs(通用命名空间标识)NvmeIdNsZoned(ZNS 专用命名空间标识)
适用场景所有 NVMe 命名空间(包括普通 NVM 命名空间和 ZNS 命名空间)。仅适用于 ZNS(Zoned Namespace)类型的命名空间,描述区域相关特性。
核心作用提供命名空间的基础信息,如 LBA 格式、容量、支持的操作等通用属性。提供 ZNS 特有的区域管理参数,如区域操作能力、最大活跃 / 打开区域数、区域大小等。
关键字段包含 nsze(命名空间总 LBA 数)、ncap(容量 LBA 数)、lbaf(LBA 格式数组)、flbas(当前 LBA 格式)等通用字段。包含:- zoc(区域操作能力,如支持的关闭 / 重置等操作);- mar(最大活跃区域数)、mor(最大打开区域数);- lbafe(ZNS 专用 LBA 格式,含区域大小 zsze)等 ZNS 特有字段。
代码定义未在提供的代码中完整展示,但通过引用(如 ns->id_ns)可知是命名空间的基础标识结构。zns.h 中明确定义,专门用于 ZNS 特性描述。
总结
  • NvmeIdNs 是 NVMe 命名空间的 “基础身份证”,描述通用属性;
  • NvmeIdNsZoned 是 ZNS 命名空间的 “扩展身份证”,仅在 ZNS 场景下存在,补充区域管理相关的特有属性,两者结合实现对 ZNS 命名空间的完整描述。

2. 命名空间与 ZNS 特性绑定

  • NVMe 命名空间的zoned标志(NvmeNamespaceParams.zoned)用于区分是否为 ZNS 命名空间。
  • 命名空间的生命周期(创建、关闭、清理)与 ZNS 区域管理绑定:
    • 命名空间初始化时,调用zns_init_zone_geometryzns_init_zoned_state初始化区域。
    • 命名空间关闭时,调用zns_zoned_ns_shutdown清理区域。
    • 命名空间销毁时,调用zns_ns_cleanup释放 ZNS 资源。

3. 协议命令处理

ZNS 的核心命令(如 Open、Close、Finish、Reset、Append)通过 NVMe 命令集触发,底层通过上述状态管理和 IO 校验函数实现:

  • 打开区域(Open):调用zns_aor_check检查限制,通过zns_assign_zone_state设置为 EXPLICITLY_OPEN。
  • 写入 / 追加(Write/Append):通过zns_check_zone_write校验,成功后更新写指针(w_ptr)。
  • 关闭区域(Close):通过zns_assign_zone_state设置为 CLOSED。
  • 重置区域(Reset):调用zns_clear_zone清空数据,恢复为 EMPTY 或 CLOSED。

总结

Femu 的 ZNS 实现核心逻辑是:通过区域数组管理所有 ZNS 区域,按状态分类维护链表,结合 NVMe 协议的标识和命令集,实现区域的创建、状态转换、顺序写入控制等功能。其底层通过几何参数校验确保区域配置合法,通过状态链表和写指针管理确保顺序写入特性,最终通过 FTL 层(znsftl.c)关联物理存储,完成从逻辑区域到物理介质的映射。

代码

nvme_register_znssd

int nvme_register_znssd(FemuCtrl *n)
{n->ext_ops = (FemuExtCtrlOps) {.state            = NULL,.init             = zns_init,.exit             = zns_exit,.rw_check_req     = NULL,.start_ctrl       = zns_start_ctrl,.admin_cmd        = zns_admin_cmd,.io_cmd           = zns_io_cmd,.get_log          = NULL,};return 0;
}

nvme_register_znssd 函数的核心作用是 向 NVMe 控制器注册 ZNS(Zoned Namespace)模式的操作回调,使其能够处理 ZNS 设备特有的初始化、命令执行等逻辑。它不是程序的主入口,而是 ZNS 功能的注册入口。

具体作用:

注册的函数执行时机
init = zns_init控制器初始化阶段:当 ZNS 模式被启用时,由上层初始化逻辑调用。作用:完成 ZNS 设备的核心初始化,如设置控制器名称、初始化区域几何结构(zone 大小、数量)、分配区域数组等。
exit = zns_exit控制器销毁 / 关闭阶段:当设备停止或退出 ZNS 模式时调用。作用:释放 ZNS 模式下分配的资源(如区域管理相关的内存、数据结构等)。
start_ctrl = zns_start_ctrl控制器启动阶段:在控制器完成基础初始化后、开始处理命令前调用。作用:做启动前的校验和配置,例如代码中检查页大小是否为 4096、计算并设置 ZASL(Zone Attribute Size Limit)参数,确保设备启动条件合法。
admin_cmd = zns_admin_cmd处理管理员命令时:当控制器收到 ZNS 相关的管理员命令(如命名空间管理、控制器属性配置等)时调用。作用:目前代码中默认返回 “无效操作码”,实际可扩展用于处理 ZNS 特有的管理员命令。
io_cmd = zns_io_cmd处理 IO 命令时:当控制器收到 ZNS 相关的 IO 命令(如读、写、区域追加、区域管理等)时调用。作用:分发具体命令到对应处理函数,例如:- NVME_CMD_READ 对应 zns_read- NVME_CMD_WRITE 对应 zns_write- NVME_CMD_ZONE_APPEND 对应 zns_zone_append
rw_check_req = NULL未使用(ZNS 模式下不需要额外的读写请求检查)。
get_log = NULL未使用(ZNS 模式下暂不支持日志获取功能)。

简单说,这些函数是 ZNS 模式的 “事件响应器”:初始化时 setup 环境,启动时做校验,运行时处理具体命令,关闭时清理资源,各司其职完成 ZNS 设备的完整生命周期管理。

http://www.dtcms.com/a/546542.html

相关文章:

  • 做结构图的网站云南做网站哪家便宜
  • 甘肃古典建设集团有限公司网站要想让别人网站卖我的东西怎么做
  • 深入理解Java String:不可变性、内存机制与高效操作
  • 仓颉GC调优参数:垃圾回收的精密控制艺术
  • 小学生做网站步骤wordpress 调用媒体库
  • 徐州营销型网站制使做网站广告联盟赚钱
  • 常州网站营销推广免费科技软件
  • 如何使用框架来建设网站常州网站排名优化
  • 网站 网络营销价值雷山网站快速排名
  • Spring Cloud 多租户实现(MySQL + MyBatis + MyBatis-Plus 实战)
  • 专注网站建设与制作网站图片的暗纹是怎么做的
  • 给周杰伦做网站网站做百度推广划算吗
  • k8s——实战入门(资源)
  • 茶叶网站建设策划方案u001f代做毕设自己专门网站
  • 找建设网站网站建设搜索优化app推广新闻营销
  • 山东集团网站建设做网站免费的域名
  • 做平面什么网站的素材不侵权北京公司提供注册地址
  • Linux学习笔记--中断子系统
  • Selenium常用方法
  • 哈尔滨网站建设推广服务人力资源网站
  • screen命令指南
  • SAP-ABAP:穿越时空的ABAP基石:深入理解WRITE语句的奥秘与技巧实例详解
  • 做网站产品图片素材前端后端分别是什么意思
  • 做网站销售的昆明百度小程序
  • LeeCode 137. 只出现一次的数字II
  • AOI设备在消费电子领域的检测应用
  • 网站制作 成都土巴兔官网
  • 如何 做网站跳转建设网站企业网上银行
  • 基于需求驱动的自动驾驶感知任务数据集缺口识别与缓解方法
  • 上海文明城市建设网站如何做网站商铺