Sober7135's Blog

we can go anywhere we want.


如何在 FEMU 中添加 NVMe CMD

水一篇

Background


什么是 FEMU

FEMU 是一个基于 QEMU 的 SSD Emulator, 发表在 FAST'18

什么是 NVMe 协议

维基百科1

NVM Express(缩写NVMe),或称非易失性内存主机控制器接口规范(英語:Non-Volatile Memory Host Controller Interface Specification,缩写:NVMHCIS),是一个逻辑设备接口规范。

NVM Express® Specification Family2

可以看到 NVMe specification 主要分为以上这几部分, 本文讨论的最基础的部分, 不涉及 ZNS, KV, RDMA…等, 即 FEMU 中最基础的 blackbox mode.

NVMe 中的 COMMAND

笔者了解也不是很全面, 只是简单介绍一下 I/O Commands 和 Admin Command 3

The Admin Command Set defines the commands that may be submitted to the Admin Submission Queue. An I/O command is a command submitted to an I/O Submission Queue.

可以简单理解为, Admin Command 和 I/O Command 的区别是提交的位置不同

  • I/O Command 可以简单理解为 I/O 相关的, 比如 Write/Read/Fluash…
  • Admin 顾名思义就是管理类的, 比如管理 I/O Submission/Completion Queue

I/O Command 可以根据 I/O Command Set 继续细分, e.g. NVM, Key Value, Zoned Namespace, 这里我们只关注 NVM

这里我们是需要添加 Command, 所以只需要关注 Vendor specific 部分就可以了

Opcodes for Admin Commands:

Opcode by FieldCombined
Opcode
Namespace
Identifier Used
CommandReference
(07:02)
Function
(01:00)
Data Transfer
..................
0011 01b01b35hNoManage Exported Port5.3.9
0011 10b01b39hNoSend Discovery Log Page5.3.10
Vendor Specific
11xx xxbNOTE 3C0h to FFhVendor specific

如果我们需要添加一条自定义的 command 按照 Vendor Specific 的格式即可

如何在 FEMU 中实现?

先说一下我的需求, 需求是记录一段时间的 latency, 这样我们就需要两个 command

  1. 用于 clear 当前的结果
  2. 用于 report 特定时间的 latency

出于简单考虑, 直接就选择 Admin, CMD 的结果呈现在 LOG 中

添加 opcode

enum NvmeAdminCommands {
    // ...
    NVME_ADM_CMD_SECURITY_SEND  = 0x81,
    NVME_ADM_CMD_SECURITY_RECV  = 0x82,
    NVME_ADM_CMD_SET_DB_MEMORY  = 0x7c,
+   NVME_ADM_CMD_PRINT_LATENCY  = 0xc0,
+   NVME_ADM_CMD_CLEAR_LATENCY  = 0xc1,
    NVME_ADM_CMD_FEMU_DEBUG     = 0xee,
    NVME_ADM_CMD_FEMU_FLIP      = 0xef,
};

ssd_read/ssd_write 后, 更新我们需要统计的信息

            switch (req->cmd.opcode) {
            case NVME_CMD_WRITE:
                lat = ssd_write(ssd, req);
+               lat_stat_update(&n->write_lat_stat, lat);
                break;
            case NVME_CMD_READ:
                lat = ssd_read(ssd, req);
+               lat_stat_update(&n->read_lat_stat, lat);
                break;
            case NVME_CMD_DSM:
                lat = 0;
                break;
            default:
                //ftl_err("FTL received unkown request type, ERROR\n");
                ;
            }

需要处理新添加的 opcode, 这里出于简单考虑直接用 log 输出

如果需要正经得到结果可能需要添加 I/O command, 然后还得修改 nvme-cli 源代码来从 memory display 出这个信息

static uint16_t bb_admin_cmd(FemuCtrl *n, NvmeCmd *cmd)
{
    switch (cmd->opcode) {
    case NVME_ADM_CMD_FEMU_FLIP:
        bb_flip(n, cmd);
        return NVME_SUCCESS;
+   case NVME_ADM_CMD_PRINT_LATENCY:
+       femu_log( "print latency statistic: "); 
+       femu_log("\t[write]: avg=%lf, stdev=%lf, count=%lu", n->write_lat_stat.mean, 
+           sqrt(n->write_lat_stat.m2 / n->write_lat_stat.count), n->write_lat_stat.count);
+       femu_log("\t[read]: avg=%lf, stdev=%lf, count=%lu", n->read_lat_stat.mean, 
+           sqrt(n->read_lat_stat.m2 / n->read_lat_stat.count), n->read_lat_stat.count);
+       return NVME_SUCCESS;
+   case NVME_ADM_CMD_CLEAR_LATENCY:
+       femu_log( "clear latency statistic.");
+       memset(&n->read_lat_stat, 0, sizeof(LatencyStat));
+       memset(&n->write_lat_stat, 0, sizeof(LatencyStat));
+       return NVME_SUCCESS;
    default:
        return NVME_INVALID_OPCODE | NVME_DNR;
    }
}

运行

  • sudo nvme admin-passthru /dev/nvme0 --opcode=0xc0 打印 latency 统计信息
  • sudo nvme admin-passthru /dev/nvme0 --opcode=0xc1 清空 latency 统计信息

结果类似这样

2024-10-22 19:43:14 [FEMU] ../hw/femu/bbssd/bb.c:104 bb_admin_cmd Log: print latency statistic: 
2024-10-22 19:43:14 [FEMU] ../hw/femu/bbssd/bb.c:105 bb_admin_cmd Log:  [write]: avg=371867.940275, stdev=3076658.698984, count=1296538
2024-10-22 19:43:14 [FEMU] ../hw/femu/bbssd/bb.c:107 bb_admin_cmd Log:  [read]: avg=229969.164519, stdev=3101376.001435, count=1945039

Reference