你所不知道的NVME

NVMe SSD具有高性能、低时延等优点,是目前存储行业的研究热点之一,但在光鲜的性能下也同样存在一些没有广为人知的问题,而这些问题其实对于一个生产系统而言至关重要,例如:

  • QoS无法做到100%保证;
  • 读写混合情况下,与单独读相比,性能下降严重,且读长尾延迟比较严重;

所以如何利用好NVMe盘的性能,并更好的为业务服务,我们需要从硬件,Linux内核等多个角度去剖析和解决。

从内核中NVMe IO框架来看其中存在的问题

当前Linux内核中对NVMe SSD的访问是通过MQ框架来实现的,接入NVMe驱动后直接略过IO调度器,具体实现上来说是从block layer中的通用块层回调make_request从而打通上下层IO通路。示意图如下,这里面有几个关键的点:

image

IO发送过程

MQ的框架提升性能最主要的将锁的粒度按照硬件队列进行拆分,并与底层SSD的队列进行绑定,理想的情况每一个CPU都有对应的硬件发送SQ与响应CQ,这样可以并发同时彼此之前无影响。按照NVMe SPEC协议中的标准,硬件最多支持64K个队列,所以理想情况下硬件队列个数将不会是我们需要担心的地方。但是实际情况又如何呢?由于硬件队列的增加会给NVMe SSD带来功耗的增加,所以不同的厂商在设计硬件队列个数时的考量是不同的,比如intel P3600支持32个队列,intel最新的P4500支持128个,但是SUMSUNG PM963却只支持到8个。那么当CPU的个数超过硬件的队列个数,就会出现多个CPU共用一个硬件队列的情况,对性能就会产生影响。

下面使用SUMSUNG PM963做一个简单的测试:

#!/bin/bash

fio -name=test1 -filename=/dev/nvme11n1 -thread=1 -bs=4k -rw=randread -iodepth=256 -direct=1 -numjobs=1 -ioengine=libaio -group_reporting  -runtime=60 -cpus_allowed=1 --output=urgent.txt  &

fio -name=test2 -filename=/dev/nvme11n1 -thread=1 -bs=4k -rw=randread -iodepth=256 -direct=1 -numjobs=1 -ioengine=libaio -group_reporting  -runtime=60 -cpus_allowed=10 --output=high.txt &

测试结果为

read : io=59198MB, bw=986.61MB/s, iops=252567, runt= 60002msec

read : io=59197MB, bw=986.62MB/s, iops=252571, runt= 60001msec

也就是整个IOPS可以达到50w

如果使用同一个硬件队列

#!/bin/bash

fio -name=test1 -filename=/dev/nvme11n1 -thread=1 -bs=4k -rw=randread -iodepth=256 -direct=1 -numjobs=1 -ioengine=libaio -group_reporting  -runtime=60 -cpus_allowed=1 --output=urgent.txt  &

fio -name=test2 -filename=/dev/nvme11n1 -thread=1 -bs=4k -rw=randread -iodepth=256 -direct=1 -numjobs=1 -ioengine=libaio -group_reporting  -runtime=60 -cpus_allowed=2 --output=high.txt &

结果为:

read : io=51735MB, bw=882937KB/s, iops=220734, runt= 60001msec

read : io=52411MB, bw=894461KB/s, iops=223615, runt= 60001msec

整个IOPS只有44w,性能下降12%,主要原因是多个CPU共用硬件队列进行发送的时候会有自旋锁争抢的影响。所以对于共用硬件队列的情况下,如何绑定CPU是需要根据业务的特点来确定的。

IO响应过程

IO响应过程中最主要问题是中断的balance,由于默认linux中并没有对NVMe的中断进行有效的绑定,所以不同的绑定策略会带来截然不同的性能数据。不过在我们的实际测试中,虽然我们没有做中断的绑定,但是貌似不管是性能还是稳定性的下降并没有那么严重,什么原因呢?根据我们的分析,这里面最主要的原因是(后面也会提到),我们并没有大压力的使用NVMe盘,所以实际的应用场景压力以及队列深度并不大。

