Мега-руководство по Non-Volatile Memory Express или NVMe

Мега-руководство по Non-Volatile Memory Express или NVMe 24 Сентября 2021

NVM Express или NVMe (от англ. Non-Volatile Memory Express) — это спецификация протокола обмена данными через линии PCI Express. Данная технология создана для твердотельных накопителей и ориентирована на достижение максимальной масштабируемости и производительности в последующем. В данной статье мы подробно расскажем про протокол и приведем примеры как использовать NVMe в Linux.

История

Если у вас был компьютер в 90-тых, вы помните что жесткий диск был подключен к материнской плате через широкий кабель (обычно это 40 или 80 контактный разъём) Parallel АТА (он же РАТА). Этот стандарт был основан на стандартах IDE (Integrated Drive Electronics) и АТА (АТ приложение, AT сокращение от Advanced Technology из поколения IBM PC / AT). Через несколько лет для поддержки устройств таких как CD-ROM на том же интерфейсе, был разработан стандарт ATAPI (ATA Packet Interface) вместе с ATA. Стандарт ATA развивался, включая в себя Enhanced IDE (EIDE), Ultra DMA (UDMA) и Ultra ATA в последующие годы.

После 2000 года, был разработан интерфейс Serial ATA (SATA), который и заменил PATA. SATA использует меньший кабель (только 7-ми контактный разъём) и обеспечивает более высокую скорость передачи данных. Так же был разработан режим AHCI для подключения накопителей SATA.

Форм-фактор vs Интерфейс vs Протокол

Давайте разберемся что есть что, перед тем как читать статью далее.

Форм-фактор означает форму и размер устройства. Общие характеристики форм-фактора для устройств хранения:

  • Диск 2,5 или 3,5 дюйма (определен в стандартах SFF)
  • M.2 и PCI Express (определены стандартами PCI-SIG)

Интерфейс означает, как устройство взаимодействует с компьютером. Общие особенности интерфейсов для устройств хранения:

  • Интерфейс SATA, использует жесткие диски 2,5 и 3,5 дюйма, а также подключенные устройства M.2.
  • Интерфейс PCI Express, используется подключенными устройствами M.2 и устройствами PCIe.
  • Интерфейсы SAS (Serial Attached SCSI) и FC (Fibre Channel), используются исключительно на серверах и дата центрах.

PCI намного быстрее чем SATA. Максимальная скорость SATA III - 6 ГБ/с в то время как M.2 коннектор использует 4 PCIe v3 линии, имеющие максимальную скорость, почти 4Гб/с = 32 Гб/с.

Протокол определяет, как управлять и обмениваться данными с устройством. Общие характиристики протокола:

  • AHCI и ATA для интерфейса SATA. AHCI - это протокол для поддержки дополнительных функций SATA на контроллере.
  • NVMe для интерфейса PCI Express.

Для лучшего понимания нам нужно сделать явное различие между контроллером и запоминающим устройством. На самом деле данные хранятся на устройстве хранения, однако программное обеспечение не взаимодействует с устройством хранения напрямую. Оно взаимодействует с контроллером. Таким образом, в случае SATA устройство хранения может использовать команды ATA, но контроллер используется с AHCI. В случае PCIe, NVMe определяет и то, и другое.

Возможные и распространенные комбинации:

  • Жесткие диски 2.5 или 3.5 подключенные к SATA порту, использование SATA интерфейса и обращение к AHCI/ATA. Это традиционные вращающиеся / магнитные жесткие диски.
  • 2,5-дюймовый SSD (твердотельный накопитель), подключенный к порту SATA с использованием AHCI / ATA, как жесткий диск.
  • M.2 SSD, подключенный к порту M.2, использует интерфейс SATA и взаимодействует с AHCI / ATA.
  • M.2 SSD, подключенный к порту M.2 через интерфейс PCIe и взаимодействующий с NVMe.
  • Устройство PCIe SSD, подключенный к слоту PCIe использует интерфейс PCIe и взаимодействует с NVMe.

NVM Express или откуда он вообще взялся

Интерфейс NVMe разработан, потому что стандарт AHCI и ATA / ATAPI предназначался для традиционных вращающихся / магнитных жестких дисков, которые по своей природе медленные из-за своей физической конструкции по сравнению с устройствами с энергонезависимой памятью (NVM) и с SSD (твердотельный накопитель).

Основное преимущество NVMe перед AHCI / ATA заключается в том, что у NVMe гораздо больше очередей команд (макс. 64 тыс команд) по сравнению с одной в AHCI. А это значит, что возможен параллельный / одновременный ввод-вывод. Также каждая очередь команд может быть очень глубокой (макс. 64 тыс команд) по сравнению с AHCI (только 32).

