Файловые системы от read() до диска (обзор)
До сих пор речь шла о процессоре, памяти, синхронизации, то есть о том, что происходит внутри. Теперь вопрос, который касается каждого пользователя - как файл попадает с диска в программу? Ответ, это целая иерархия из слоёв, и эта часть, это обзор иерархия. Каждый отдельный слой потом получит свою глубокую подробную статью.
Зачем столько слоёв?
Прочитать файл - звучит как одно действие, но это четыре очень разные задачи разом. Кто-то должен разобрать путь /docs/x.txt. Кто-то должен понимать конкретный формат, ведь FAT устроен иначе, чем EXT. Кто-то должен найти нужный раздел (partition). И кто-то должен забрать сырые секторы (row data) с нужного железа, со старого диска IDE иначе, чем с современного SATA SSD. Каждая из этих задач, это отдельный слой. Это позволяет держать каждый из них простым и, что мне важнее, заменяемым. Новая файловая система или новый дисковый драйвер встраивается, а другие слои этого не замечают.
Слои файловой системы сверху вниз
В самом верху находится VDS, управление FD (descriptoren). open() отдаёт тебе файловый дескриптор, простое число, и VDS помнит для каждого процесса, на что указывает каждое из этих чисел. Для файла оно указывает на поток VFS. VDS может указывать и на совсем другое устройство - на консоль, на сетевую карту, но здесь я не буду останавливаться подробно и VDS получит свою полную статью. Важно знать - отдельного слоя блочного устройства у меня пока нет, файловые системы вызывают слой Storage напрямую.
Ниже идёт VFS, виртуальная файловая система. Она понимает пути.
Из /dev/hd0p0:/docs/x.txt она делает :
- /dev - устройство
- hd0p0: - физического устройства 0 раздел 0
- / - абсолютный путь от корневого елемента
- docs - искать директорий
- x.txt - искать или создать (в зависимости от OP)
- знает текущий рабочий каталог каждого процесса
- и затем передаёт вниз.
Абстрактный слой ФС, это посредник. Он знает, какая конкретная файловая система на каком разделе, и вызывает подходящую реализацию. Здесь "прочитать файл X" становится конкретным вызовом tria_read_file , fat_read_file или ext_read_file.
Конкретная файловая система наконец понимает формат на диске. TriaFS, FAT и EXT2/3 реализованы полностью, чтение и запись. ISO9660 (диск с данными) и CDDA (аудио-CD), это намеренно только читающие каркасы, на CD не пишут, да и реализованы еще не до конца.
Ниже, раздел (MBR или GPT), затем
слой Storage, который мыслит только секторами,
и в самом низу драйверы IDE и AHCI, которые реально говорят с железом.
Абстрактный слой
на удивление прост - ветвление по типу файловой системы раздела.
uint8_t* fs_read_file(uint8_t partition, const char* path, uint32_t base, uint32_t* out_size) {
uint8_t fs = partition_get_filesystem(partition);
if (fs == PARTITION_FILESYSTEM_FAT) return fat_read_file(partition, path, base, out_size);
if (fs == PARTITION_FILESYSTEM_TRIAFS) return triafs_read_file(path, base, out_size);
if (fs == PARTITION_FILESYSTEM_EXT) return ext_read_file(path, base, out_size);
/* ... ISO9660, CDDA ... */
}
Каждая конкретная файловая система предлагает одни и те же операции, чтение, запись, перечисление каталога, отличается лишь реализация за ними. Поэтому VFS сверху всегда видит один и тот же интерфейс, совершенно независимо от того, TRIAFS, FAT под ним или EXT.
Как раздел попадает в систему
При старте система обходит каждый диск, читает таблицу разделов (MBR или GPT) и для каждого раздела первый сектор. По нему она опознаёт файловую систему, у каждой есть уникальная метка:
if (header->magic == TRIAFS_MAGIC) { /* TriaFS */ }
if (bpb->signature == 0x55AA && bpb->bps) { /* FAT */ }
if (sb->ext_signature == EXT_SIGNATURE /*0xEF53, по смещению 1024*/) { /* EXT */ }
Каждый распознанный раздел попадает в реестр с носителем, стартовым сектором и типом файловой системы:
struct connected_partition_info_t {
uint8_t medium_type; // hd0, hd1, оптический привод ...
uint8_t filesystem; // FAT · LISTFS · EXT · ISO9660 · CDDA
uint32_t first_sector; // где раздел начинается на диске
uint32_t num_of_sectors;
};
С этого момента абстрактный слой знает для каждого раздела, какую реализацию вызывать.
Путь на примере одного read()
Конкретно, сверху до диска, на примере раздела FAT:
Приходит read(fd), VDS смотрит, на что указывает FD (поток VFS).
2. VFS через vfs_fread передаёт абстрактному слою.
3. Абстрактный слой диспетчеризует по типу ФС раздела к fat_read_file.
4. FAT превращает путь в цепочку кластеров, а кластеры в номера секторов.
5. Слой Storage получает секторы и выбирает драйвер:
StorageError thread_read_storage(StorageContext* ctx, uint32_t sector,
uint8_t num_of_sectors, void* memory);
6. IDE или AHCI читает секторы. И тут видна разница поколений: IDE перекидывает каждое слово через порты ввода-вывода (PIO), AHCI же даёт диску самому писать в память через DMA.
// IDE (старый, PIO): процессор читает каждое слово вручную
for (uint32_t j = 0; j < 256; j++) *mem++ = inw(base_port + 0); // 512 байт = 256 слов
// AHCI (современный, DMA) - кладём команду в command-list, железо делает передачу само
sata_read(port_base, command_list, sector, num_sectors, memory);
Пусть будет правда жизни
Эта иерархическая система росла долго, и это по ней видно. На границах слоёв находилось большинство моих проблем с блокировками, у каждого слоя своя (контекст VFS, блокировка раздела, блокировка Storage), и их порядок должен быть верным, иначе получаются ровно те deadlock'и из части про синхронизацию. Баг, стоивший мне часов, был простым printf под удерживаемой блокировкой Storage, который блокировал очередь консоли и замораживал всю систему на безобидном ls. А пути записи не устойчивы к сбоям - журнала нет, если питание пропадёт посреди записи, файловая система может остаться повреждённой. Буду доделывать :-)
Что дальше
Это был обзор. В следующих частях я опишу каждый слой по отдельности, каждый с настоящим кодом, проблемами, которые у меня были, и трюками, которые меня спасли. Сначала VDS (полностью, со всей механикой FD), затем VFS, абстрактный слой, разделы, слой Storage и, наконец, IDE и AHCI.
◀ Предыдущая статья Содержание Следующая статья ▶
*Система не стоит на месте, поэтому в дальнейшем тексты могут не совпадать с реальным положением