从应用本身的IO Pattern来看使用NVMe问题

我们在评测一个NVMe SSD的性能的时候,往往都是通过benchmark工具,例如见1, 见2

然而这些测试的结果与业务实际使用NVMe SSD看到的性能相比差距很大, 原因是因为这些性能测试存在两个比较大的误区,因而并不能反映生产系统的真实情况。

1. 片面夸大了NVMe盘的性能

从上面两篇文章中的测试中我们可以看到,大多数压测中使用的队列深度为128,并且是用libaio这样的异步IO模式来下发IO。但是在实际应用场景中很少有这么大的队列深度。在这种场景下,根据“色子效应”,并不会将NVMe盘的并发性能充分发挥出来。

image

image

2. 低估了NVMe的长尾延迟

然而在另外一些场景下,队列深度又会非常高(比如到1024甚至更高),在这种情况下NVMe SSD带来的QoS长尾延迟影响比上面的benchmark的测试又严重得多。

所以综合起来看,这种评测选择了一个看上去没啥大用的场景做了测试,所以得出的结果也对我们实际的应用基本没有参考价值。那么问题出在什么地方么?

问题分析

首先让我们再次强调一下一般评测文章中benchmark进行的测试场景的特点:

  1. 大多是fio工具,开启libaio引擎增加IO压力
  2. 队列深度到128或者256

在这种场景下确实基本都可以将NVMe盘的压力打满。

在展开分析问题的原因之前,我们先看看Linux内核的IO栈

image

在实际应用中,VFS提供给应用的接口,从IO的特点来分类,大致上可以分为两类:direct IO与buffer IO。使用direct IO的业务大多在应用本身就已经做了一层cache,不依赖OS提供的page cache。其他的情况大多使用的都是buffer IO。linux kernel中的block layer通过REQ_SYNC与~REQ_SYNC这两种不同的标志来区分这两类IO。大家常用的direct IO这个类型,内核要保证这次IO操作的数据落盘,并且当响应返回以后,应用程序才能够认为这次IO操作是完成的。所以是使用了这里的REQ_SYNC标志,而对应的buffer IO,则大量使用了~REQ_SYNC的标志。让我们一个一个看过去。

direct IO

由于在实际使用方式中AIO还不够成熟,所以大多使用direct IO。但是direct IO是一种SYNC模式,并且完全达不到测试用例中128路并发AIO的效果。
这主要两个方面原因:

direct io在下发过程中可能会使用文件粒度的锁inode->i_mutex进行互斥。