Таким образом, с несколькими ядрами CPU, когда у вас есть устройство AHCI / ATA, каждое ядро отправляет команды ATA (например, чтение / запись) в одну очередь, и при отправке команд требуется синхронизация и блокировка. С NVMe каждое ядро может иметь свою собственную очередь, и нет необходимости в синхронизации при отправке команд.

На самом деле в NVMe есть два типа очередей: одна для отправки, а другая для завершения. Очереди отправки также могут совместно использовать одну и ту же очередь завершения, поэтому нет необходимости в соответствии 1:1. Очереди находятся в памяти, и каждая запись очереди отправки, команда, обычно имеет размер 64 байта. Завершенная команда идентифицируется по идентификатору очереди отправки и идентификатору команды при отправке.

Два типа команд в NVMe:

  • Команды администратора отправляются в очередь отправки и завершения администратора (из этой пары есть только одна с идентификатором = 0).
  • Команды ввода-вывода (называемые командами NVM) отправляются в очереди ввода-вывода и завершения. Очереди команд ввода-вывода должны управляться явно (создаваться / удаляться и т. д.).

* Резюмируем: команда Администратора или NVMe отправляется в очередь отправки как новая запись. NVMe контроллер читает команду оттуда, выполняет операцию и помещает запись в очередь завершения, таким образом хост/запросчик программного обеспечения понимает завершенную команду (или нет). Если данные читаются, они передаются напрямую в буфер данных, а не в очередь завершения. Очереди большие, но ограниченные по размеру и сформированы как круговые буферы.

NVМe имеет расширенные/корпоративные функции наподобие Multi-Path I/O и совместное использование Namespace. Поскольку они обычно не используются в компьютерах, мы не будем рассказывать о них.

Из спецификации NVMe:

Namespace это «Количество энергонезависимой памяти, которое может быть отформатировано в логические блоки. При форматировании пространство имен размера n представляет собой набор логических блоков с адресами логических блоков от 0 до (n-1) ».

Подытожим теорию

Поскольку SSD это отличная от жестких дисков технология, необходим новый стандарт - и это NVMe.

Для управления NVMe, команды администратора отправляются в административную очередь отправки Аdmin Submission Queue (ASQ) и результат собирается из ASQ.

Одно и более Namespaces создается и форматируется перед использованием SSD. Это выполняется операционной системой, которую вы используете. Создается минимум одна пара ввода/вывода Command Submission и Completion Queue. Опять же это выполняется операционной системой. Для выполнения ввода/вывода операций, таких как чтение и запись на SSD, операционная система отправляет команды ввода/вывода NVM в очередь (очереди) ввода/вывода и собирает результаты из очередей завершения ввода-вывода.


Теперь рассмотрим некоторые детали в примерах.

NVMе в примерах

Для этой статьи мы используем Samsung M.2 с NVMe SSD (MZVPV512HDGL - SSD SM951 M.2 512 GB PCIe 3.0) и инструменты nvme-cli. Nvme-cli предоставляется дистрибьюторами Linux, но мы рекомендуем взять его с Github, так как там самая последняя версия.

Во-первых, давайте посмотрим есть ли у нас контроллеры NVMe на шине PCI

Есть ли устройства nvme?

Видим одно символьное устройство (nvme0) и четыре блочных устройства

Как вы догадались, nvme0n1 - это диск, другие - это разделы.

Еще один способ узнать, есть ли у нас контроллеры NVMe:

Это формируется из sysfs (например, /sys/class/nvme/)

Давайте отправим команду определение администратора (Identify Admin) на контроллер NVMe. Это возвращает 4096 байт данных, поэтому вывод будет длинным. (-H разрешает битовые поля, делает его удобным для человека)


Некоторые поля являются необязательными и обозначаются как 0. Значения некоторых полей:

  • vid - это идентификатор поставщика PCI (в данном случае Samsung), 0x144d, как в выводе lspci.
  • ssvid - это идентификатор поставщика подсистемы PCI.
  • SN - серийный номер, MN - номер модели, FR - версия прошивки.
  • cmic - это возможности многопутевого ввода-вывода контроллера и совместного использования пространства имен.

  • mdts - это максимальный размер передаваемых данных. Он указывается как степень двойки и в единицах минимального размера страницы памяти. Здесь mdts = 5, поэтому 2⁵ = 32. Мы увидим минимальный размер страницы памяти (MPSMIN) как 4К ниже. Таким образом, MDTS составляет 32 * 4K = 128K.
  • oacs - это необязательная поддержка административных команд.

  • frmw - это обновления прошивки.

  • tnvmcap - это общая емкость NVM, и это необязательное поле. Поскольку он отображается как ноль, он не поддерживается.
  • sqes - это (максимальный и обязательный) размер записи очереди передачи (SQE). Ожидается, что он будет 64, но с помощью проприетарных расширений его можно изменить. Здесь значение 0x66.

  • cqes - это (максимальный и обязательный) размер записи очереди завершения (CQE). Подобно вычислению размера SQE, здесь его значение равно 0x44, поэтому максимальный и требуемый размер записи очереди завершения составляет 2⁴ = 16 байт.
  • nn - (максимальное) количество пространств имен, поддерживаемых контроллером. Хотя теоретический максимум высок, здесь он равен 1, поэтому возможно только одно пространство имен.
  • oncs - это дополнительная поддержка команд NVM.

