Имея базовые знания и базовый фреймворк, встроенный в кодовую базу, мы можем начать получать удовольствие! Поскольку моя мотивация для написания этих статей - обмен знаниями, я хотел бы сосредоточиться на пути, а не на конечной цели. Мы собираемся сразу приступить к делу и посмотреть, как наивно создать простую прошивку SSD, которая позволяет хосту записывать и читать данные. Мы будем обсуждать и решать проблемы по ходу дела, а не пытаться избежать их заранее.

Адресные пространства

Напомним из Части 3, что у хоста есть адресное пространство, называемое пространством LBA, где каждый LBA - это некоторое фиксированное количество байтов, а LBA находится в диапазоне от 0 до максимального количества секторов для данного SSD (обратите внимание, что я буду использовать LBA. и сектор взаимозаменяемы). SSD определяет поддерживаемый диапазон LBA, и хост обычно выполняет запрос для получения этой информации. В рамках проекта HostComm мы реализовали CustomProtocol, который поддерживает команды настраиваемого протокола. На данный момент это позволяет нам меньше отвлекаться от изучения стандартизованных протоколов, таких как SATA, SCSI или NVMe. Мы можем использовать наш собственный протокол, чтобы определить команду, которая позволяет хосту получать количество секторов, которые будет поддерживать наш SSD. Назовем эту команду GetDeviceInfo.

Прошивка нашего SSD-устройства имеет доступ к NAND для хранения данных. NAND имеет собственное адресное пространство, а общее адресуемое пространство NAND представляет собой комбинацию количества устройств NAND, которые мы физически имеем в нашем SSD, и того, сколько физического хранилища доступно для каждого устройства NAND (т. Е. Количество блоков на устройство, количество страниц на блок и количество байтов на страницу). В нашем SSDSim мы можем определить это, используя NandSpec в hardwarespec.json. Это будет аналогично физическому оборудованию, построенному для нашего целевого устройства. А поскольку мы играем наивно, давайте предоставим хосту все адресуемое физическое пространство для хранения. Нам действительно нужно выполнить небольшое преобразование пространства хранения NAND в количество LBA, вычислив общее количество адресуемых страниц NAND и умножив его на количество LBA на странице. Например, мы можем определить количество байтов на LBA равным 512, а количество байтов на страницу - 8 КБ. Это дает нам 16 LBA на страницу. Если наш SSD может адресовать 256 КБ страниц, наш диапазон адресов LBA будет [0, 4194303] (4 МБ памяти).

Перевод

Что касается самого основного требования, ожидается, что микропрограмма SSD будет выполнять команды записи и чтения, запрошенные хостом, где хост предоставляет как минимум адрес (LBA) и счетчик (количество секторов). Для команд записи хост также предоставит данные для записи. SSD должен хранить данные из команд записи, запрошенных хостом, и гарантировать целостность данных, когда хост желает прочитать данные, которые были записаны ранее. Интуитивно понятно, что микропрограммное обеспечение должно иметь возможность преобразовывать адрес LBA в адрес NAND для команд записи и чтения. В индустрии твердотельных накопителей часть микропрограммы, которая выполняет это, называется F lash T ranslation L ayer. FTL может хранить данные хоста любым способом, если гарантируется целостность данных. Действительно, существует множество различных способов, которыми FTL может выполнять свою задачу, и каждый имеет свои плюсы и минусы и по-разному влияет на производительность, долговечность и общее качество SSD.

Поскольку мы играем наивно, мы реализуем простой FTL и просто выполним перевод простым методом по фиксированным формулам. Мы немного знаем о чередовании операций NAND, поэтому мы также включим эту информацию в то, как мы будем выполнять перевод. Это может показаться немного уродливым, чтобы проиллюстрировать это, но давайте немного упростим следующую конфигурацию и посмотрим на рисунок 1.

  • LBA на страницу: 2
  • Страниц в блоке: 2
  • Количество каналов: 2
  • Количество устройств на канал: 2
  • Количество блоков на устройство: 2

Что мы хотим здесь сделать, так это заполнить данную страницу последовательными LBA, затем перейти к следующему каналу, перейти по каналу к следующему устройству, а затем перейти к следующей странице в блоке. Это довольно просто и дает нам необходимый параллелизм за счет операций чередования.

Для этого мы сначала вычисляем pageIndex с учетом lba. Легкий способ представить себе pageIndex - представить, что все страницы NAND расположены линейно (без учета фактической адресации NAND на данный момент). Итак, для примера у нас будет всего

(2 страницы / блок) * (2 блока / устройство) * (2 устройства / канал) * (2 канала) = 8 страниц

Если мы разместим их линейно, мы можем пометить их как страницы с 0 по 7. Наше устройство будет иметь в общей сложности 16 LBA (8 страниц * 2 фунта / страница). Затем, чтобы вычислить pageIndex, мы можем просто выполнить целочисленное деление.

pageIndex = lba / LbasPerPage

Учитывая, что размер страницы больше, чем размер LBA, обычно кратный степени 2, мы можем вычислить LBA или смещение сектора на странице.

sectorInPage = lba % sectorsPerPage

Теперь мы можем использовать pageIndex для преобразования в адрес NAND и заполнения канала, устройства, блока и (физической) страницы на основе желаемого порядка чередования.

channel = pageIndex % channelCount
device = (pageIndex / channelCount) % devicesPerChannel
page = ((pageIndex / channelCount) / devicesPerChannel) % pagesPerBlock
block = ((pageIndex / channelCount) / devicesPerChannel) / pagesPerBlock

Если мы можем быть уверены, что «делители» всегда равны степени двойки, мы могли бы использовать сдвиг битов вместо дорогостоящих операций деления. Кроме того, вы также можете просматривать группы битов в pageIndex как встроенный физический адрес NAND. Например, давайте определим большую конфигурацию NAND

channelCount: 4
devicesPerChannel: 2
blocksPerDevice: 64
pagesPerBlock: 256

Если pageIndex равен 206, мы можем увидеть двоичное представление как (отображается как 16-битное значение, где слева - самый старший бит):

0000000011001110

Напомним, что нам нужен порядок чередования: канал, устройство, страница, затем блок, если мы должны сгруппировать битовое поле pageIndex, тогда канал - это наименее значимая группа, затем устройство, затем страница, затем заблокируйте.

0000000 011001 1 10

Поскольку имеется 4 канала, первые 2 младших бита являются значением адреса канала. Бит 2 - это значение адреса устройства, бит [3, 8] - это значение адреса страницы, а бит [9, 15] - это значение адреса блока. Давайте еще раз проверим.

channel = pageIndex % channelCount = 206 % 4 = 2 = 10b
device = (pageIndex / channelCount) % devicesPerChannel = (206/4) % 2 = 1 = 1b
page = ((pageIndex / channelCount) / devicesPerChannel) % pagesPerBlock = ((206/4)/2)%64 = 25 = 011001b
block = ((pageIndex / channelCount) / devicesPerChannel) / pagesPerBlock = ((206/4)/2)/256 = 0 = 00000000b

Это хорошо, если нам нужно обработать команду, которая включает в себя несколько секторов или LBA, которые охватывают несколько страниц, мы можем просто вычислить pageIndex один раз из начального LBA и «пройти», увеличивая pageIndex для последующих страниц. Например, если мы увеличим pageIndex до 207, он естественным образом перейдет на канал 3, а все остальное останется прежним. Снова увеличьте значение до 208, channel сбрасывается до 0, device сбрасывается до 0, а page увеличивается до 26.

Поскольку я хотел бы, чтобы каждая статья была короткой, мы можем обсудить, как применить эту информацию к коду на более позднем этапе.