101 static ssize_t
102 ext4_file_dio_write(struct kiocb *iocb, const struct iovec *iov,
103                     unsigned long nr_segs, loff_t pos)
104 {
105         struct file *file = iocb->ki_filp;
106         struct inode *inode = file->f_mapping->host;
107         struct blk_plug plug;
108         int unaligned_aio = 0;
109         ssize_t ret;
110         int overwrite = 0;
111         size_t length = iov_length(iov, nr_segs);
112
            ..........
122
123         BUG_ON(iocb->ki_pos != pos);
124
125         mutex_lock(&inode->i_mutex);
127

前面说的IO SYNC模式

1052 static inline ssize_t
1053 do_blockdev_direct_IO(int rw, struct kiocb *iocb, struct inode *inode,
1054         struct block_device *bdev, const struct iovec *iov, loff_t offset,
1055         unsigned long nr_segs, get_block_t get_block, dio_iodone_t end_io,
1056         dio_submit_t submit_io, int flags)

     ...........

1283         if (retval != -EIOCBQUEUED)
1284                 dio_await_completion(dio);

也就是说,我们很难通过direct IO来达到压满NVMe盘的目的。如果一定要打满NVMe盘,那么一方面要提高进程并发,另外一方面还要提高多进程多文件的并发。而这是生产系统中很难满足的。

buffer IO

我们再来看看buffer IO的特点。下面我使用了比较简单的fio通过buffer IO的模式下发,而且通过rate限速,我们发现平均下来每秒的数据量不到100MB,整个IO的特点如下:

fio -name=test1 -filename=/home/nvme/test1.txt -thread=1 -bs=4k -rw=randwrite -iodepth=32 -numjobs=32 -ioengine=sync -size=10G -group_reporting -runtime=600 -rate=200m -thinktime=1000

06/11/17-14:06:02   0.00    0.00    0.00   78.2K    0.00  354.3K    4.53  313.00    3.84    0.00   30.90
06/11/17-14:06:03   0.00    0.00    0.00  193.0K    0.00  866.5K    4.49  781.00    3.91    0.00   76.40
06/11/17-14:06:04   0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
06/11/17-14:06:05   0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
06/11/17-14:06:06   0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
06/11/17-14:06:07   0.00    0.00    0.00    3.00    0.00   12.00    4.00    0.00    0.00    0.00    0.00
06/11/17-14:06:08   0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
06/11/17-14:06:09   0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
Time              ----------------------------------------nvme11n1----------------------------------------
Time               rrqms   wrqms      rs      ws   rsecs   wsecs  rqsize  qusize   await   svctm    util
06/11/17-14:06:10   0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
06/11/17-14:06:11   0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
06/11/17-14:06:12   0.00    0.00    0.00    3.00    0.00   12.00    4.00    0.00    0.00    0.00    0.00
06/11/17-14:06:13   0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
06/11/17-14:06:14   0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
06/11/17-14:06:15   0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
06/11/17-14:06:16   0.00    0.00    0.00   32.5K    0.00  152.0K    4.68  128.00    3.83    0.00   13.00
06/11/17-14:06:17   0.00    0.00    0.00   31.8K    0.00  152.0K    4.78  128.00    3.95    0.00   13.10
06/11/17-14:06:18   0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
06/11/17-14:06:19   0.00    0.00    0.00  229.2K    0.00    1.1M    4.85  986.00    4.19    0.00   97.00
06/11/17-14:06:20   0.00    0.00    0.00  100.7K    0.00  467.8K    4.64  419.00    4.07    0.00   41.40
06/11/17-14:06:21   0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
06/11/17-14:06:22   0.00    0.00    0.00    3.00    0.00   12.00    4.00    0.00    0.00    0.00    0.00
FUNC                              COUNT
submit_bio                       113805
FUNC                              COUNT
submit_bio                       143703
FUNC                              COUNT
submit_bio                          154
FUNC                              COUNT
submit_bio                           86
FUNC                              COUNT
submit_bio                           78
FUNC                              COUNT
submit_bio                           96
FUNC                              COUNT
submit_bio                          124

抓取了下submit_bio在每秒的调用次数并分析可以得出,buffer IO在下刷的时候并不会考虑QD的多少,而是类似aio那样,kworker将需要下发的脏页都会bio形式下发,而且不需要等待某些bio返回。注意这里面有一个细节从qusize观察到IO最大值986,并没有达到百K,或者几十K,这个原因是由本身MQ的框架中tag机制nr_request决定,目前单Q设置默认值一般为1024。总之buffer IO这样特点的结果就是突发量的高iops的写入,buffer IO对于应用程序来说是不可见的,因为这是linux kernel的本身的刷脏页行为。但是它带给应用的影响确实可见的,笔者曾经总结过异步IO的延时对长尾的影响,如下图所示,分别是buffer IO与direct IO在相同带宽下延时表现,可以看出这延迟长尾比我们简单的通过fio benchmark测试严重的多,特别是盘开始做GC的时候,抖动更加严重;而且随着盘的容量用着越来越多,GC的影响越来越大,长尾的影响也是越来越严重。

image

在HDD的时代上面的问题同样会存在,但是为什么没有那么严重,原因主要是HDD大多使用CFQ调度器,其中一个特性是同步、异步IO队列分离。并且在调度过程中同步优先级比较高,在调度抢占、时间片等都是同步优先。

解决问题

前面描述了使用NVMe硬盘的严重性,下面介绍一下如何解决这些问题。
(1)MQ绑定的问题,需要根据当前业务的特点,如果硬件的队列小于当前CPU的个数,尽量让核心业务上跑的进程分散在绑定不同硬件队列的CPU上,防止IO压力大的时候锁资源的竞争。

(2)中断绑定CPU,建议下发的SQ的CPU与响应的CQ的CPU保持一致,这样各自CPU来处理自己的事情,互相业务与中断不干扰。

(3)解决direct IO状态下长尾延迟,因为长尾延迟是本身NVMe SSD Controller带来,所以解决这个问题还是要从控制器入手,使用的方法有WRR(Weight Round Robin),这个功能在当前主流厂商的最新的NVMe SSD中已经支持。

image

(4)解决buffer IO状态下长尾延迟,可以通过控制NVME SSD处理的QD来解决,使用的NVME多队列IO调度器,充分利用了MQ框架,根据同步写、读延迟动态调整异步IO的队列,很好的解决buffer io带来的长尾延迟。

<think>我们正在解决嵌入式Linux系统中使用RAIDON模式时无法识别NVMe硬盘的问题。根据引用[1]的内容,我们知道在Linux内核配置中,需要启用ATA设备支持(包括SATA/PATA),同时还需要注意SCSI相关的支持(因为ATA设备可能需要SCSI子系统的支持)。此外,引用[1]还提到了RAID和LVM的支持配置(Multipledevicesdriversupport(RAIDandLVM))。但是,我们的问题是关于NVMe硬盘的,而NVMe是一种同于ATA(SATA/PATA)的接口,它使用PCIe总线,因此我们需要关注内核中NVMe驱动的配置。引用[2]提到了Windows下的驱动框架,但我们的问题在嵌入式Linux系统上,因此这个引用可能直接相关。过,它提醒我们要确保内核中包含了正确的驱动程序。因此,我们需要关注以下几点:1.确保内核配置中启用了NVMe驱动。2.由于使用RAIDON模式(通常是指主板BIOS/UEFI中将硬盘模式设置为RAID,但实际上可能只是将硬盘作为单个磁盘使用,但以RAID模式呈现给操作系统),我们需要确保内核支持处理这种模式下的NVMe设备。3.在RAIDON模式下,NVMe设备可能被识别为SCSI设备(因为某些主板RAID控制器会将NVMe设备映射为SCSI设备),因此需要确保SCSI相关的驱动也启用。解决方案步骤:步骤1:检查内核配置,确保启用了NVMe驱动在内核配置中,需要确保以下选项启用:-`CONFIG_NVME_CORE=y`-`CONFIG_NVME_QUIRKS=y`(可能需要的修复)-根据主控选择相应的驱动,如`CONFIG_NVME_HWMON=y`(如果需要硬件监控)以及`CONFIG_NVME_FABRICS`(通常需要,除非是fabrics设备)等。-对于PCIeNVMe设备,确保`CONFIG_PCI`和`CONFIG_PCIEPORTBUS`启用,以及`CONFIG_NVME_PCI=y`步骤2:检查SCSI驱动支持由于在RAIDON模式下,NVMe设备可能被呈现为SCSI设备,因此需要确保SCSI磁盘驱动启用:-`CONFIG_SCSI=y`-`CONFIG_BLK_DEV_SD=y`(SCSI磁盘支持)步骤3:启用RAID支持虽然我们可能没有使用多个设备的RAID,但主板RAID模式可能需要内核的RAID支持来识别设备。因此,确保:-`CONFIG_MD=y`(Multipledevicesdriversupport(RAIDandLVM))-如果需要,可以启用特定的RAID级别支持(如`CONFIG_MD_RAID0`,`CONFIG_MD_RAID1`等),但即使启用这些,通常也能识别单个磁盘。步骤4:重新编译内核并更新修改内核配置后,重新编译内核,并更新到嵌入式系统中。步骤5:检查设备节点启动后,使用命令`lsblk`或`lspci-k`检查NVMe设备是否被识别。如果被识别为SCSI设备,那么应该出现在`/dev/sdX`设备节点;如果是原生NVMe设备,则应该出现在`/dev/nvmeXnY`。步骤6:加载必要的内核模块如果驱动编译为模块,则需要确保模块被加载:-对于NVMe:`modprobenvmenvme_corenvme_pci`-对于SCSI:`modprobesd_mod`如果问题仍然存在,可能需要检查系统启动日志(`dmesg`)以获取更多信息。注意:在某些主板的RAIDON模式下,可能需要额外的驱动(如Intel的VMD驱动),特别是对于Intel平台。如果主板是Intel的,并且启用了VMD(VolumeManagementDevice),则需要启用内核选项`CONFIG_PCIE_DPC`和`CONFIG_VMD`,并加载`vmd`模块。综上所述,我们需要确保内核配置正确,并重新编译内核。相关问题:1.如何配置嵌入式Linux内核?2.如何查看Linux系统启动日志中的设备识别信息?3.在Linux系统中,如何加载内核模块?4.如何确定NVMe硬盘在Linux系统中是否被正确识别?5.嵌入式Linux系统中如何重新编译内核?</think>### 嵌入式 Linux RAID ON 模式下 NVMe 硬盘无法识别的解决方案 在嵌入式 Linux 系统中使用 RAID ON 模式时无法识别 NVMe 硬盘,通常是由以下原因导致: 1. **内核缺少 NVMe 驱动支持** RAID ON 模式依赖主板 RAID 控制器,但原生 NVMe 驱动可能未启用或未编译进内核。 **解决方案**: - 检查内核配置:确保启用 `CONFIG_NVME_CORE=y` 和 `CONFIG_NVME_PCI=y` - 若使用 Intel VMD 技术(常见于服务器平台),需额外启用 `CONFIG_VMD=y` ```bash # 查看当前内核配置 zcat /proc/config.gz | grep NVME ``` 2. **RAID 控制器驱动未加载** 主板 RAID 控制器(如 Intel VROC、AMD RAID)需要特定驱动支持[^1]。 **解决方案**: - 加载对应内核模块: ```bash # Intel 平台尝试加载 vmd 模块 modprobe vmd # 查看设备是否识别 lspci -v | grep RAID ``` 3. **SCSI 转换层缺失** 某些 RAID 控制器将 NVMe 设备映射为 SCSI 设备,需启用 SCSI 子系统支持[^1]。 **解决方案**: - 在 kernel 配置中启用: ``` CONFIG_SCSI=y CONFIG_BLK_DEV_SD=y # SCSI 磁盘支持 ``` 4. **UEFI 配置问题** BIOS/UEFI 中 RAID ON 模式设置完整。 **解决方案**: - 进入主板设置: - 确认 NVMe 硬盘已加入 RAID 阵列 - 关闭 "Secure Boot"(可能阻止驱动加载) - 更新主板固件至最新版本 5. **Initramfs 未包含驱动** 嵌入式系统 initramfs 可能缺失必要模块。 **解决方案**: - 更新 initramfs(以 Debian 为例): ```bash echo "nvme vmd" >> /etc/initramfs-tools/modules update-initramfs -u ``` **诊断步骤**: 1. 检查硬件识别: ```bash lspci -nn | grep -i nvme # 查看 PCI 设备 dmesg | grep -i nvme # 检查内核日志 ``` 2. 验证模块加载: ```bash lsmod | grep nvme # 查看已加载模块 ``` > **关键提示**:嵌入式系统需确保内核配置与硬件平台匹配。若使用 Buildroot/Yocto 定制系统,需在设备树中显式启用 PCIe 和 NVMe 支持[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值