Теперь давайте посмотрим если у нас есть какие нибудь namespace отправив команду Identify List Namespaces:

Есть одно пространство имен с идентификатором 0x1.

Рассмотрим детальнее это пространство имен:

Некоторые поля являются необязательными, как и в разделе «Идентифицировать вывод контроллера».

Значение некоторых команд:

  • nsze, Размер пространства имен (NSZE). Указывает количество логических блоков, 0x3b9e12b0 = 1000215216. Мы можем проверить это, запустив lsblk -b и увидев размер диска как 512110190592, а размер блока равен 512Б, так что это равно NSZE * 512.
  • ncap, Емкость пространства имен (NCAP). Максимальное количество логических блоков, выделенных пространству имен. Если есть тонкое предоставление (если дисковое пространство выделяется по запросу), это значение может быть меньше NSZE. Здесь это не так, поэтому его значение такое же, как NSZE.
  • nuse, использование пространства имен (NUSE). Текущее количество логических блоков, выделенных пространству имен. Если есть тонкое предоставление (если дисковое пространство выделяется по запросу), это значение может быть меньше NSZE. Здесь это не так, поэтому его значение такое же, как NSZE.
  • nsfeat, Возможности пространства имен (NSFEAT).

  • nlbaf, Количество форматов LBA (NLBAF). Указывает количество поддерживаемых комбинаций размера данных LBA и размера метаданных, поддерживаемых пространством имен. Это значение отсчитывается от нуля, и его значение здесь равно 0, поэтому поддерживается только 1 формат.
  • flbas, форматированный размер LBA (FLBAS). Указывает комбинацию размера данных LBA и размера метаданных, с которой было отформатировано это пространство имен. Первые 3 бита = 0 - это индекс, поэтому он отформатирован в формате 0, а бит 4 = 0, что указывает на то, что метаданные для команды передаются отдельно.
  • nguid, глобальный уникальный идентификатор пространства имен (NGUID) и eui64, расширенный уникальный идентификатор IEEE (EUI64) назначаются, когда пространство имен создается и сохраняется во всех операциях пространства имен и контроллера (например, сброс, форматирование).
  • lbaf 0 - это формат LBA 0.

Заполнение LBA формата

  • ms, размер метаданных (Metadata). Поскольку его значение равно 0, метаданные не поддерживаются.
  • ds, размер данных LBA (LBADS). Он выражается в степени двойки, поэтому размер данных LBA составляет 2⁹ = 512 байт.
  • rp, Относительное исполнение (Relevant Performans). Указывает относительную производительность этого формата по сравнению с другими форматами. Значение 0 указывает, что это лучшая производительность. Здесь нет особого смысла, потому что у нас поддерживается только один формат LBA.

Теперь давайте посмотрим, что на самом деле означает отправка этих команд. В настоящее время NVMe определяет набор команд администратора и NVM. Выше мы использовали утилиты nvme в качестве помощника для отправки команд. Также можно отправить команду вручную.

Команды администратора NVMe

Команды администратора NVMe с кодами операций:

  • Create and Delete I/O Submission Queue - Создание и удаление очереди отправки ввода-вывода (01ч, 00ч)
  • Get Log Page - Получить страницу журнала (02ч)
  • Create and Delete I/O Completion Queue - Создание и удаление очереди завершения ввода-вывода (05ч, 04ч)
  • Identify - Команда идентификации (контроллера или пространства имен) (06ч)
  • Abort - Прервать (08ч)
  • Get and Set Features - Получить и установить функции (0Ah, 09ч)
  • Asynchronous Event Request - Асинхронный запрос события (0Ch)
  • Namespace Management - Управление пространством имен (0Dh)
  • Firmware Commit and Image Download - Фиксация прошивки и загрузка образа (10ч, 11ч)
  • Device Self-test - Команда самопроверки устройства (14ч)
  • Namespace Attachment - Присоединение пространства имен (15ч)
  • Keep Alive - Поддерживать соединение (18ч)
  • Directive Send and Receive - Указание отправки и получения (19ч, 1Ah)
  • Virtualization Management - Управление виртуализацией (1ч)
  • NVMe-MI Send and Receive - Отправка и получение NVMe-MI (1Dh, 1Eh)
  • Doorbell Buffer Config - Получить доступ к дорбелл буферу (7Ch)
  • Format NVM - Форматирование пространства имен (80ч)
  • Security Send and Receive - поддержка команд протокола безопасности (81ч, 82ч)
  • Sanitize - Безопасная очистка данных (84ч)

