Такая система, которая выглядит сложной, призвана удовлетворить ряд требований. Во-первых, она позволяет добиться фиксированного размера структуры индексного дескриптора и в то же время допускает произвольный размер файлов. Кроме того, она позволяет файловой системе хранить блоки файла в виде несмежных блоков, благодаря чему возможен произвольный доступ к данным с помощью команды lseek(); ядру необходимо лишь определить, по какому указателю (или указателям) следовать. И наконец, для небольших файлов, которые составляют подавляющее большинство от общего числа файлов во многих файловых системах, такая схема разрешает быстрый доступ к блокам данных файла через прямые указатели индексного дескриптора.
Для примера здесь выполнена оценка одной системы, содержащую более чем 150 000 файлов. Около 30 % этих файлов имели размер не более 1000 байт каждый, а 80 % файлов занимали 10 000 байт и менее. Если принять размер блока равным 1024 байтам, для всех файлов из второй группы можно было бы использовать всего 12 прямых указателей, которые могут ссылаться на блоки, содержащие в общей сложности 12 288 байт. При использовании блока размером 4096 байт этот предел возрастает до 49 152 байт (и он охватывает 95 % файлов в данной системе).
Такая схема допускает также наличие файлов гигантских размеров; при размере блока 4096 байт самый большой теоретически возможный размер файла составляет чуть более 1024 × 1024 × 1024 × 4096 байт, или около 4 Тбайт (4096 Гбайт). (Я говорю «чуть более», поскольку имеются блоки, на которые указывают прямые, косвенные и двойные косвенные указатели. Но их количество несущественно по сравнению с диапазоном, для которого можно использовать тройной косвенный указатель.)
Еще одним преимуществом, которое предоставляет такая схема, является то, что файлы могут обладать дырами, как описано в разделе 4.7. Вместо выделения блоков с пустыми байтами для дыр в файле файловой системе достаточно пометить (значением 0) соответствующие указатели в индексном дескрипторе и в блоках косвенного указателя, чтобы показать, что они не ссылаются на актуальные блоки диска.
Каждая файловая система, которая доступна в Linux, отличается деталями своей реализации. К числу таких различий относятся, например, способы выделения блоков для файла и организация каталогов. Если бы каждой программе, которая работает с файлами, потребовалось вникать в особенности каждой файловой системы, то тогда задача по написанию программ, работающих во всех файловых системах, стала бы практически неосуществимой.
Рис. 14.2.
• Виртуальная файловая система определяет обобщенный интерфейс для операций файловой системы. Все программы, которые работают с файлами, выражают свои операции в терминах данного обобщенного интерфейса.
• Каждая файловая система обеспечивает реализацию интерфейса виртуальной файловой системы.
Согласно этой схеме программам необходимо понимать только VFS-интерфейс. Они могут игнорировать детали реализации отдельных файловых систем.
Интерфейс виртуальной файловой системы содержит операции, соответствующие всем обычным системным вызовам для работы с файловыми системами и каталогми: open(), read(), write(), lseek(), close(), truncate(), stat(), mount(), umount(), mmap(), mkdir(), link(), unlink(), symlink() и rename().
Уровень абстракции VFS очень близок к традиционной модели файловой системы UNIX. Естественно, некоторые файловые системы — в особенности не относящиеся к семейству UNIX — поддерживают не все операции виртуальной файловой системы (например, файловая система VFAT, разработанная компанией Microsoft, не поддерживает символические ссылки, созданные с помощью команды symlink()). В таком случае основная файловая система возвращает обратно на уровень VFS код ошибки, сообщающий об отсутствии поддержки, а виртуальная система, в свою очередь, возвращает этот код ошибки в приложение.
Рис. 14.3.