Анатомия виртуального коммутатора файловых систем Linux
Операционная система Linux® — само воплощение гибкости и расширяемости. Возьмём, к примеру, виртуальный коммутатор файловых систем (virtual file system switch, VFS). Он позволяет создавать файловые системы на различных устройствах, — на традиционных дисках, USB флэш-накопителях, в памяти, на других устройствах хранения. Можно даже встроить файловую систему в контекст другой файловой системы. Давайте узнаем, что же делает VFS столь мощным инструментом, и рассмотрим его основные интерфейсы и процессы.
Гибкость и расширяемость поддержки файловых систем в Linux является прямым следствием наличия абстрагированного набора интерфейсов. В основе этого набора интерфейсов лежит виртуальный коммутатор файловых систем (virtual file system switch, VFS).
VFS предоставляет набор стандартных интерфейсов, которые дают приложениям верхнего уровня возможность выполнять файловые операции ввода-вывода через различные файловые системы. При этом одновременно поддерживаются различные файловые системы на одном или нескольких ниже лежащих устройствах. Кроме того, эти файловые системы не обязательно должны быть статичными, они могут появляться и исчезать в соответствии с изменениями в устройствах хранения.
Например, типичный настольный компьютер с Linux поддерживает файловую систему ext3 на имеющемся жестком диске, а также файловую систему ISO 9660 на имеющемся CD-ROM (другое название — CD-ROM file system, CDFS). Поскольку компакт-диски то вставляются, то извлекаются, ядро Linux должно подстраиваться под эти новые файловые системы с различным содержимым и структурой. Также может иметь место доступ к удаленной файловой системе с помощью Network File System (NFS). В то же время Linux может монтировать раздел с файловой системой NT File System (NTFS) с локального жесткого диска системы с двойной загрузкой (Windows® или Linux) и осуществлять чтение или запись на неё.
Наконец, к компьютеру может быть оперативно подключен съемный USB флэш-накопитель, что добавляет еще одну файловую систему. В течение этого времени на этих устройствах может использоваться один и тот же набор интерфейсов ввода-вывода, что позволяет абстрагировать от пользователя нижележащие файловую систему и физическое устройство (см. рисунок 1).
Рисунок 1. Уровень абстракции предоставляет единый интерфейс поверх различных файловых систем и устройств хранения
Слои (уровни) абстрагирования
Теперь давайте рассмотрим конкретную архитектуру реализации абстрактных функций, предоставляемых VFS в Linux. На рисунке 2 показан общий вид уровней в Linux с точки зрения VFS. Над VFS располагается стандартный интерфейс системных вызовов ядра (standard kernel system-call interface, SCI). Этот интерфейс позволяет передавать вызовы из пространства пользователя в ядро (в разные адресные пространства). В этой области приложение из пространства пользователя, делающее вызов POSIX open, через библиотеку GNU C (glibc) проходит в ядро и в демультиплексор системных вызовов. В конце концов с помощью вызова sys_open вызывается VFS.
Рисунок 2. Структура слоев VFS
VFS предоставляет уровень абстракции, отделяющий POSIX API от подробностей работы конкретной файловой системы. Ключевым моментом здесь является то, что системные вызовы API Open, Read, Write или Close работают одинаково, независимо от того, какая файловая система располагается ниже: ext3 или Btrfs. VFS предоставляет общую файловую модель, которую наследуют нижележащие файловые системы (они должны реализовать действия для различных функций POSIX API). Дальнейшее абстрагирование, за пределами VFS, скрывает находящееся ниже физическое устройство (которое может являться диском, разделом диска, сетевым модулем хранения, памятью или любым другим носителем, способным хранить информацию—даже временно).
В дополнение к сокрытию деталей файловых операций от лежащих ниже файловых систем VFS «привязывает» нижележащие блочные устройства к имеющимся файловым системам. Теперь давайте посмотрим на внутреннюю организацию VFS, чтобы увидеть, как он работает.
Внутренняя организация VFS
Перед рассмотрением общей структуры подсистемы VFS, взглянем на главные используемые объекты. В этом разделе изучаются суперблок (superblock), индексный узел (inode), запись каталога (dentry), и, наконец, объект file. Некоторые другие важные дополнительные элементы, например, кэши, будут рассмотрены позже при рассмотрении общей структуры.
Суперблок
Суперблок (superblock) является контейнером для высокоуровневых метаданных о файловой системе. Суперблок — это структура, которая существует на диске (на самом деле в нескольких местах диска для избыточности) и в памяти. Он предоставляет основу для действий с файловой системой на диске, так как в ней определяются параметры для управления файловой системой (например, суммарное число блоков, свободных блоков, корневой индексный узел).
На диске суперблок предоставляет ядру информацию о структуре файловой системы на диске. В памяти суперблок предоставляет необходимую информацию и состояние для управления активной (смонтированной) файловой системой. Поскольку Linux поддерживает параллельно несколько одновременно смонтированных файловых систем, каждая структура super_block сохраняется в списке (super_blocks, определяется в ./linux/fs/super.c, а структура определена в /linux/include/fs/fs.h).
На рисунке 3 приводится упрощенный вид суперблока и его элементов. Структура super_block ссылается на ряд других структур, которые содержат другую информацию. Например, структура file_system_type определяет имя файловой системы (например, ext3), а также различные блокировки и функции для получения и удаления super_block. Управление объектами file_system_type осуществляется через хорошо известные функции register_file system и unregister_file system (см. ./linux/fs/file systems.c). Структура super_operations определяет ряд функций для чтения и записи inod’ов, а также для операций более высокого уровня (таких как повторное монтирование). Объект записи корневого каталога (dentry) здесь тоже кэшируется, как и блочное устройство, на котором находится эта файловая система. Наконец, предоставляется число списков для управления объектами inode, в том числе s_inodes (список всех inod’ов), s_dirty (список всех «грязных» inod’ов), s_io и s_more_io (зарегистрированные для отложенной записи) и s_files (список всех открытых файлов для данной файловой системы).
Рисунок 3. Упрощенный вид структуры super_block и связанных с ней элементов
Обратите внимание, что внутри ядра другой объект управления, называемый vfsmount, предоставляет информацию о смонтированных файловых системах. Список этих объектов ссылается на суперблок и определяет точку монтирования, имя /dev-устройства, на котором находится эта файловая система, и другую информацию о присоединении на верхнем уровне.
Индексный узел (inode)
Linux управляет всеми объектами в файловой системе через объект, называемый inode (сокращение от index node). Inode может ссылаться на файл или каталог или символическую ссылку на другой объект. Обратите внимание, что поскольку файлы используются для представления других типов объектов, например, устройств или памяти, inod’ы используются и для их представления.
Обратите также внимание, что inode, который я здесь имею в виду, является inod’ом уровня VFS (inode в памяти). Каждая файловая система также включает в себя inode, который находится на диске и предоставляет сведения об объекте, характерном для конкретной файловой системы.
Inod’ы VFS размещаются с помощью механизма распределения памяти slab (из inode_cache; см. ссылку в разделе Ресурсы для дополнительной информации по распределителю slab). Inode состоит из данных и операций, которые описывают inode, его содержимое и различные операции, которые на нем возможны. На рисунке 4 приведена простая иллюстрация inod’а VFS, состоящего из ряда списков, один из которых ссылается на записи dentry, которые ссылаются на этот inode. Сюда входят метаданные объектного уровня, состоящие из знакомых атрибутов времени (время создания, время доступа, время изменения), а также данные о принадлежности владельцу и о правах доступа (идентификатор группы, идентификатор пользователя и права доступа). Inode ссылается на файловые операции, которые возможны на нем, большинство из которых непосредственно отображаются на интерфейсы системных вызовов (например, open, read, write и flush). Есть также ссылка на относящиеся к inod’у операции (create, lookup, link, mkdir и так далее). Наконец, есть структура для управления фактическими данными для объекта, который представлен объектом адресного пространства. Объект адресного пространства — это такой объект, который управляет различными страницами для inod’а в кэше страниц. Объект адресного пространства используется для управления страницами для файла, а также для отображения секций файла на индивидуальные адресные пространства. Объект адресного пространства существует со своим собственным набором операций (writepage, readpage, releasepage и так далее).
Рисунок 4. Упрощенное представление inod’а VFS
Обратите внимание, что всю эту информацию можно найти в ./linux/include/linux/fs.h.
Запись каталога (dentry)
Иерархическая природа файловой системы управляется другим объектом в VFS, называемым dentry. В файловой системе имеется одна корневая запись dentry (на которую имеется ссылка в системном блоке), причем это единственная запись dentry без родителя. У всех других записей dentry есть родители, а у некоторых — потомки. Например, при открытии файла, составленного из /home/user/name, создается четыре dentry-объекта: один для корня /, один для записи home корневого каталога, один для записи name каталога user и, наконец, один для записи name в каталоге user. Таким образом, записи dentry четко отображаются на иерархические файловые системы, используемые сегодня.
Объект dentry определяется структурой dentry (в ./linux/include/fs/dcache.h). Она состоит из ряда элементов, которые отслеживают связь данной записи с другими записями в файловой системе, а также физическими данными (такими как имя файла). Упрощенный вид dentry-объекта приведен на рисунке 5. Объект dentry ссылается на super_block, который определяет конкретный экземпляр файловой системы, в котором содержится этот объект. Затем идет родительская запись dentry (родительский каталог) объекта, за ней дочерние записи dentry, содержащиеся в списке (если объект оказывается каталогом). Затем определяются операции для dentry (состоящие из таких операций, как hash, compare, delete, release и так далее). Затем определяется имя объекта, которое здесь хранится в записи dentry вместо самого inod’а. Наконец, дается ссылка на inode VFS.
Рисунок 5. Упрощенное представление dentry-объекта
Заметьте, что dentry-объекты существуют только в памяти файловой системы и не хранятся на диске. Постоянно хранятся только inod’ы, а dentry-объекты используются для повышения производительности. Полное описание структуры dentry можно посмотреть в ./linux/include/dcache.h.
Объект file
Для каждого открытого в Linux-системе файла существует объект file. Этот объект содержит информацию, относящуюся к открытому экземпляру для данного пользователя. Очень упрощенный вид file-объекта представлен на рисунке 6. Как можно видеть, структура path содержит ссылку и на dentry, и на vfsmount. Для каждого файла определен набор файловых операций; это хорошо известные файловые операции (open, close, read, write, flush и так далее). Определен набор флагов и полномочий (включая группу и владельца). Наконец, для конкретного экземпляра файла определяются данные, хранящие состояние, например, текущее смещение в файле.
Рисунок 6. Упрощенное представление объекта file
Связи объектов
Теперь, рассмотрев различные важные объекты в слое VFS, давайте посмотрим, как они связаны между собой на одной схеме. Исследовав объект снизу вверх, взглянем теперь в обратном направлении, с точки зрения пользователя (см. рисунок 7).
Наверху находится открытый объект file, на который ссылается список файловых дескрипторов процесса. Объект file ссылается на объект dentry, который ссылается на inode. Оба объекта, и inode и dentry, ссылаются на лежащий ниже объект super_block. Несколько file-объектов могут ссылаться на один и тот же dentry (например, в случае, когда два пользователя совместно используют один и тот же файл). Обратите внимание также, что на рисунке 7 объект dentry ссылается на другой объект dentry. В этом случае каталог ссылается на файл, который, в свою очередь, ссылается на inode для конкретного файла.
Рисунок 7. Связи основных объектов в VFS
Архитектура VFS
Внутренняя архитектура VFS состоит из распределительного слоя, который обеспечивает абстрагирование файловой системы, и ряда кэшей для повышения производительности операций файловой системы. В этом разделе исследуется внутренняя архитектура и взаимодействия основных объектов (см. рисунок 8).
Рисунок 8. Обобщенный вид слоя VFS
Два главных объекта, которые динамически поддерживаются в VFS, — это объекты dentry и inode. Они кэшируются для ускорения доступа к лежащим ниже файловых системам. Когда файл открывается, кэш dentry заполняется записями, представляющими уровни каталогов, составляющих путь к файлу. Также для объекта создается inode, представляющий файл. Кэш dentry строится с помощью хеш-таблицы и хешируется по имени объекта. Записи для кэша dentry размещаются в dentry_cache с помощью распределителя памяти slab; когда появляется нехватка памяти, для удаления записей используется алгоритм вытеснения по давности использования. Функции, связанные с кэшем dentry, можно найти в ./linux/fs/dcache.c (и ./linux/include/linux/dcache.h).
Кэш inode-объектов реализован в виде двух списков и хеш-таблицы для ускорения поиска. В первом списке определяются inod’ы, которые используются в данное время; во втором списке определяются неиспользуемые inod’ы. Используемые inod’ы хранятся также в хеш-таблице. Отдельные объекты кэшей inod’ов размещаются из inode_cache с помощью распределителя памяти slab. Функции, относящиеся к кэшу inod’ов, можно найти в ./linux/fs/inode.c (и ./linux/include/fs.h). В нынешней реализации кэш inod’ов зависит от кэша dentry. При существующем объекте dentry в кэше inod’ов существует и объект inode. Поиски выполняются в кэше dentry, что в итоге приводит к объекту в кэше inod’ов.