什么是NVMe
NVMe全称NonVolatile Memory Express(非易失性内存主机控制器接口规范),其官方(NVMe官网NVM Express)定义将其描述为“一个开放的标准和信息集合,以充分释放非易失性存储在从移动端到数据中心的所有类型的计算环境中能够提供的优势。NVMe从底层开始设计,为当前和未来的NVM技术提供高带宽和低延迟存储访问。”
NVMe的历史
机械硬盘(HDD,Hard Disk Drive的缩写形式)其内部主要由马达(控制电机)、磁盘片、磁头、转轴、磁头控制器、数据转换器、接口和缓存等几个部分组成,它存在体积大、使用过程容易受震动和碰撞等影响导致损坏、速度慢等问题。固态硬盘(SSD,Solid State Drives的缩写形式)由主控和固态存储芯片(通常是NAND Flash)阵列构成,如下图所示:
相比与机械硬盘,其存在的优点包括:读写速度快、防震抗摔性、低功耗、无噪音、工作温度范围大、轻便等等。
SSD开始作为存储使用时,一般使用如SATA、SAS或光纤通道等接口与计算机的总线连接。随着固态硬盘在大众市场上的流行,SATA已成为个人计算机中连接SSD的最典型方式。但是,SATA的设计主要是作为机械硬盘的接口,并随着时间的推移越来越难满足速度日益提高的SSD。随着在大众市场的流行,许多固态硬盘的数据速率提升已经放缓。不同于机械硬盘,部分SSD已受到SATA最大吞吐量的限制。
在NVMe出现之前,高端SSD只得以采用PCI Express总线制造,但需使用非标准规范的接口。若使用标准化的SSD接口,操作系统只需要一个驱动程序就能使用匹配规范的所有SSD。这也意味着每个SSD制造商不必用额外的资源来设计特定接口的驱动程序。
2009年Intel开始着手寻找SATA的替代方案。SATA作为串行接口,采用AHCI规范,其已经成为制约SSD速度的瓶颈。AHCI只有1个命令队列,队列深度32;而NVMe可以有65535个命令队列,每个队列都可以深达65536个命令。NVMe也充分使用了MSI的2048个中断向量优势,延迟大大减小。
后续的版本:
NVMe基础
NVMe是一种用于高速并行数据传输的协议,在存储架构中的位置:
使用NVMe可减少固态硬中使用的每个输入/输出(I/O)的系统开销。由于设备驱动器的更改允许并行和轮询,因此NVMe SSD能够提供比传统硬盘驱动器(HDD)更快的响应时间。这些改进有助于减少延迟,使其成为企业工作负载以及众多消费者和专业应用程序的理想选择。
NVMe基于PCIe,所以它不像SATA这样需要额外的控制器,而是可以直接与CPU或者芯片组连接:
以下是使用 NVMe 存储相对于 SAS 或 SATA 驱动器的一些优势:
- 提升性能:NVMe技术可以使用PCIe将SSD存储直接连接到服务器或中央处理单元(CPU)。凭借性能的显著提高,对于游戏玩家、视频编辑器和其他需要比SAS或SATA HDD更高性能的用户来说,NVMe技术成为了首选数据存储/传输选项。
- 速度更快:NVMe驱动器可以提供比 SAS或 SATA 驱动器更高的速度,因为它们可以更快地发送和接收NVMe命令并提供更好的吞吐量。
- 提高兼容性:NVMe被广泛视为比SAS/SATA更具兼容性的选项,并且随着AI、ML和云计算等快速发展的关键技术的发展而经常更新。NVMe技术可以与所有现代操作系统无缝协作,包括手机、笔记本电脑和游戏机。
- 改进的带宽:PCIe连接比SAS或SATA端口更宽并且具有更多的带宽。它还随着每一代的改进而改进,带宽是上一代的两倍。SAS和SATA的带宽连接要低得多并且是固定的,因此它们不会随着时间的推移而改进。PCIe连接脱颖而出的另一个特点是它们在“通道”中可扩展,因此即使在同一代产品中,用户也可以通过两倍的通道数量将带宽加倍。
BIOS下的NVMe驱动
BIOS下的NVMe驱动对应模块时edk2\MdeModulePkg\Bus\Pci\NvmExpressDxe\NvmExpressDxe.inf。它时一个UEFI Model Driver,所以有以下的接口:
//
// NVM Express Driver Binding Protocol Instance
//
EFI_DRIVER_BINDING_PROTOCOL gNvmExpressDriverBinding = {
NvmExpressDriverBindingSupported,
NvmExpressDriverBindingStart,
NvmExpressDriverBindingStop,
0x10,
NULL,
NULL
};
该模块依赖于gEfiDevicePathProtocolGuid
和gEfiPciIoProtocolGuid
,最终提供的主要接口是gEfiNvmExpressPassThruProtocolGuid
、gEfiBlockIoProtocolGuid
、gEfiBlockIo2ProtocolGuid
、gEfiDiskInfoProtocolGuid
:
NvmExpressDriverBindingSupported
Supported主要就是判断Device Path和PCI IO的情况。
前者需要判断Device Path的类型,主要需要关注的是RemainingDevicePath
部分:
//
// Check whether device path is valid
//
if (RemainingDevicePath != NULL) {
//
// Check if RemainingDevicePath is the End of Device Path Node,
// if yes, go on checking other conditions
//
if (!IsDevicePathEnd (RemainingDevicePath)) {
//
// If RemainingDevicePath isn't the End of Device Path Node,
// check its validation
//
DevicePathNode.DevPath = RemainingDevicePath;
if ((DevicePathNode.DevPath->Type != MESSAGING_DEVICE_PATH) ||
(DevicePathNode.DevPath->SubType != MSG_NVME_NAMESPACE_DP) ||
(DevicePathNodeLength (DevicePathNode.DevPath) != sizeof (NVME_NAMESPACE_DEVICE_PATH)))
{
return EFI_UNSUPPORTED;
}
}
}
这是因为NVMe存在逻辑上的分区,称为Namespace。
后者主要是通过PCI IO获取Class Code:
//
// Examine Nvm Express controller PCI Configuration table fields
//
if ((ClassCode[0] != PCI_IF_NVMHCI) || (ClassCode[1] != PCI_CLASS_MASS_STORAGE_NVM) || (ClassCode[2] != PCI_CLASS_MASS_STORAGE)) {
Status = EFI_UNSUPPORTED;
}
两者判断都成功之后才会执行Start函数。
NvmExpressDriverBindingStart
该函数中的操作包括:
- 初始化
NVME_CONTROLLER_PRIVATE_DATA
。
//
// Nvme private data structure.
//
struct _NVME_CONTROLLER_PRIVATE_DATA {
UINT32 Signature;
EFI_HANDLE ControllerHandle;
EFI_HANDLE ImageHandle;
EFI_HANDLE DriverBindingHandle;
EFI_PCI_IO_PROTOCOL *PciIo;
UINT64 PciAttributes;
EFI_DEVICE_PATH_PROTOCOL *ParentDevicePath;
EFI_NVM_EXPRESS_PASS_THRU_MODE PassThruMode;
EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL Passthru;
//
// pointer to identify controller data
//
NVME_ADMIN_CONTROLLER_DATA *ControllerData;
//
// 6 x 4kB aligned buffers will be carved out of this buffer.
// 1st 4kB boundary is the start of the admin submission queue.
// 2nd 4kB boundary is the start of the admin completion queue.
// 3rd 4kB boundary is the start of I/O submission queue #1.
// 4th 4kB boundary is the start of I/O completion queue #1.
// 5th 4kB boundary is the start of I/O submission queue #2.
// 6th 4kB boundary is the start of I/O completion queue #2.
//
UINT8 *Buffer;
UINT8 *BufferPciAddr;
//
// Pointers to 4kB aligned submission & completion queues.
//
NVME_SQ *SqBuffer[NVME_MAX_QUEUES];
NVME_CQ *CqBuffer[NVME_MAX_QUEUES];
NVME_SQ *SqBufferPciAddr[NVME_MAX_QUEUES];
NVME_CQ *CqBufferPciAddr[NVME_MAX_QUEUES];
//
// Submission and completion queue indices.
//
NVME_SQTDBL SqTdbl[NVME_MAX_QUEUES];
NVME_CQHDBL CqHdbl[NVME_MAX_QUEUES];
UINT16 AsyncSqHead;
//
// Flag to indicate internal IO queue creation.
//
BOOLEAN CreateIoQueue;
UINT8 Pt[NVME_MAX_QUEUES];
UINT16 Cid[NVME_MAX_QUEUES];
//
// Nvme controller capabilities
//
NVME_CAP Cap;
VOID *Mapping;
//
// For Non-blocking operations.
//
EFI_EVENT TimerEvent;
LIST_ENTRY AsyncPassThruQueue;
LIST_ENTRY UnsubmittedSubtasks;
};
- 创建与NVMe交互的Buffer。
//
// 6 x 4kB aligned buffers will be carved out of this buffer.
// 1st 4kB boundary is the start of the admin submission queue.
// 2nd 4kB boundary is the start of the admin completion queue.
// 3rd 4kB boundary is the start of I/O submission queue #1.
// 4th 4kB boundary is the start of I/O completion queue #1.
// 5th 4kB boundary is the start of I/O submission queue #2.
// 6th 4kB boundary is the start of I/O completion queue #2.
//
// Allocate 6 pages of memory, then map it for bus master read and write.
//
Status = PciIo->AllocateBuffer (
PciIo,
AllocateAnyPages,
EfiBootServicesData,
6,
(VOID **)&Private->Buffer,
0
);
if (EFI_ERROR (Status)) {
goto Exit;
}
Bytes = EFI_PAGES_TO_SIZE (6);
Status = PciIo->Map (
PciIo,
EfiPciIoOperationBusMasterCommonBuffer,
Private->Buffer,
&Bytes,
&MappedAddr,
&Private->Mapping
);
if (EFI_ERROR (Status) || (Bytes != EFI_PAGES_TO_SIZE (6))) {
goto Exit;
}
- 构建
EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL
,它是与NVMe交互的接口。
//
// Protocol Interface Structure
//
struct _EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL {
EFI_NVM_EXPRESS_PASS_THRU_MODE *Mode;
EFI_NVM_EXPRESS_PASS_THRU_PASSTHRU PassThru;
EFI_NVM_EXPRESS_PASS_THRU_GET_NEXT_NAMESPACE GetNextNamespace;
EFI_NVM_EXPRESS_PASS_THRU_BUILD_DEVICE_PATH BuildDevicePath;
EFI_NVM_EXPRESS_PASS_THRU_GET_NAMESPACE GetNamespace;
};
- 创建定时器处理Asynchronous I/O。
//
// Start the asynchronous I/O completion monitor
//
Status = gBS->CreateEvent (
EVT_TIMER | EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
ProcessAsyncTaskList,
Private,
&Private->TimerEvent
);
if (EFI_ERROR (Status)) {
goto Exit;
}
Status = gBS->SetTimer (
Private->TimerEvent,
TimerPeriodic,
NVME_HC_ASYNC_TIMER
);
if (EFI_ERROR (Status)) {
goto Exit;
}
- 遍历NVMe所有的Namespace,并在其上安装Block IO等Protocol,重点函数是:
/**
Check if the specified Nvm Express device namespace is active, and create child handles
for them with BlockIo and DiskInfo protocol instances.
@param[in] Private The pointer to the NVME_CONTROLLER_PRIVATE_DATA data structure.
@param[in] NamespaceId The NVM Express namespace ID for which a device path node is to be
allocated and built. Caller must set the NamespaceId to zero if the
device path node will contain a valid UUID.
@retval EFI_SUCCESS All the namespaces in the device are successfully enumerated.
@return Others Some error occurs when enumerating the namespaces.
**/
EFI_STATUS
EnumerateNvmeDevNamespace (
IN NVME_CONTROLLER_PRIVATE_DATA *Private,
UINT32 NamespaceId
)
有了Block IO等接口,就可以接入到了UEFI BIOS中,后续可以进行读写NVMe和创建启动项等操作。
参考资料
- 了解 SSD 技术:NVMe、SATA、M.2 - 金士顿科技 (kingston.com.cn)
- 什么是 NVMe?| IBM