Пришло время написать файловую систему. Файловая система сама себя не напишет. В этой половинке лабы мы таки реализуем файловую систему FAT32, прикрутим к ней драйвер SD-карты и чуть-чуть повзаимодействуем с ней через нашу интерактивную оболочку.
Первая лаба: младшая половина и старшая половина
Младшая часть. Продолжение под катом.
Фаза 2: 32-битные липиды
В этой фазе мы будем реализовывать файловую систему FAT32. Исключительно read-only на данный момент. Основная работа будет вестисть в каталоге 2-fs/fat32
.
Диски и Файловые системы
Данными на диске управляют одна или несколько файловых систем. Аналогично распределителям памяти, файловые системы отвечают за управление, выделение и освобождение памяти. С той лишь разницей, что это не быстрая оперативная память, а медленная и энергонезависимая память. Другими словами все изменения сохраняются на любой момент в будущем. В том числе и после перезагрузки компьютера. Есть много различных файловых систем. На Linux есть EXT4. На macOS есть HFS+ и APFS. На Windows есть NTFS. Некоторые файловые системы реализованны сразу для нескольких ОС. FAT32 — одна из таких. Она реализована для всех основных ОС включая Linux, macOS и Windows. Изначальна она использовалась в поздних версиях DOS и ранних версиях Windows. Главное приемущество FAT32 — вездесущность. Это одна из самых коросс-платформенных файловых систем.
Для того, чтоб позволить более чем одной файловой системе находиться на диске, этот самый диск можно поделить на разделы. Каждый раздел можно независимо отформатировать для разных файловых систем. Чтоб разбить диск на разделы, на диске в определённое место записывается, где какой раздел начинается, где он закачивается и тип файловой системы, что этот раздел использует. Одной из широко распространённых систем является Master Boot Record (главная загрузочная запись) или просто MBR во имя краткости. MBR содержит в себе таблицу из четырёх записей, описывающих разделы. При этом некоторые разделы можно не объявлять как используемые. Есть чуть более современные схемы разделения вроде GPT, который помимо прочего поддерживает более четырёх разделов.
В этом задании мы будем реализовывать код чтения MBR с диска, который в свою очередь включает один раздел FAT32. Эту комбинацию использует наша малинка: тоже MBR и тоже FAT32.
Разбиение диска
На вот этой диаграмме показана физическая компоновка дискового раздела с MBR и FAT32:
В PDF-ке структуры FAT содержится вся необходимая информация о размерах и содержимом этих самых структур. Вместе с минимально необходимым описанием. Мы будем использовать документ при реализации нашей файловой системы. Помимо этого полезно будет изучить соответсвующую статью из википедии.
Master Boot Record
MBR всегда находится в нулевом секторе диска. MBR содержит четыре записи разделов. Каждая из этих записей содержит в себе: тип раздела, смещение раздела в секторах и разные флаги вроде того, является ли этот раздел загрузочным. Все остальные поля вроде CHS (цилиндр, головка, сектор) можно целиком и полностю игнорировать. Так поступает большинство современных реализаций. Стоит ещё заметить, что тип раздела для FAT32 равен 0xB
или 0xC
.
Extended Bios Parameter Block
Первый сектор раздела FAT32 содержит расширенный блок параметров BIOS. Сокращённо EBPB. Сам этот блок начинается с блока параметров BIOS или BPB. Вместе они определяют все необходимые параметры компоновки файловой системы FAT.
Есть одна область в EBPB, на которую стоит обратить отдельное внимание. Та которая определяет количество зарезервированных секторов (number of reserved sectors). Это смещение от начала раздела FAT32 в секторах, где FAT могут быть найдены. Сразу после последнего FAT будет область, содержащая данные для кластеров. Сейчас мы подробнее рассотрим FAT-ы, область данных, кластеры и вот это всё.
Кластеры
Все данные, которые хранятся в файловой системе FAT, разделяются на кластеры. В EBPB есть поле, из которого можно найти, сколько в каждом кластере секторов (number of sectors per cluster). Нумерация кластеров начинается с цифры 2. Как видно из диаграммы, данные для кластера 2 расположены в начале области данных. Данные для кластера 3 расположены сразу после кластера 2 и далее в таком духе.
File Allocation Table
FAT расшифровывается как file allocation table. Таблица распределения файлов. Исходя из названия FAT это таблица (массив) записей FAT. В FAT32 каждая такая записть имеет размер в 32 бита. Размер же всей этой таблицы определяется полями sectors per FAT и bytes per sectors из EPBP. Для избыточности в файловой системе может быть более одного FAT (во имя пресвятого бекапа!). Количество таблиц так же можно найти в EPBP. Смотреть поле number of FATs.
Помимо записей за номерами 0 и 1 каждая из FAT-записей определяет статус кластера. Записть за номером 2 определяет статус кластера 2. Запить 3 определяет статус кластера 3. И далее по списку. Каждому кластеру свою FAT-запись.
Записи 0 и 1 скорее всего такие:
- Запись 0:
0xFFFFFFFN
, который ID. - Запись 1: Маркер конца цепочки кластеров (EOC).
Помимо этих двух записей все остальные соотвесвуют определённому кластеру из области данных. Хотя FAT-записи имеют полный размер в 32 бита, используются только 28 бит. Верхние 4 бита игнорируются. И значения могут быть такие:
0x?0000000
: Пустой неиспользуемый кластер.0x?0000001
: Зарезервировано.0x?0000002
-0x?FFFFFEF
: Кластер данных. Конкретное значение — следующий кластер в цепочке.0x?FFFFFF0
-0x?FFFFFF6
: Зарезервировано.0x?FFFFFF7
: Зарезервированный или испорченый кластер.0x?FFFFFF8
-0x?FFFFFFF
: Последний кластер в цепочке. Должен быть маркером EOC.
Цепочка кластеров
Кластеры образуют цепочки кластеров. По сути это связанный список из кластеров. Если кластер используется для данных, то его значение содержит либо ссылку на следующий кластер, либо является маркером EOC, указывающем конец цепочки.
В качестве примера рассмотрим диаграммку с 8-ю FAT-записями:
Кластеры раскрашены по цветам так, чтоб можно было проще разобраться, что к какой цепочке принадлежит. Первые две записи это ID и EOC. Запись 2 указывает, что соответсвующий кластер является кластером данных и эта цепочка (зелёная) размером в один кластер. Запись 3 указывает, что кластер 3 содержит данные и следующим в цепочке (синей) будет кластер 5 с данными, который ссылается на кластер 6, который эту цепочку обрывает. Аналогичным образом кластеры 7 и 5 образуют цепочку (красная). Кластер за номером 8 свободен и не используется.
Каталоги и записи
Цепочка кластеров — это данные для файла или каталога. Каталог — суть специальный файлик, в котором содержатся имена файлов и все прочие метаданные. Внутри каталок представляет собой массив каталожных записей. Каждая такая запись содержит имя, стартовый кластер и является ли эта запись каталогом или просто файлом.
Есть один специальный каталог, который не связан с записями в других каталогах. Корневой каталог. Стартовый кластер для корнегого каталога можно найти в EBPB. Через это всё можно определить место всех других файлов и каталогов.
По историческим причинам каждая запись физического каталога можно интерпретировать аж двумя разными способами. Поле атрибутов так же указывает на один из этих способов. Вот эти две вариации:
- Обычная каталожная запись. (regular directory entry)
- Запись для длинного имени файла. (long file name entry)
Длинное имя файла (LFN) добавлено в FAT32 для того, чтоб использовать имена файлов длиннее 11 символов. Если запись имеет имя длинной более 11 символов, то ей предшествуют записи LFN. При этом эти записи не сортированы физически. Вместо этого они содержат поле для того, чтоб определить последовательность. Таким образом на физический порядок записей LFN полагаться не получится.
Итак
Прежде чем продолжить надо разобраться со структурами FAT. После этого постарайтесь ответить на следующие вопросы:
Каким образом определить, содержит ли первый сектор MBR-структуру? [mbr-magic]
Первый сектор диска может не содержать MBR. Каким образом можно определить есть ли там MBR или его там нет?
Каково максимальное количество кластеров FAT32? [max-clusters]
Дизайн FAT32 подразумевает некоторое количество ограничений. Какое максимальное количество кластеров в FAT32 и откуда эти ограничения происходят? А если взять FAT16, то там будут те же самые ограничения или другие?
Какой максимальный размер одного файла? [max-file-size]
Есть ли какие либо ограничения на максимальный размер файла? Если есть, то каков максимальный размер файла и что определяет эту границу?
Подсказка: Посмотрите на структуру записи в каталоге.
Как определить, перед нами запись LFN или другая? [lfn-identity]
Если внимательно посмотреть на записи в каталоге, то какие именно байтики определяют, LFN перед нами или обычная запись? Конкретно, какие это байты и какие у них должны быть значения?
Каким образом можно найти
/a/b/c.txt
[manual-lookup]Не забывая про EBPB, опишите все шаги, которые вы предпримите для того, чтоб найти начальный кластер для файла
/a/b/c.txt
.
Структура кода
Написание файловой системы является достаточно сурьёзным делом. FAT32 даже при том, что мы её будем только читать, не исключение. Предоставленный код в крейте 2-fs/fat32
обеспечивает в основном базовую структуру, но многие дизайнерские решения и большая часть реализации целиком принадлежит вам.
Сейчас займёмся описанием того, что уже готово. Почитайте код из каталога fat32/src
.
Трейты файловой системы
Там можно найти модуль traits
. Точкой входа будет traits/mod.rs
. Там можно найти примерно семь трейтов и одну структурку. При реализации файловой системы мы в том числе будем реализовывать это всё.
Там есть одна структура Dummy
, которая обеспечивает фиктивную реализацию большинства трейтов. Этот тип можно использовать как заглушку. Если присмотреться к коду, то эта заглушка используется в некоторых местах. Может и вам пригодится.
Советую читать код из traits/
в следующем порядке:
BlockDevice
изtraits/block_device.rs
. Файловая система будет отвязана генериками от физического/виртуального хранилища. Другими словами файловая система будет работать на любом устройстве, пока это самое устройство реализуетBlockDevice
. В процессе реализации/тестирования можно использовать реализациюBlockDevice
поверх обычного файла. Во имя удобства конечно же! А вот на малинке мы вBlockDevice
завернём драйвер SD-карты вместе с контроллером EMMC и этим всем. Разницы при этом почти не заметим.File
,Dir
иEntry
изtraits/fs.rs
. Эти трейты определяют, какими минимальными свойствами должны обладать файл, каталог или их обобщение в файловой системе. Обратите внимание на зависимость их друг от друга. Например свойстваEntry
используют ассоциированный с ним типFile
.FileSystem
изtraits/fs.rs
. Данный трейт определяет свойства файловой системы. В том числе и через привязку к остальным трейтам. Например требует тип, реализующийFile
для этой файловой системы. Таким образом гарантируется, что для каждой реализацииFileSystem
есть только одна реализацияFile
,Dir
иEntry
.Metadata
иTimestamp
изtraits/metadata.rs
. КаждаяEntry
должна быть связана с некоторыми метаданными, которые позволяют получать сведения о файле или каталоге. За эти метаданные отвечаетMetadata
. АTimestamp
в свою очередь определяет набор свойств для определённых моментов во времени. Этот трейт используется для таких штук, как время создания файла.
Кеш-устройство
Доступ к диску напрямую — это достаточно дорогая операция. По этому весь доступ будет выполняться на кешированных секторах. Структуру CachedDevice
можно найти в файле vfat/cache.rs
. Оная обеспечивает прозрачный и явный доступ к кешам сектора. По сути это обёртка над BlockDevice
, которая внутри себя использует HashMap
в качестве хранилища. Ключём в HashMap
будет номер сектора. Как только вы реализуете CachedDevice
, его можно прозрачно использовать как кешированную версию BlockDevice
. В дополнение предоставляются методы get()
и get_mut()
, которые позволяют напрямую ссылаться на кешированные сектора.
Помимо этого структура CachedDevice
обязана следить за соответствием между логическими секторами и физическими секторами, которые определяются EBPB
. Для этого предоставлен метод virtual_to_physical()
. Этот самый метод следует использовать для того, чтоб определить сколько физических секторов потребуется прочитать для данного логического сектора.
Полезности
Файл util.rs
содержит один полезный трейт и его реализацию для срезов (&[T]
) и динамических массивов (Vec<T>
). Это можно использовать для переноса одного в другое при сохранении определённых условиях. Например для того, чтоб скастовать &[u32]
в &[u8]
можно использовать вот такое:
use util::SliceExt;
let x: &[u32] = &[1, 2, 3, 4];
assert_eq!(x.len(), 4);
let y: &[u8] = unsafe { x.cast() };
assert_eq!(y.len(), 16);
MBR и EBPB
Структуру MasterBootRecord
можно найти в файле mbr.rs
. Она отвечает за чтение и анализ MBR из BlockDevice
. Аналогично можно использовать структуру BiosParameterBlock
. Её можно найти в файле vfat/ebpb.rs
. Она отвечает за чтение и анализ BPB и EBPB раздела FAT32.
Shared
Структуру Shared<T>
из vfat/shared.rs
можно использовать для безопасного мутабельного доступа типу T
. Пригодится при реализации файловой системы. Особенно когда нам потребуется возможность совместного доступа к ФС из разных частей кода. Прежде чем продолжить, убедитесь, что понимаете, как и зачем пригодится использование Shared<T>
.
Файловая система
Само ядро файловой системы можно найти в файле vfat/vfat.rs
. Очевидно это структура VFat
. Как можно заметить, структура содержит в себе CachedDevice
. Реализация должна обернуть предоставленный BlockDevice
в CachedDevice
.
Что за VFAT?
VFAT — это ещё одна файловая система от Microsoft, которая является предшественником FAT32. По разным историческим причинам это стало синонимом FAT32. Мы продолжим эту глупую традицию с не всегда корректными названиями.
Частичная реализация свойств FileSystem
для типа &Shared<VFat>
уже присутствует. Помимо этого можно заметить, что метод from()
возвращает Shared<VFat>
. Основная задача — завершить реализацию метода from()
и некоторых необходимых свойств FileSystem
для &Shared<VFat>
. Это тянет за собой реализацию остальных структур, которые реализуют необходимые кусочки свойств файловой системы.
Ещё каталоге vfat/
можно найти:
error.rs
. Содержит перечислениеError
с возможными ошибками при инициализацииFAT32
.file.rs
. Содержит заготовку структурыFile
, которая должна реализовывать трейтtraits::File
.dir.rs
. Аналогичноfile.rs
. Помимо этого содержит заготовки для структур в том виде, как они записаны на диске.entry.rs
. Содержит заготовку структурыEntry
, которая должна реализовывать трейтtraits::Entry
.metadata.rs
. Содержит структурыData
,Time
,Attributes
для работы с сырыми свойствами файлов. И недописанные структурыTimestamp
,Metadata
, которые должны реализовывать соответствующие трейты из модуляtraits
.fat.rs
. Содержит структуруFatEntry
. Эта самая структура обёртывает FAT-записи и может быть использована для лёгкого и непринуждённого считывания соответствующей FAT-записи.cluster.rs
. Содержит структуру кластера, которая обертывает физический номер кластера и может использоваться для чтения номера логического кластера.
При реализации файловой системы надо будет дополнить всё это необходимым кодом. Не бойтесь дополнять любые из этих структур методами, которые кажутся вам необходимыми. Однако не изменяйте ни одно из предоставленных определений трейтов или сигнатур у существующих методов.
Прочитайте сейчас весь код начиная с vfat.rs
и убедитесь, что вы понимаете, что там происходит.
Реализация
Теперь у нас есть всё необходимое для реализации файловой системы FAT32. Вы можете заниматься реализацией в том порядке, в котором вам больше нравиться.
Убедитесь, что обновили все предоставленные заготовки!
Убедитесь, что все ваши копии репозиториев находятся в актуальном состоянии. Стащите последние версии
2-fs
иos
с помощьюgit pull
и поправьте всё необходимое.
Мы предоставляем некоторый набор достаточно строгих тестов для проверки реализации. Перед запуском тестов запустите make clean && make fetch
в каталоге 2-fs
. Оно загрузит несколько файликов в 2-fs/files/resources/
. Эти файлы используются модульными тестами. В этом каталоге вы найдёте образы, которые содержат внутри MBR, EBPB и FAT32, а так же хеши, которые используются для проверки разных частей реализации. Возможно вы найдёте полезным проанализировать образы при помощи hex-редакторов вроде *Bless в Linux или Hex Fiend на macOS.
Тесты можно запустить при помощи cargo test
. Для того, чтоб увидеть отладочные сообщения можно выполнить cargo test -- --nocapture
. Это предотвращает перехват stdout
и stderr
. Кроме того вы можете свободно добавлять собственные тесты в том количестве, в котором сочтёте необходимым. Чтоб предотвратить конфликты слияния рекомендуется добавить тесты в файлик с именем, отличным от tests.rs
.
Рекомендуется также следовать вот этим правилам:
- Используйте осмысленные типы везде, где это возможно. Например вместо использования
u16
для поля времени можно использовать структуруTime
- Избегайте
unsafe
на столько, на сколько это возможно. Наша реализация использует в общей сложности четыре не-union
строки сunsafe
и три строки для обработкиunion
. Ваша реализация должна стараться следовать этому. - Избегайте дублирования, используя всякие вспомогательные методы. Часто полезно вынести общий вариант поведения во вспомогательный метод. Постарайтесь делать это, когда оно имеет смысл.
- Убедитесь, что ваша реализация не зависит от размера кластера или сектора. Не хардкорьте какие либо конкретные значения размеров сектора или размеров кластера. Ваша реализация должна работать с любыми размерами кластера и сектора, которые кратны 512 и взяты из EBPB.
- Не буферезируйте дважды без необходимости. Убедитесь, что не читаете сектора в память, если они уже есть в кеше. Старайтесь использовать память без фанатизма.
Вы можете делать всё в том порядке, в каком хотите. Но вот такой порядок рекомендуем:
- Реализуйте разбор MBR в
mbr.rs
. Вероятно для реализации потребуется использованиеunsafe
. Но будет достаточно одной строчки. Скорее всего slice::from_raw_parts_mut() или mem::transmute().mem::transmute()
невероятно мощный инструмент. Избегайте его использования на столько, на сколько получается. В противном случае вы должны полностью понимать, что делаете. Для реализацииDebug
используйтеdebug_struct()
изFormatter
. Можете посмотреть предоставленую дляCachedDevice
реализациюDebug
. - Реализуйте разбор EBPB в
ebpb.rs
. Как и в случае с MBR, тут должно хватить одной строки сunsafe
. - Протестируйте реализации MBR и EBPB. Попробуйте написать тесты для более тщательного тестирования. Обратите внимание на реализацию
BlockDevice
дляCursor<&mut [u8]>
. Кроме того вы можете красиво вывести структуру при помощи:println!("{:#?}", x);
- Реализуйте
CachedDevice
вvfat/cached.rs
. - Реализуйте
VFat::from()
вvfat/vfat.rs
. ИспользуйтеMasterBootRecord
,BiosParameterBlock
иCachedDevice
для реализации. Протестируйте ваши реализации также как MBR и EBPB. - Реализуйте
FatEntry
вvfat/fat.rs
. - Реализуйте
VFat::fat_entry
, VFat::read_clusterи VFat::read_chain
. Эти вспомогательные методы абстрагируют чтение изCluster
или цепочки кластеров в буфер. Для реализации этих методов вам возможно понадобятся некоторые дополнительные методы. Например вычисление сектора диска из номера кластера. Можете добавлять такие методы свободно. А ещё можно использовать методVFat::fat_entry
для реализации двух других. - Допишите
vfat/metadata.rs
. ТипыDate
,Time
иAttributes
быть идентичны по структуре тем, что хранятся на диске. При их реализации обратитесь к описанию структур FAT. ТипыTimestamp
иMetadata
не имеют аналогичной структуры на диске, но они служат более удобными абстракциями над исходными структурами на диске и будут полезны при реализации трейтовEntry
,File
иDir
. - Реализуйте
Dir
вvfat/dir.rs
иEntry
вvfat/entry.rs
.
Начните с добавления необходимых полей кDir
, которые должны хранить начальныйCluster
каталога иShared<VFat>
. Возможно вы захотите предоставить реалистичные свойства для типаFile
изvfat/file.rs
. Кроме того будет полезным создать дополнительную структуру, которая реализуетIterator<Item=Entry>
и возвращать эту структуру из методаentries()
. При реализацииentries()
будет достаточно одной строкиunsafe
. Кроме того тут могут быть особенно полезныVecExt
иSliceExt
. Читайте описание структур FAT — там много информации, необходимой для реализацииDir
.
Разбор Entry
Поскольку запись может быть либо записью LFN, либо обычной записью, придётся использоватьunion
для представления записи с диска. Заготовка уже предоставлена в видеVFatDirEntry
. Подробнее об объединениях в Rust можно читнуть в документации. Об объединениях вообще можно читнуть в википедии.
В начале вы должны интерпретировать запись в каталоге как неизвестную. Затем использовать эту структуру, чтоб определить, есть ли эта запись и затем определить истинный тип этой самой записи. Работаunion
потребует использование некоторого количества небезопастного кода. Наша реализация использует по одной практически идентичной строке кода на каждый из трёх вариантов.
При анализе имени записи вы должны вручную добавить.
к именам, которые не основаны на LFN, для того, чтоб отделить имя от расширения. При этом добавлять.
нужно только если расширение не пустое.
И наконец нам потребуется декодировать символы в кодировке UTF-16 при анализе записей LFN. Для этого надо использовать функциюdecode_utf16()
. Будет полезно хранить символы UTF-16 в одном или нескольких массивахVec<u16>
при анализе длинного имени файла.
Dir::find()
Вы должны реализоватьDir::find()
после того как реализуетеtraits::Dir
дляDir
. Обратите внимание на то, чтоDir::find()
должна быть независима от регистра символов. При этом реализация должна быть относительно короткой. Как один из вариантов можно использовать eq_ignore_ascii_case() для выполнения сравнения без учёта регистра символов. - Реализуйте
File
вvfat/file.rs
. Начните с добавления полей, которые хранят первыйCluster
в цепочке иShared<VFat>
. Затем реализуйтеtraits::File
для типаFile
. Возможно потребуется немного пофикситьentries()
изDir
. - Реализуйте
VFat::open()
вvfat/vfat.rs
. Используйте components() для того, чтоб пройтись по всем компонентамPath
. Обратите внимание, что реализация, предоставленная нашим вариантомstd
, не содержит каких либо методов, требующих поддержки со стороны операционной системы. Другими словами там нет всякихread_dir()
,is_file()
,is_dir()
и многих других.
Используйте методDir::find()
. РеализацияVFat::open()
должна получаться достаточно короткой. Наша состоит из примерно 17 строк. Вы также можете счесть полезным добавление кDir
вспомогательных методов.
После того, как ваша реализация пройдёт через все юнит-тесты и будет работать так, как от неё ожидают, вы можете насладиться этой маленькой победой. Вы реализовали целую файловую систему! После того, как эйфория пройдёт, можно переходить к следующему этапу.
Седлаем SD-карту
В этой части мы будем взаимодействовать с существующим драйвером контроллера SD-карты для Raspbrerry Pi 3, используя Foreign function interface или FFI для краткости. О FFI в Rust можно читнуть в главе 19.1 книги по Rust. Помимо этого мы создадим глобальный дескриптор для файловой системы в нашей операционной системе. Работать будем в основном в os/kernel/src/fs
.
Foregin Function Interface
FFI в Rust позволяет коду взаимодействовать с программным обеспечением, написанным на других языках программирования и наоборот. Внешние, по отношению к Rust, элементы объявляются в блоке extern
:
extern {
static outside_global: u32;
fn outside_function(param: i16) -> i32;
}
Тут объявляется внешняя функция outside_function
и внешняя глобальная переменная outside_global
. Использовать их можно следующим образом:
unsafe {
let y = outside_function(10);
let global = outside_global;
}
Обратите внимание, что тут требуется использовать блок unsafe
. Rust требует этого, поскольку он не может гарантировать правильность указанных объявлений. Компилятор слепо подставляет эти вызовы функций и взаимодействия с переменными. Другими словами, как и в любых других случаях использования небезопасного кода, Rust предполагает, что вы всё сделали правильно. При этом всём на этапе линковки символы outside_function
и outside_global
должны существовать. Иначе программа не соберётся.
Для вызова функции Rust из внешнего кода, местоположение функции (адрес в памяти) должно быть экспортировано в качестве определённого символа. Внутри Rust может свободно искажать (mangles) символы, которые присваиваются функциям. Для управления версиями и всем таким. Получается, что по умолчанию нельзя узнать заранее, какой символ будет присвоен каждой функции и следовательно мы не сможем вызвать эту функцию из внешнего кода. Для предотвращения этого произвола процесса мы можем добавить атрибут #[no_mangle]
:
#[no_mangle]
fn call_me_maybe(ptr: *mut u8) { .. }
Затем программа на (например) Няшном Си может вызвать эту функцию таким образом:
void call_me_maybe(unsigned char *);
call_me_maybe(...);
Почему Rust не может гарантировать безопасность использования внешнего кода? [foreign-safety]
Объясните, почему Rust не может гарантировать, что использование внешнего кода безопасно. Помимо этого объясните, почему Rust может гарантировать, что другой код Rust безопасен, даже если он находится за пределами текущего крейта, однако не может сделать то же самое для кода не на Rust.
Почему Rust калечит символы? [mangling]
Няшный Си не занимается переименовыванием всего этого. C++ и Rust занимаются. Чем эти два языка отличаются, раз они требуют такого отношения к символам? Предоставьте конкретный пример того, что произойдёт, если Rust не будет это всё делать.
Драйвер SD-карты
Мы предоставили предварительно скомпилированную библиотеку с драйвером SD-карты как os/kernel/ext/libsd.a
. Помимо этого эта библиотека включена в процесс сборки. Т.е. библиотека уже связана с ядром. Кроме того в os/kernel/src/sd.rs
предоставлены объявления всего, что экспортирует эта библиотека.
Сама библиотека зависит от функции wait_micros
, которую она ожидает найти в нашем ядрышке. Функция должна отправлять процессор в сон на указанное количество микросекунд. Вам нужно будет создать и экспортировать эту функцию для успешной линковки. В Няшном Си объявление этой функции выглядит следующим образом:
/*
* Sleep for `us` microseconds.
*/
void wait_micros(unsigned int us);
Задача — обернуть внешний небезопасный API в безопасный Rust-код. Реализуйте структуру Sd
, которая инициализирует контроллер SD-карты в методе new()
. Затем реализуйте трейт BlockDevice
для Sd
. Вам нужно будет использовать unsafe
для взаимодействия с внешними элементами. Проверьте свою реализацию, вручную прочитав MBR прямо из kmain
. Убедитесь, что прочитанные байтики соответсвуют ожидаемым. Когда всё заработает так, как ожидается, переходите к следущему разделу.
Подсказка: На 64-битном ARM
unsigned int
из Няшного Си станетu32
в Rust.
Является ли ваша реализация потокобезопасной? [foreign-sync]
Предоставленный предварительно скомпилированный драйвер SD-карты использует глобальную переменную (
sd_err
) для отслеживания ошибок безо всякой синхронизации. Таким образом эта часть даже не пытается быть потокобезопасной. Как это влияет на правильность наших обвязок? Напомним, что мы должны поддерживать гарантии вокруг гонок данных в Rust при использованииunsafe
-кода. Является ли наш связующий код потокобезопасным? Почему да или почему нет?Подсказка: Скорее всего являются! (Если нет, то должны являться) Что реализует эти гарантии?
Файловая система
В этой части мы будем инициализировать глобальную файловую систему для использования нашим ядром. Основная работа в kernel/src/fs/mod.rs
.
Как и аллокатор памяти, файловая система является глобальным ресурсом. Мы хотим, чтоб оно было доступно всегда и везде. Для того, чтоб это работало, мы создали глобальную переменную static FILE_SYSTEM: FileSystem
в файле kernel/src/kmain.rs
. Как и аллокатор, наша ФС начинает свою работу в неинициализированном состоянии.
На текущий момент у нас есть файловая система и драйвер диска. Пришло время связать их вместе. Доделайте реализацию структуры FileSystem
из kernel/src/fs/mod.rs
, используя при этом файловую систему FAT32 и наши биндинги к драйверу SD-карты. Вы должны инициализировать файловую систему при помощи Sd
(реализующей BlockDevice
) в функции initialize()
. Затем реализуйте трейт FileSystem
для структуры, переведя все вызовы на VFat
. И в конце убедитесь, что инициализируете файловую систему из kmain
после аллокатора памяти.
Проверьте свою реализацию, распечатав содежримое корневого каталога (/
) вашей SD-карты. Как только всё заработает так, как вы ожидаете — переходите к следующему этапу.
Фаза 4: Mo’sh
В этой фазе будем реализовывать команды cd
, pwd
, ls
и cat
для нашей интерактивной строки. Работа ведётся в файлике os/kernel/src/shell.rs
.
Рабочий каталог
Вероятно вы уже знакомы с понятием рабочего каталога. Текущий рабочий каталог (cwd
от current working directory) — это такой каталог, от которого рассчитываются относительные пути к файлам. Например если cwd
равно /a
, то если обратиться к файлу hello
этот самый файл будет искаться под именем /a/hello
. Если cwd
переключить на /a/b/c
, то доступ к файлу hello
будет аналогичен доступу к файлу /a/b/c/hello
. Символ /
может быть добавлен к началу любого пути и в таком случае оный будет считаться абсолютным, а не относительным. Другими словами не будет использовать текущий рабочий каталог. Таким образом обращение к /hello
всегда будет ссылаться на файл с именем hello
в корневом каталоге вне зависимости от текущего рабочего каталога.
В нашей оболочке текущий рабочий каталог можно будет изменить при помощи команды cd <dir>
. Например ежели запустить команду cd /hello/there
, то cwd
станет равным /hello/there
. Если после этого запустить cd you
, то cwd
станет равным cd /hello/there/you
.
Большинство операционных систем предоставляют специальный системный вызов для изменения рабочего каталога процесса. Поскольку наша ОС ещё не имеет процессов и системных вызовов, мы будем следить за изменениями cwd
непосредсвенно силами нашей интерактивной оболочки.
Команды
Вы реализуете четыре команды, которые позволяют взаимодействовать с файловой системой посредством интерактивной оболочки: cd
, pwd
, ls
и cat
. В контексте этого задания они определяются следующим образом:
pwd
(print the working directory). Распечатывает полный путь к текущему рабочему каталогу.cd <directory>
(change (working) directory). Изменяет текущий рабочий каталог наdirectory
. Требует аргументаdirectory
.ls [-a] [directory]
(list the files in a directory). Перечисляет все файлы в каталоге.-a
иdirectory
являются необязательными аргументами. Если передан флаг-a
, то должны отображаться скрытые файлы. В противном случае такие файлы отображаться не должны. Еслиdirectory
не передан, то отображаются записи из текущего рабочего каталога. В противном случае отображаются записи из каталогаdirectory
. Эти аргументы могут быть использованы вместе. Однако-a
должен стоять передdirectory
. Недопустимые аргументы должны приводить к ошибкам. Кроме того должна выводиться ошибка, если каталог не сущесвует.cat <path..>
(concatenate files). Выводит содержимое файлов по указанным путямpath
один за другим. Требуется по меньшей мере один такой аргумент. Если путь не указывает на реально сущесвующий файл — выводить ошибку. Если файл содержит недопустимый контент в контексте кодировки UTF-8, то также выводить ошибку.
Все не абсолютные пути должны быть обработаны относительно текущего рабочего каталога. Все абсолютные пути должны обрабатываться от корня. Пример действия команд можно посмотреть в гифке выше. Когда вы будете реализовывать эти команды — можете выводить ошибки или содержимое каталогов любым способом, каким сочтёте нужным. Главное, чтоб вся необходимая информация присутствовала.
Реализация
Расширьте os/kernel/src/shell.rs
реализацией этих четырёх команд. Используйте мутабельный PathBuf
для того, чтоб отслеживать текущий рабочий каталог. Этот самый PathBuf
должен изменяться каждым вызовом cd
. Будет полезно создать функции с общей сигнатурой для каждой из ваших команд. Для дополнительного уровня типобезопасности можете выделить трейт, как абстракцию для команд и реализовывать этот трейт для каждой команды.
После того, как вы реализовали, протестировали и проверили все четыре команды с учётом указанных спецификаций — задача этой лабы решена. Поздравляем!
Убедитесь, что используете bin-аллокатор!
Скорее всего реализация файловой системы будет очень интенсивно использовать память. Во избежание нехватки памяти убедитесь, что используете bin-аллокатор. Ибо он позволяет не только выделять, но и освобождать память.
Подсказка: Используйте существующие методы
PathBuf
иPath
для своих грязных целей.
Подсказка: Вам потребуется специально обратить внимание на
..
и.
при реализацииcd
.
Такие дела.
Автор: lain8dono