Как мы видели из вывода id-ctrl, размер записи очереди отправки (SQE) составляет 64 байта. Что в этих 64-байтах?

Составляющие:

  • Команда Dword 0 (CDW0), 4 байта: включает идентификатор команды (2 байта) и код операции (1 байт)
  • Идентификатор пространства имен (NSID), 4 байта. Если он не используется, его следует сбросить до нуля. Если он установлен в 0xFFFFFFFF, это заставляет команду примениться ко всем пространствам имен.
  • Следующие 8 байтов зарезервированы.
  • Указатель метаданных (MPTR), 8 байт.
  • Указатель данных (DPTR), 16 байт.
  • Команда Dword 10 (CDW10), 4 байта
  • Команда Dword 11 (CDW11), 4 байта
  • Команда Dword 12 (CDW12), 4 байта
  • Команда Dword 13 (CDW13), 4 байта
  • Команда Dword 14 (CDW14), 4 байта
  • Команда Dword 15 (CDW15), 4 байта

Теперь давайте напрямую отправим команду Идентифицировать (Identify). Для этого мы используем утилиту nvme admin-passthru.

Команда Identify имеет код операции 06h (— opcode=0x06). Он выводит 4096 байт, поэтому мы устанавливаем длину данных как таковую (- data-len = 4096) и указываем, что команда является командой чтения (-r). Мы не хотим отправлять команду сейчас, поэтому используем пробный запуск (-d), и хотим, чтобы команда была отправлена первой (-s).


Результатом является сводка командных полей. Указатель данных (DPTR) (адрес в выходных данных) устанавливается командой при выделении буферов данных.

NVM команды

Вот команды NVM с кодами операций:

  • Flush - Очистка кэша (00ч)
  • Write and Read - Запись и чтение (01ч, 02ч)
  • Write Uncorrectable - Сообщает о неисправимой ошибке (04ч)
  • Compare - Сравнить (05ч)
  • Write Zeroes - Команда используется для установки диапазона логических блоков в 0. (08ч)
  • Dataset Management - Команда используется хостом для указания атрибутов диапазонов логических блоков. (09ч)
  • Reservation Register and Report - Реестр резервирования, Отчет о резервировании (0Dh, 0Eh)
  • Reservation Acquire and Release - Получение резервирования и Релиз резервирования (11ч, 15ч)

Команды NVM должны отправляться только в том случае, если контроллер готов (на это указывает регистр состояния контроллера CSTS.RDY) и после того, как была создана очередь отправки и завершения ввода-вывода.

Посмотрим регистры:

Значения некоторых из них:

  • Размер страницы памяти максимум CAP.MPSMAX = 128К и минимум CAP.MPSMIN = 4К
  • Timeout CAP.TO = 30000 мс, наихудшее время ожидания, пока CSTS.RDY меняет состояние с 0 на 1 и 1 на 0.
  • Максимальное количество поддерживаемых записей в очереди (CAP.MQES) = 16K. В каждой очереди отправки ввода/вывода может быть максимум 16К записей.
  • Version (VS) = 1.1, поддерживает контроллер версии спецификации NVMe. Последняя версия это 1.3 от 22.08.2017.
  • Размер страницы памяти (Memory Page Size) (CC.MPS) = 4K.
  • Выбранный набор команд ввода/вывода (CC.CSS) = набору команд NVM.
  • Ready (CSTS.RDY) = 1, контроллер готов принять отправку команд.
  • Размер очереди отправки и завершения администратором (ASQS и ACQS) = 256.

Есть обоснованная причина, почему существуют отдельные промежуточные утилиты. Для административных команд существует отдельная пара очереди отправки и завершения, их нельзя отправить в обычные очереди.

В последнем примере запустим команду чтения (Read) как с помощью помощника , так и в вручную. Мы прочитаем LBA 0 и сравним его с тем, что у нас есть с dd.

Во-первых, сделаем сброс LBA 0 в файл lba.0 с dd:

Во-вторых, прочитаем LBA 0 через утилиту nvme:

В-третьих, прочитаем LBA 0 отправив команду вручную через nvme io passthru:

Read - это код операции 0x02, мы отправляем команду в namespace 0x1, читаем 512 байт, кодовое слово 10 и 11 (cdw10 и cdw11) указывает начало чтения в блоках LBA, поэтому с 0 (оба равны 0), а биты 15:00 кодового слова 12 (cdw12) - это количество блоков для чтения (установлено в 0, чтобы указать, что будет прочитан 1 блок).

Если сравнить, это выглядит так:  

Перевод с английского статьи от Mete Balci.