Разделы MBR и GPT
В абстрактном слое ФС была таблица connected_partitions[] и я пользовался ею, будто она просто есть. Но это не так. При загрузке кто-то должен просканировать каждый диск, выяснить, на какие области он разбит, и какая файловая система лежит в какой области. Это работа этого слоя, и она на удивление детективная.
Что делает этот слой
Три шага:
- прочитать таблицу разделов диска
- вывести из неё области (разделы)
- для каждой области угадать, какая файловая система там лежит.
Результат попадает в таблицу, которой потом пользуется абстрактный слой. "Угадать" это буквально, ведь нигде на диске нет таблички "здесь FAT". Надо смотреть и сравнивать.
MBR - Master Boot Record
Есть два рода таблиц разделов. Старый, это Master Boot Record, и он целиком помещается в самый первый сектор диска. Его структура почти не менялась с восьмидесятых.
struct mbr_partition_entry_t {
uint8_t status: 7;
uint8_t bootable: 1;
uint8_t H_of_first_sector; // CHS — Cylinder/Head/Sector (legacy)
/* ... ещё поля CHS ... */
uint8_t type; // 0x7E = TriaFS, 0xEE = защита GPT, ...
uint32_t first_sector; // LBA — именно этот номер важен
uint32_t number_of_sectors;
} __attribute__((packed));
struct mbr_sector_t {
uint8_t reserved[0x1BE]; // 446 байт загрузочного кода
struct mbr_partition_entry_t partition_entry[4];
uint16_t boot_signature; // должна быть 0xAA55
} __attribute__((packed));
Четыре записи, больше не влезает, это знаменитый предел в четыре раздела у MBR. Каждая запись несёт ещё старые поля CHS (цилиндр, головка, сектор) из времён, когда диску называли его геометрию напрямую. Сегодня используется лишь first_sector, линейный номер LBA. Поля CHS тащатся за нами по традиции.
GPT
Современная таблица, это GUID Partition Table. Она распределена по нескольким секторам - защитный MBR в секторе 0 (одна запись типа `0xEE`, которая не даёт старым утилитам считать диск пустым), затем заголовок GPT с сигнатурой "EFI PART", а за ним массив записей, каждая не меньше 128 байт, с двумя GUID, 64-битными номерами секторов и именем. Так отпадают оба предела MBR - много разделов вместо четырёх, огромные диски вместо 2 ТБ. В моей системе GPT пока поддерживается до 2 ТБ, потому что путь ещё считает в 32-битных номерах секторов, всё сверх того пропускается.
Сканирование дисков
Обнаружение всегда начинается одинаково: прочитать сектор 0 и посмотреть, что внутри.
// прочитать сектор 0
thread_read_storage(ctx, 0, 1, &mbr_sector);
uint16_t sig = *((uint16_t*)((uint8_t*)&mbr_sector + 510));
bool has_valid_mbr = (sig == 0xAA55);
if (has_valid_mbr ) {
for (int i = 0; i < 4; i++) {
if (type == 0x7E) add(... TRIAFS ...); // свой тип TRIAFS
else if (type == 0xEE) { /* прочитать таблицу GPT, взять её записи */ }
else if (num_sectors > 0) add(... UNKNOWN ...); // ФС пока неизвестна
}
} else {
add(... UNKNOWN, от сектора 0, весь диск ...);
}
Бросаются в глаза два случая. Первый: тип 0x7E, это самостоятельно назначенное значение для разделов TriaFS. Второй: 0xEE значит "здесь следует GPT". Если действительного MBR не нашлось вовсе, весь диск трактуется как единый, пока неизвестный раздел. Оптические носители, это отдельная глава, у них всегда ровно один раздел.
Проба ФС
После разбора области известны, но у большинства написано лишь "неизвестная файловая система". Теперь идёт детективная часть: для каждого неизвестного раздела по очереди пробуются три пробы, и первая, что подходит, побеждает.
// порядок важен — первое совпадение побеждает
if (is_partition_fat(p.first_sector)) p.type = PARTITION_FILESYSTEM_FAT;
else if (is_partition_ext(p.first_sector)) p.type = PARTITION_FILESYSTEM_EXT;
else if (is_partition_listfs(p.first_sector)) p.type = PARTITION_FILESYSTEM_TRIAFS;
Каждая проба читает характерный блок соответствующей файловой системы и проверяет его. Тут есть пример, который я показываю на EXT. Одной магии ( #define EXT_SIGNATURE 0xEF53 ) мало, ведь случайное число в нужном месте могло бы выглядеть как магия. Поэтому EXT дополнительно проверяет, сходятся ли структурные числа:
uint8_t is_partition_ext(uint32_t first_partition_sector) {
struct ext_superblock_t sb;
StorageContext* ctx = &get_current_thread()->storage_context;
// суперблок EXT лежит на два сектора позади начала раздела
if (thread_read_storage(ctx, first_partition_sector + 2, 1, &sb) != STORAGE_SUCCESS)
return false;
if (sb.ext_signature == EXT_SIGNATURE // 0xEF53 — магия
&& sb.total_inodes != 0 && sb.total_blocks != 0
&& sb.blocks_per_group != 0 && sb.inodes_per_group != 0
/* ... и: число блок-групп согласовано, состояние верно, фичи поддержаны ... */)
return true;
return false;
}
Если блоков на группу, инодов на группу и итоги не сходятся, это не настоящий EXT, что бы ни говорила магия. Фиксированный порядок трёх проб вдобавок не даёт спутать две файловые системы.
Почти всё без блокировки
Всё обнаружение идёт на буферах стека, у каждого потока свои. Поэтому чтению и пробам не нужна блокировка. thread_select_storage_medium тоже без блокировки (тот же per-thread StorageContext из статьи про абстрактный слой). Лишь когда обнаруженный раздел добавляется в глобальный список add_new_partition_to_list ненадолго осуществляется блокировка. Так в теории можно было бы сканировать даже несколько дисков сразу, хотя при загрузке это пока идёт по очереди.
Честное пречестное...
По сути этот слой это набор эвристик, а эвристики могут ошибаться. Проверка правдоподобия в EXT именно для этого, сломанный или чужой диск не должен случайно сойти за действительную файловую систему. Предел GPT в 2 ТБ, это наследие 32-битного пути. Поправлю когда нибудь...
Что дальше
В каждой пробе стояло thread_read_storage(ctx, сектор, число, буфер), будто это само собой. Это не так, кто-то должен реально достать секторы из железа, будь то по IDE или AHCI, и при этом следить, чтобы параллельные обращения не столкнулись между собой. Это следующая часть - слой Storage. Дальше драйверы IDE и AHCI.
Было бы интересно увидеть ваши комментарии и улучшить статьи.
◀ Предыдущая статья Содержание Следующая статья ▶
*Система не стоит на месте, поэтому в дальнейшем тексты могут не совпадать с реальным положением