Разработка ядра Linux

Лав Роберт

Глава 12

Виртуальная файловая система

 

 

Виртуальная файловая система (Virtual File System), иногда называемая виртуальным файловым коммутатором (Virtual File Switch) или просто VFS, — это подсистема ядра, которая реализует интерфейс пользовательских программ к файловой системе. Все файловые системы зависят от подсистемы VFS, что позволяет не только сосуществовать разным файловым системам, но и совместно функционировать. Это также дает возможность использовать стандартные системные вызовы для чтения и записи данных на различные файловые системы, которые находятся на различных физических носителях, как показано на рис. 12.1.

Рис. 12.1. Подсистема VFS в действии: использование команды cp(1) для копирования данных с жесткого диска, на котором монтируется файловая система, ext3, на гибкий диск, на котором монтируется файловая система ext2

 

Общий интерфейс к файловым системам

Подсистема VFS — это связующее звено, которое позволяет таким системным вызовам, как open(), read() и write(), работать независимо от файловой системы и физической среды носителя информации. Сегодня это может не впечатлять, поскольку такая возможность принимается как должное. Тем не менее сделать так, чтобы общие системные вызовы работали для всех поддерживаемых файловых систем и физических сред хранения данных, — задача не тривиальная. Более того, эти системные вызовы позволяют выполнять операции между различными файловыми системами и различными физическими носителями — мы можем копировать и перемещать данные с одной файловой системы на другую с помощью стандартных системных вызовов. В старых операционных системах (например, DOS) таких возможностей не было. Любые операции доступа к "неродным" файловым системам требовали использования специальных утилит. Сейчас такие возможности существуют, потому что все современные операционные системы, включая Linux, абстрагируют доступ к файловым системам с помощью виртуального интерфейса, который дает возможность совместной работы с данными и обобщенного доступа к данным. В операционной системе Linux может появиться поддержка новых типов файловых систем или новых физических средств хранения данных, при этом нет необходимости переписывать или перекомпилировать существующие программы.

 

Уровень обобщенной файловой системы

Общий интерфейс для всех типов файловых систем возможен только благодаря тому, что в ядре реализован обобщающий уровень, который скрывает низкоуровневый интерфейс файловых систем. Данный обобщающий уровень позволяет операционной системе Linux поддерживать различные файловые системы, даже если эти файловые системы существенно отличаются друг от друга своими функциями и особенностями работы. Это в свою очередь становится возможным благодаря тому, что подсистема VFS реализует общую файловую модель, которая в состоянии представить общие функции и особенности работы потенциально возможных файловых систем. Конечно, эта модель имеет уклон в сторону файловых систем в стиле Unix (что представляют собой файловые системы в стиле Unix, будет рассказано в следующем разделе). Несмотря на это в ОС Linux поддерживается довольно большой диапазон различных файловых систем.

Обобщенный уровень работает путем определения базовых интерфейсов и структур данных, которые нужны для поддержки всех файловых систем. Код поддержки каждой файловой системы должен формировать все концепции своей работы в соответствии с шаблонными требованиями подсистемы VFS, например "так открываем файл", а "так представляем каталог". Код файловой системы скрывает все детали реализации. По отношению к уровню VFS и остальным частям ядра все файловые системы выглядят одинаково, т.е. все файловые системы начинают поддерживать такие объекты, как файлы и каталоги, и такие операции, как создание и удаление файла.

В результате получается общий уровень абстракции, который позволяет ядру легко и просто поддерживать множество типов файловых систем. Код файловых систем программируется таким образом, чтобы поддерживать общие интерфейсы и структуры данных, которые нужны для работы с виртуальной файловой системой. В свою очередь, ядро легко может работать со всеми файловыми системами, и соответственно, экспортируемый ядром интерфейс пользователя также позволяет аналогично работать со всеми файловыми системами.

В ядре нет необходимости поддерживать низкоуровневые детали реализации файловых систем нигде, кроме кода самих файловых систем. Например, рассмотрим следующую простую программу, работающую в пространстве пользователя.

write(f, &buf, len);

Этот системный вызов записывает len байт из области памяти по адресу &buf в файл, представленный с помощью дескриптора f, начиная с текущей позиции файла. Этот системный вызов вначале обрабатывается общей функцией ядра sys_write(), которая определяет функцию записи в файл для той файловой системы, на которой находится файл, представленный дескриптором f. Далее общий системный вызов вызывает найденную функцию, которая является частью реализации файловой системы и служит для записи данных на физический носитель (или для других действий, которые файловая система выполняет при записи файла). На рис. 12.2 показана диаграмма выполнения операции записи, начиная от пользовательской функции write() и заканчивая поступлением данных на физический носитель. Далее в этой главе будет показано, как подсистема VFS позволяет достичь необходимой абстракции и какие для этого обеспечиваются интерфейсы.

Рис. 12.2. Схема прохождения данных из пространства пользователя, где вызывается функция write(), через общий системный вызов VFS, к специфическому методу записи файловой системы и, наконец, поступление на физический носитель

 

Файловые системы Unix

Исторически так сложилось, что ОС Unix обеспечивает четыре абстракции, связанные с файловыми системами: файлы, элементы каталогов (directory entry), индексы (inode) и точки монтирования (mount point).

Файловая система — это иерархическое хранилище данных определенной структуры. Файловые системы содержат файлы, каталоги и соответствующую управляющую информацию. Обычные операции, которые выполняются с файловыми системами, — это создание (create), удаление (delete) и монтирование (mount). В ОС Unix файловые системы монтируются на определенную точку монтирования в общей иерархии, которая называется пространством имен (namespace). Это позволяет все файловые системы сделать элементами одной древовидной структуры.

Файл (file) — это упорядоченный поток байтов. Первый байт соответствует началу файла, а последний байт — концу файла. Каждому файлу присваивается удобочитаемое имя, по которому файл идентифицируется как пользователями, так и системой. Обычные файловые операции— это чтение (read), запись (write), создание (create) и удаление (delete).

Файлы помещаются в каталогах (directory). Каталог — это аналог папки, которая обычно содержит связанные между собой файлы. Каталоги могут содержать подкаталоги. В этой связи каталоги могут быть вложены друг в друга и образуют пути (path). Каждый компонент пути называется элементом каталога (directory entry). Пример пути — "/home/wolfman/foo". Корневой каталог "/", каталоги home и wolfman, a также файл fоо — это элементы каталогов, которые называются dentry. В операционной системе Unix каталоги представляют собой обычные файлы, которые просто содержат список файлов каталога. Так как каталог по отношению к виртуальной файловой системе — это файл, то с каталогами можно выполнять те же операции, что и с файлами.

Unix-подобные операционные системы отличают концепцию файла от любой информации об этом файле (права доступа, размер, владелец, время создания и т.д.). Последняя информация иногда называется метаданнымм файла (file metadata), т.е. данные о данных, и хранится отдельно от файлов в специальных структурах, которые называются индексами (inode). Это сокращенное название от index node (индексный узел), хотя в наши дни термин "inode" используется значительно чаще.

Вся указанная информация, а также связанная с ней информация о самой файловой системе хранится в суперблоке (superblock). Суперблок — это структура данных, которая содержит информацию о файловой системе в целом. Иногда эти общие данные называются метаданными файловой системы. Метаданные файловой системы содержат информацию об индивидуальных файлах и о файловой системе в целом.

Традиционно файловые системы ОС Unix реализуют эти понятия как структуры данных, которые определенным образом расположены на физических дисках. Например, информация о файлах хранится в индексе, в отдельном блоке диска, каталоги являются файлами, информация по управлению файловой системой хранится централизованно в суперблоке и т.д. Подсистема VFS операционной системы Linux рассчитана на работу с файловыми системами, в которых поддерживаются аналогичные концепции. Не Unix-подобные файловые системы, такие как FAT или NTFS, также работают в ОС Linux, однако их программный код должен обеспечить наличие аналогичных концепций. Например, если файловая система не поддерживает отдельные индексы файлов, то код должен построить в оперативной памяти структуры данных таким образом, чтобы казалось, что такая поддержка работает. Если файловая система рассматривает каталоги как объекты специальных типов, для VFS каталоги должны представляться как обычные файлы. Часто код не файловых систем не в стиле Unix требует выполнять некоторую дополнительную обработку чтобы уложиться в парадигму Unix и требования VFS. Такие файловые системы также поддерживаются, и обычно качество не особенно страдает.

 

Объекты VFS и их структуры данных

 

Виртуальная файловая система (VFS) объектно-ориентированна. Общая файловая модель представлена набором структур данных. Эти структуры данных очень похожи на объекты. Так как ядро программируется строго на языке С, то, при отсутствии возможностей прямой поддержки парадигм ООП в языке программирования, структуры данных представляются структурами языка С. Структуры содержат как указатели на элементы данных, так и указатели на функции, которые работают с этими данными.

Существуют следующие четыре основных типа объектов VFS.

• Объект суперблок (superblock), который представляет определенную смонтированную файловую систему.

• Объект файловый индекс (inode), который представляет определенный файл.

• Объект элемент каталога (dentry), который представляет определенный элемент каталога.

• Объект файл (file), который представляет открытый файл, связанный с процессом.

Следует обратить внимание, что поскольку подсистема VFS рассматривает каталоги как обычные файлы, то не существует специальных объектов для каталогов. Как рассказывалось ранее, объект dentry представляет компонент пути, который может содержать обычный файл. Другими словами, dentry — это не то же самое, что каталог, а каталог — это то же, что и файл. Все понятно?

Каждый из рассмотренных основных объектов содержит объект operations (операции). Эти объекты описывают методы, которые ядро может применять для основных объектов.

В частности, существуют следующие объекты операций.

• Объект super_operations (операции с суперблоком файловой системы) содержит методы, которые ядро может вызывать для определенной файловой системы, как, например, read_inode() или sync_fs().

• Объект inode_operations (операции с файловыми индексами) содержит методы, которые ядро может вызывать для определенного файла, как, например, create() или link().

• Объект dentry_operations (операции с элементами каталогов) содержит методы, которые ядро может вызывать для определенного элемента каталога, как, например, d_compare() или d_delete().

• Объект file_operations (операции с файлами) содержит методы, которые процесс может вызывать для открытого файла, как например, read() и write().

Объекты операций реализованы в виде структур, содержащих указатели на функции. Эти функции оперируют объектом, которому принадлежит объект операций. Для многих методов объект может унаследовать общую функцию, если для работы достаточно базовой функциональности. В противном случае каждая файловая система присваивает указателям адреса своих специальных методов.

И еще раз повторимся, что под объектами мы будем понимать структуры, которые явно не являются объектными типами (в отличие от языков программирования C++ и Java). Однако эти структуры представляют определенные экземпляры объектов, данные связанные с объектами, и методы, которые ими оперируют. Это практически то же, что и объектные типы.

 

Другие объекты подсистемы VFS

Структуры для VFS — это самая "любимая" вещь, и в этой подсистеме существуют не только рассмотренные структуры, но и еще некоторые. Каждая зарегистрированная файловая система представлена структурой file_system_type. Объекты этого типа описывают файловую систему и ее свойства. Более того, каждая точка монтирования представлена в виде структуры vfsmount. Эта структура содержит информацию о точке монтирования, такую как ее положение и флаги, с которыми выполнена операция монтирования.

И наконец, каждый процесс имеет три структуры, которые описывают файловую систему и файлы, связанные с процессом. Это структуры file_struct, fs_struct и namespace.

Далее в этой главе будут рассматриваться эти объекты и их роль в функционировании уровня VFS.

 

Объект superblock

 

Объект суперблок должен быть реализован для каждой файловой системы. Он используется для хранения информации, которая описывает определенную файловую систему. Этот объект обычно соответствует суперблоку (superblock) или управляющему блоку (control block) файловой системы, который хранится в специальном секторе диска (отсюда и имя объекта). Файловые системы, которые не располагаются на дисках (например, файловые системы в виртуальной памяти, как sysfs), генерируют информацию суперблока "на лету" и хранят в памяти.

Объект суперблок представляется с помощью структуры struct super_block, которая определена в файле . Она выглядит следующим образом (комментарии описывают назначение каждого поля).

struct super_block {

 struct list_head         s_list;           /* список всех суперблоков */

 dev_t                    s_dev;            /* идентификатор */

 unsigned long            s_blocksize;      /* размер блока в байтах */

 unsigned long            s_old_blocksize;  /* старый размер блока

                                               в байтах */

 unsigned char            s_blocksize_bits; /* размер блока в битах */

 unsigned char            s_dirt;           /* флаг того,

                                               что суперблок изменен */

 unsigned long long       s_maxbytes; /* максимальный размер файла */

 struct file_system_type  *s_type;          /* тип файловой системы */

 struct super_operations  *s_op;            /* операции суперблока */

 struct dquot_operations  *dq_op;           /* операции с квотами */

 struct quotactl_ops      *s_qcop; /* операции управления квотами */

 struct export_operations *s_export_op;     /* операции экспортирования */

 unsigned long            s_flags;          /* флаги монтирования */

 unsigned long            s_magic; /* магический номер файловой системы */

 struct dentry            *s_root; /* каталог, точка монтирования */

 struct rw_semaphore      s_umount;         /* семафор размонтирования */

 struct semaphore         s_lock;           /* семафор суперблока */

 int                      s_count; /* счетчик ссылок на суперблок */

 int                      s_syncing;        /* флаг синхронизации

                                               файловой системы */

 int                      s_need_sync_fs; /* флаг того, что файловая

                                       система еще не синхронизирована */

 atomic_t                 s_active;         /* счетчик активных ссылок */

 void                     *s_security;      /* модуль безопасности */

 struct list_head         s_dirty; /* список измененных индексов */

 struct list_head         s_io;             /* список обратной записи */

 struct hlist_head        s_anon;           /* анонимные элементы каталога

                                               для экспортирования */

 struct list_head         s_files;          /* список связанных файлов */

 struct block_device      *s_bdev;          /* соответствующий драйвер

                                               блочного устройства */

 struct list_head         s_instances;      /* список файловых систем

                                               данного типа */

 struct quota_info        s_dquot;          /* параметры квот */

 char                     s_id[32];         /* текстовое имя */

 void                     *s_fs_info;       /* специфическая информация

                                               файловой системы */

 struct semaphore         s_vfs_rename_sem; /* семафор переименования */

};

Код для создания, управления и ликвидации объектов суперблок находится в файле fs/super.c. Объект суперблок создается и инициализируется в функции alloc_super(). Эта функция вызывается при монтировании файловой системы, которая считывает суперблок файловой системы с диска и заполняет поля объекта суперблок.

 

Операции суперблока

Наиболее важный элемент суперблока — это поле s_op, которое является указателем на таблицу операций суперблока. Таблица операций суперблока представлена с помощью структуры struct super_operations, которая определена в файле . Она выглядит следующим образом.

struct super_operations {

 struct inode *(*alloc_inode)(struct super_block *sb);

 void (*destroy_inode)(struct inode*);

 void (*read_inode)(struct inode*);

 void (*dirty_inode)(struct inode*);

 void (*write_inode)(struct inode*, int);

 void (*put inode)(struct inode*);

 void (*drop_inode)(struct inode*);

 void (*delete_inode)(struct inode*);

 void (*put_super)(struct super_block*);

 void (*write_super)(struct super block*);

 int (*sync_fs)(struct super_block*, int);

 void (*write_super_lockfs)(struct super_block*);

 void (*unlockfs)(struct super_block*);

 int (*statfs)(struct super_block*, struct statfs*);

 int (*remount_fs)(struct super_block*, int*, char*);

 void (*clear_inode)(struct inode*);

 void (*umount_begin)(struct super block*);

 int (*show_options)(struct seq_file*, struct vfsmount*);

};

Каждое поле этой структуры представляет собой указатель на функцию, которая работает с объектом суперблок. Операции суперблока выполняют низкоуровневые действия с файловой системой и ее файловыми индексами.

Когда для файловой системы необходимо выполнить операции с суперблоком, то выполняется разыменование указателя на суперблок, и далее получается указатель на необходимый метод. Например, если файловой системе необходимо записать суперблок, то вызывается следующая функция.

sb->s_op->write_super(sb);

где параметр sb — это указатель на суперблок файловой системы. Следуя по указателю s_op, получаем таблицу операций суперблока и, наконец, необходимую функцию write_super(), которая вызывается непосредственно. Следует обратить внимание на то, что вызову функции write_super() необходимо передать указатель на суперблок в качестве параметра, несмотря на то что метод связан с суперблоком. Это происходит от того, что язык программирования С не объектно-ориентирован. В C++ аналогичный вызов может быть выполнен следующим образом.

sb.write_super();

В языке С нет простого способа получить указатель на объект, для которого вызван метод, поэтому его необходимо передавать явно.

Рассмотрим операции суперблока, которые описаны в структуре super_operations.

• struct inode* alloc_inode(struct super_block *sb) — эта функция создает и инициализирует новый объект файлового индекса, связанного с данным суперблоком.

• void destroy_inode(struct inode *inode) — эта функция уничтожает данный объект индекса файла.

• void read_inode(struct inode *inode) — эта функция считывает с диска файловый индекс с номером inode->i_ino и заполняет все остальные поля структуры данных индекса.

• void dirty_inode(struct inode *inode) — эта функция вызывается подсистемой VFS, когда в индекс вносятся изменения (dirty). Журналируемые файловые системы (как, например, ext3) используют эту функцию для обновления журнала.

• void write_inode(struct inode inode*, int wait) — эта функция записывает указанный индекс на диск. Параметр wait указывает, должна ли данная операция выполняться синхронно.

• void put_inode(struct inode *inode) — эта функция освобождает указанный индекс.

• void drop_inode(struct inode *inode) — эта функция вызывается подсистемой VFS, когда исчезает последняя ссылка на индекс. Обычные файловые системы Unix никогда не определяют эту функцию, в таком случае подсистема VFS просто удаляет индекс. Вызывающий код должен удерживать блокировку inode_lock.

• void delete_inode(struct inode *inode) — эта функция удаляет индекс файла с диска.

• void put_super(struct super_block *sb) — эта функция вызывается подсистемой VFS при размонтировании файловой системы, чтобы освободить указанный суперблок.

• void write_super(struct super_block *sb) — эта функция обновляет суперблок на диске данными из указанного суперблока. Подсистема VFS вызывает эту функцию для синхронизации измененного суперблока в памяти с данными суперблока на диске.

• int sync_fs(struct super_block *sb, int wait) — эта функция синхронизирует метаданные файловой системы с данными на диске. Параметр wait указывает, должна ли операция быть синхронной или асинхронной.

• void write_super_lockfs(struct super_block *sb) — эта функция предотвращает изменения файловой системы и затем обновляет данные суперблока на диске данными из указанного суперблока. Сейчас она используется диспетчером логических томов (LVM, Logical Volume Manager).

• void unlockfs(struct super_block *sb) — эта функция разблокирует файловую систему после выполнения функции write_super_lockfs().

• int statfs(struct super_block *sb, struct statfs *statfs) — эта функция вызывается подсистемой VFS для получения статистики файловой системы, Статистика указанной файловой системы записывается в структуру statfs.

• int remount_fs(struct super_block *sb, int *flags, char *data) — эта функция вызывается подсистемой VFS, когда файловая система монтируется с другими параметрами монтирования.

• void clear_inode(struct inode*) — эта функция вызывается подсистемой VFS для освобождения индекса и очистки всех страниц памяти, связанных с индексом.

• void umount_begin(struct super_block *sb) — эта функция вызывается подсистемой VFS для прерывания операции монтирования. Она используется сетевыми файловыми системами, такими как NFS.

Все рассмотренные функции вызываются подсистемой VFS в контексте процесса. Все они при необходимости могут блокироваться.

Некоторые из этих функций являются необязательными. Файловая система может установить их значения в структуре операций суперблока равными NULL. Если соответствующий указатель равен NULL, то подсистема VFS или вызывает общий вариант функции, или не происходит ничего, в зависимости от операции.

 

Объект inode

 

Объект inode содержит всю информацию, которая необходима ядру для манипуляций с файлами и каталогами. В файловых системах в стиле Unix вся информация просто считывается из дисковых индексов и помещается в объект inode подсистемы VFS. Если файловые системы не имеют индексов, то эту информацию необходимо получить из других дисковых структур.

Объект индекса файла представляется с помощью структуры struct inode, которая определена в файле . Эта структура с комментариями, описывающими назначение каждого поля, имеет следующий вид.

struct inode {

 struct hlist_node       i_hash;         /* хешированный список */

 struct list_head        i_list;         /* связанный список индексов */

 struct list_head        i_dentry; /* связанный список объектов dentry */

 unsigned long           i_ino;          /* номер индекса */

 atomic_t                i_count;        /* счетчик ссылок */

 umode_t                 i_mode;         /* права доступа */

 unsigned int            i_nlink;        /* количество жестких ссылок */

 uid_t                   i_uid; /* идентификатор пользователя-владельца */

 gid_t                   i_gid; /* идентификатор группы-владельца */

 kdev_t                  i_rdev;         /* связанное устройство */

 loff_t                  i_size;         /* размер файла в байтах */

 struct timespec         i_atime; /* время последнего доступа к файлу */

 struct timespec         i_mtime; /* время последнего изменения файла */

 struct timespec         i_ctime;        /* время изменения индекса */

 unsigned int            i_blkbits;      /* размер блока в битах */

 unsigned long           i_blksize;      /* размер блока в байтах */

 unsigned long           i_version;      /* номер версии */

 unsigned long           i_blocks;       /* размер файла в блоках */

 unsigned short          i_bytes; /* количество использованных байтов */

 spinlock_t              i_lock;         /* блокировка для защиты полей */

 struct rw_semaphore     i_alloc_sem     /* вложенные блокировки при

                                            захваченной i_sem */

 struct semaphore        i_sem;          /* семафор индекса */

 struct inode_operations *i_op;          /* таблица операций с индексом */

 struct file_operations  *i_fop;         /* файловые операции */

 struct super_block      *i_sb;          /* связанный суперблок */

 struct file_lock        *i_flock;       /* список блокировок файлов */

 struct address_space    *i_mapping;     /* соответствующее адресное

                                            пространство */

 struct address_space    i_data; /* адресное пространство устройства */

 struct dquot            *i_dquot[MAXQUOTAS]; /* дисковые квоты

                                                 для индекса */

 struct list_head        i_devices;      /* список блочных устройств */

 struct pipe_inode_info  *i_pipe;        /* информация конвейера */

 struct block_device     *i_bdev;        /* драйвер блочного устройства */

 unsigned long           i_dnotify_mask; /* события каталога */

 struct dnotify_struct   *i_dnotify; /* информация о событиях каталога */

 unsigned long           i_state;        /* флаги состояния */

 unsigned long           dirtied_when    /* время первого изменения */

 unsigned int            i_flags;        /* флаги файловой системы */

 unsigned char           i_sock;         /* сокет или нет? */

 atomic_t                i_writecount;   /* счетчик использования

                                            для записи */

 void                    *i_security;    /* модуль безопасности */

 __u32                   i_generation;   /* номер версии индекса */

 union {

  void *generic_ip; /* специфическая информация

                       файловой системы */

 } u;

};

Для каждого файла в системе существует представляющий его индекс (хотя объект файлового индекса создается в памяти только тогда, когда к файлу осуществляется доступ). Это справедливо и для специальных файлов, таких как файлы устройств или конвейеры. Следовательно, некоторые из полей структуры struct inode относятся к этим специальным файлам. Например, поле i_pipe указывает на структуру данных именованного конвейера. Если индекс не относится к именованному конвейеру, то это поле просто содержит значение NULL Другие поля, связанные со специальными файлами, — это i_devices, i_bdev, i_cdev.

Может оказаться, что та или иная файловая система не поддерживает тех свойств, которые присутствуют в объекте inode. Например, некоторые файловые системы не поддерживают такого атрибута, как время создания файла. В этом случае файловая система может реализовать это свойство как угодно. Например, поле i_ctime можно сделать нулевым или равным значению поля i_mtime.

 

Операции с файловыми индексами

Так же как и в случае операций суперблока, важным является поле inode_operations, в котором описаны функции файловой системы, которые могут быть вызваны подсистемой VFS для объекта файлового индекса. Как и для суперблока, операции с файловыми индексами могут быть вызваны следующим образом.

i->i_op->truncate(i);

где переменная i содержит указатель на определенный объект файлового индекса. В данном случае для индекса i выполняется операция truncate(), которая определена для файловой системы, в которой находится указанный файловый индекс i. Структура inode_operations определена в файле , как показано ниже.

struct inode_operations {

 int (*create)(struct inode*, struct dentry*, int);

 struct dentry* (*lookup)(struct inode*, struct dentry*);

 int (*link)(struct dentry*, struct inode*, struct dentry*);

 int (*unlink)(struct inode*, struct dentry*);

 int (*symlink)(struct inode*, struct dentry*, const char*);

 int (*mkdir)(struct inode*, struct dentry*, int);

 int (*rmdir)(struct inode*, struct dentry*);

 int (*mknod)(struct inode*, struct dentry*, int, dev_t);

 int (*rename)(struct inode*, struct dentry*,

  struct inode*, struct dentry*);

 int (*readlink)(struct dentry*, char*, int);

 int (*follow_link)(struct dentry*, struct nameidata*);

 int (*put_link)(struct dentry*, struct nameidata*);

 void (*truncate)(struct inode*);

 int (*permission)(struct inode*, int);

 int (*setattr)(struct dentry*, struct iattr*);

 int (*getattr)(struct vfsmount*, struct dentry*, struct kstat*);

 int (*setxattr)(struct dentry*, const char*,

 const void*, size_t, int);

 ssize_t (*getxattr)(struct dentry*, const char*, void*, size_t);

 ssize_t (*listxattr)(struct dentry*, char*, size_t);

 int (*removexattr)(struct dentry*, const char*);

};

Рассмотрим указанные операции более подробно.

• int create(struct inode *dir, struct dentry *dentry, int mode);

Эта функция вызывается подсистемой VFS из системных вызовов creat() и open() для создания нового файлового индекса, который имеет указанный режим доступа (mode) и связан с указанным элементом каталога (dentry).

• struct dentry* lookup(struct inode *dir, struct dentry *dentry);

Эта функция производит поиск файлового индекса в указанном каталоге. Файловый индекс должен соответствовать имени файла, хранящемуся в указанном объекте элемента каталога.

• int link(struct dentry *old_dentry, struct inode *dir,

  struct dentry *dentry);

Эта функция вызывается из системного вызова link() для создания жесткой ссылки (hard link) на файл, соответствующий элементу каталога old_dentry в каталоге dir. Новая ссылка должна иметь имя, которое хранится в указанном элементе каталога dentry.

• int unlink(struct inode *dir, struct dentry *dentry);

Эта функция вызывается из системного вызова unlink() для удаления файлового индекса, соответствующего элементу каталога dentry в каталоге dir.

• int symlink(struct inode *dir, struct dentry *dentry,

  const char *symname);

Эта функция вызывается из системного вызова symlink() для создания символьной ссылки с именем symname на файл, которому соответствует элемент каталога dentry в каталоге dir.

• int mkdir(struct inode *dir, struct dentry *dentry, int mode);

Эта функция вызывается из системного вызова mkdir() для создания нового каталога с указанным режимом доступа (mode).

• int rmdir(struct inode *dir, struct dentry *dentry);

Эта функция вызывается из системного вызова rmdir() для удаления каталога на который указывает элемент каталога dentry из каталога dir.

• int mknod(struct inode *dir, struct dentry *dentry,

  int mode, dev_t rdev);

Эта функция вызывается из системного вызова mknod() для создания специального файла (файла устройства, именованного конвейера или сокета), информация о котором хранится в параметре rdev. Файл должен быть создан в каталоге dir с именем, указанным в параметре dentry, и режимом доступа mode.

• int rename(struct inode *old_dir, struct dentry *old_dentry,

  struct inode *new_dir, struct dentry *new_dentry);

Эта функция вызывается подсистемой VFS для перемещения указанного элемента каталога old_dentry из каталога old_dir в каталог new_dir с новым именем, указанным в параметре new_dentry.

• int readlink(struct dentry *dentry, char *buffer, int buflen);

Эта функция вызывается из системного вызова readlink() для копирования не более buflen байт полного пути, связанного с символьной ссылкой, соответствующей указанному элементу каталога, в указанный буфер.

• int follow_link(struct dentry *dentry, struct nameidata *nd);

Эта функция вызывается подсистемой VFS для трансляции символьной ссылки в индекс файла, на который эта ссылка указывает. На ссылку указывает указатель dentry, а результат сохраняется в структуру nameidata, на которую указывает параметр nd.

• int put_link(struct dentry *dentry, struct nameidata* nd);

Эта функция вызывается подсистемой VFS после вызова функции followlink().

• void truncate(struct inode *inode);

Эта функция вызывается подсистемой VFS для изменения размера заданного файла. Перед вызовом поле i_size указанного индекса файла должно быть установлено в желаемое значение размера.

• int permission(struct inode *inode, int mask);

Эта функция проверяет, разрешен ли указанный режим доступа к файлу, на который ссылается объект inode. Функция должна возвращать нулевое значение, если доступ разрешен, и отрицательное значение кода ошибки в противном случае. Для большинства файловых систем данное поле устанавливается в значение NULL, и при этом используется общий метод VFS, который просто сравнивает биты поля режима доступа файлового индекса с указанной маской. Более сложные файловые системы, которые поддерживают списки контроля доступа (ACL), реализуют свой метод permission().

• int setattr(struct dentry *dentry, struct iattr *attr);

Эта функция вызывается функцией notify_change() для уведомления о том, что произошло "событие изменения" ("change event") после модификации индекса.

• int getattr(struct vfsmount *mnt, struct dentry *dentry,

  struct kstat *stat);

Эта функция вызывается подсистемой VFS при уведомлении, что индекс должен быть обновлен с диска.

• int setxattr(struct dentry *dentry, const char *name,

  const void *value, size_t size, int flags);

Эта функция вызывается подсистемой VFS для установки одного из расширенных атрибутов (extended attributes) с именем name в значение value для файла, соответствующего элементу каталога dentry.

• int getxattr(struct dentry *dentry, const char *name,

  void *value, size_t size);

Эта функция вызывается подсистемой VFS для копирования значения одного из расширенных атрибутов (extended attributes) с именем name в область памяти с указателем value.

• ssize_t listxattr(struct dentry *dentry, char *list, size_t size);

Эта функция должна копировать список всех атрибутов для указанного файла в буфер, соответствующий параметру list.

• int removexattr(struct dentry *dentry, const char *name);

Эта функция удаляет указанный атрибут для указанного файла.

 

Объект dentry

 

Как уже рассказывалось, подсистема VFS представляет каталоги так же, как и файлы. В имени пути /bin/vi, и элемент bin, и элемент vi — это файлы, только bin — это специальный файл, который является каталогом, a vi — это обычный файл. Объекты файловых индексов служат для представления обоих этих компонентов. Несмотря на такую полезную унификацию, подсистеме VFS также необходимо выполнять операции, специфичные для каталогов, такие как поиск компонента пути по его имени, проверка того, что указанный элемент пути существует, и переход на следующий компонент пути.

Для решения этой задачи в подсистеме VFS реализована концепция элемента каталога (directory entry или dentry). Объект dentry — это определенный компонент пути. В предыдущем примере компоненты /, bin и vi — это объекты элементов каталога. Первые два — это каталоги, а последний — обычный файл. Важным моментом является то, что все объекты dentry — это компоненты пути, включая и обычные файлы.

Элементы пути также могут включать в себя точки монтирования. В имени пути /mnt/cdrom/foo, компоненты /, mnt, cdrom и foo — это все объекты типа dentry. Подсистема VFS при выполнении операций с каталогами по необходимости конструирует объекты элементов каталога на лету.

Объекты типа dentry представлены с помощью структуры struct dentry и определены в файле . Эта структура с комментариями, которые определяют назначение каждого поля, имеет следующий вид.

struct dentry {

 atomic_t                 d_count;     /* счетчик использования */

 unsigned long            d_vfs_flags; /* флаги кэша объектов dentry */

 spinlock_t               d_lock; /* блокировка данного объекта dentry */

 struct inode             *d_inode; /* соответствующий файловый индекс */

 struct list_head         d_lru; /* список неиспользованных объектов */

 struct list_head         d_child;     /* список объектов у родительского

                                          экземпляра */

 struct list_head         d_subdirs;   /* подкаталоги */

 struct list_head         d_alias;     /* список альтернативных (alias)

                                          индексов */

 unsigned long            d_time;      /* время проверки правильности */

 struct dentry_operations *d_op;       /* таблица операций с элементом

                                          каталога */

 struct super_block       *d_sb;       /* связанный суперблок */

 unsigned int             d_flags;     /* флаги элемента каталога */

 int                      d_mounted;   /* является ли объект точкой

                                           монтирования */

 void                     *d_fsdata;   /* специфические данные

                                          файловой системы */

 struct rcu_head          d_rcu; /* блокировки RCU (read-copy update) */

 struct dcookie_struct    *d_cookie;    /* cookie-идентификатор */

 struct dentry            *d_parent;    /* объект dentry

                                           родительского каталога */

 struct qstr              d_name;       /* имя dentry */

 struct hlist_node        d_hash;       /* список хеширования */

 struct hlist_head        *d_bucket;    /* сегмент хеш-таблицы */

 unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* короткое имя файла */

};

В отличие от предыдущих двух объектов, объект dentry не соответствует какой бы то ни было структуре данных на жестком диске. Подсистема VSF создает эти объекты на лету на основании строкового представления имени пути. Поскольку объекты элементов каталога не хранятся физически на дисках, то в структуре struct dentry нет никаких флагов, которые указывают на то, изменен ли объект (т.е. должен ли он быть записан назад на диск).

 

Состояние элементов каталога

Действительный объект элемента каталога, может быть в одном из трех состояний: используемый (used), неиспользуемый (unused) и негативный (negative).

Используемый объект соответствует существующему файловому индексу (т.е. поле d_inode указывает на связанный объект типа mode) и используется один или более раз (т.е. значение поля d_count — положительное число). Используемый элемент каталога используется подсистемой VFS, а также указывает на существующие данные, поэтому не может быть удален.

Неиспользуемый объект типа dentry соответствует существующему объекту inode (поле d_inode указывает на объект файлового индекса), но подсистема VFS в данный момент не использует этот элемент каталога (поле d_count содержит нулевое значение). Так как элемент каталога указывает на существующий объект, то он сохраняется на случай, если вдруг окажется нужным. Если объект не ликвидировать преждевременно, то его и не нужно будет создавать заново, если вдруг он понадобится в будущем, и поиск по имени пути пройдет быстрее. Когда же появляется необходимость освободить память, то такой объект элемента каталога может быть удален, потому что он никем не используется.

Негативный объект dentry не связан с существующим файловым индексом (поле d_inode равно значению NULL), потому что или файловый индекс был удален, или соответствующий элемент пути никогда не существовал. Такие объекты элементов каталогов сохраняются, чтобы в будущем поиск по имени пути проходил быстрее. Хотя такие объекты dentry и полезны, но они при необходимости могут уничтожаться, поскольку никто их на самом деле не использует.

Объект dentry может быть освобожден, оставаясь в слябовом кэше объектов, как обсуждалось в предыдущей главе. В таком случае на этот объект нет ссылок ни в коде VFS, ни в коде файловых систем.

 

Кэш объектов dentry

После того как подсистема VFS преодолела все трудности, связанные с переводом всех элементов пути в объекты элементов каталогов, и был достигнут конец пути, то было бы достаточно расточительным выбрасывать на ветер всю проделанную работу. Ядро кэширует объекты в кэше элементов каталога, который называют dcache.

Кэш объектов dentry состоит из трех частей.

• Список "используемых" объектов dentry, которые связаны с определенным файловым индексом (поле i_dentry объекта inode). Поскольку указанный файловый индекс может иметь несколько ссылок, то ему может соответствовать несколько объектов dentry, а следовательно используется связанный список.

• Двухсвязный список неиспользуемых и негативных объектов dentry "с наиболее поздним использованием" (last recently used, LRU). Вставки элементов в этот список отсортированы по времени, поэтому элементы, которые находятся в начале списка, — самые новые. Когда ядро должно удалить элементы каталогов для освобождения памяти, то эти элементы берутся из конца списка, потому что там находятся элементы, которые использовались наиболее давно и для которых меньше шансов, что они понадобятся в ближайшем будущем.

• Хеш-таблица и хеш-функция, которые позволяют быстро преобразовать заданный путь в объект dentry.

Указанная хеш-таблица представлена с помощью массива dentry_hashtable. Каждый элемент массива — это указатель на список тех объектов dentry, которые соответствуют одному ключу. Размер этого массива зависит от объема физической памяти в системе.

Значение ключа определяется функцией d_hash(), что позволяет для каждой файловой системы реализовать свою хеш-функцию.

Поиск в хеш-таблице выполняется с помощью функции d_lookup(). Если в кэше dcache найден соответствующий объект, то это значение возвращается. В случае ошибки возвращается значение NULL.

В качестве примера рассмотрим редактирование файла исходного кода в вашем домашнем каталоге, /home/dracula/src/fоо.с. Каждый раз, когда производится доступ к этому файлу (например, при первом открытии, при последующей записи, при компиляции и так далее), подсистема VFS должна пройти через псе элементы каталогов в соответствии с путем к файлу: /, home, dracula, src и, наконец, foo.c. Для того чтобы каждый раз при доступе к этому (и любому другому) имени пути избежать выполнения данной операции, которая требует довольно больших затрат времени, подсистема VFS вначале может попытаться найти это имя пути в dentry-кэше. Если поиск проходит успешно, то необходимый конечный элемент каталога нужного пути получается без особых усилий. Если же данного элемента каталога нет в dentry-кэше, то подсистема VFS должна самостоятельно отследить путь. После завершения поиска найденные объекты dentry помещаются в кэш dcache, чтобы ускорить поиск в будущем.

Кэш dcache также является интерфейсом к кэшу файловых индексов icache. Объекты inode связаны с объектами dentry, поскольку объект dentry поддерживает положительное значение счетчика использования для связанного с ним индекса. Это в свою очередь позволяет объектам dentry удерживать связанные с ними объекты mode в памяти. Иными словами, если закэширован элемент каталога, то соответственно оказывается закэшированным и соответствующий ему файловый индекс. Следовательно, если поиск в кэше для некоторого имени пути прошел успешно, то соответствующие файловые индексы уже закэшированы в памяти.

 

Операции с элементами каталогов

Структура dentry_operations содержит методы, которые подсистема VFS может вызывать для элементов каталогов определенной файловой системы. Эта структура определена в файле следующим образом.

struct dentry_operations {

 int (*d_revalidate)(struct dentry*, int);

 int (*d_hash)(struct dentry*, struct qstr*);

 int (*d_compare)(struct dentry*, struct qstr*, struct qstr*);

 int (*d_delete)(struct dentry*);

 void (*d_release)(struct dentry*);

 void (*d_iput)(struct dentry*, struct inode*);

};

Методы служат для следующих целей

• int d_revalidate(struct dentry *dentry, int flags);

Эта функция определяет, является ли указанный объект элемента каталога действительным. Подсистема VFS вызывает эту функцию, когда она пытается использовать объект dentry из кэша dcache. Для большинства файловых систем этот метод установлен в значение NULL, потому что объекты dentry, которые находятся в кэше, всегда действительны.

• int d_hash(struct dentry *dentry, struct qstr *name);

Эта функция создает значение хеш-ключа на основании указанного объекта dentry. Подсистема VFS вызывает эту функцию всякий раз, когда добавляет объект элемента каталога в хеш-таблицу.

• int d_compare(struct dentry *dentry,

  struct qstr *name1, struct qstr *name2);

Эта функция вызывается подсистемой VFS для сравнения двух имен файлов name1 и name2. Большинство файловых систем используют умолчание VFS, которое соответствует простому сравнению двух строк. Для некоторых файловых систем, таких как FAT, не достаточно простого сравнения строк. Файловая система FAT не чувствительна к регистру символов в именах файлов, поэтому появляется необходимость в реализации функции, которая при сравнении не учитывает регистр символов. Эта функция вызывается при захваченной блокировке dcache_lock.

• int d_delete(struct dentry *dentry);

Эта функция вызывается подсистемой VFS, когда количество ссылок d_count указанного объекта dentry становится равным пулю. Функция вызывается при захваченной блокировке dcache_lock.

• void d_release(struct dentry *dentry);

Эта функция вызывается подсистемой VFS, когда она собирается освободить указанный объект dentry. По умолчанию данная функция не выполняет никаких действий.

• void d_iput(struct dentry *dentry, struct inode *inode);

Эта функция вызывается подсистемой VFS, когда элемент каталога теряет связь со своим файловым индексом (например, когда этот элемент каталога удаляется с диска). По умолчанию подсистема VFS просто вызывает функцию iput(), чтобы освободить соответствующий объект inode. Если файловая система переопределяет эту функцию, то она также должна вызывать функцию iput() в дополнение к специфичной для файловой системы работе.

 

Объект file

 

Последним из основных объектов подсистемы VFS рассмотрим объект файла. Объект File используется для представления файлов, которые открыты процессом. Когда мы думаем о подсистеме VFS с точки зрения пространства пользователя, то объект файла — это то, что первое приходит в голову. Процессы непосредственно работают с файлами, а не с суперблоками, индексами или элементами каталогов. Не удивительно, что информация, которая содержится в объекте file, наиболее привычна (такие данные, как режим доступа или текущее смещение), а файловые операции очень похожи на знакомые системные вызовы, такие как read() и write().

Объект файла — это представление открытого файла, которое хранится в оперативной памяти. Объект (а не сам файл) создается в ответ на системный вызов open() и уничтожается в результате системного вызова close(). Все вызовы, связанные с файлом, на самом деле являются методами, которые определены в таблице операций с файлом. Так как несколько процессов могут одновременно открыть и использовать один и тот же файл, то для одного файла может существовать несколько объектов file. Файловый объект просто представляет открытый файл с точки зрения процесса. Этот объект содержит указатель на соответствующий элемент каталога (который, в свою очередь, указывает на файловый индекс), представляющий открытый файл. Соответствующие объекты inode и dentry, конечно, являются уникальными.

Файловый объект представляется с помощью структуры struct file, которая определена в файле . Рассмотрим поля этой структуры с комментариями, которые описывают назначение каждого поля.

struct file {

 struct list_head       f_list;      /* список объектов file */

 struct dentry          *f_dentry;   /* связанный объект dentry */

 struct vfsmount        *f_vfsmnt;   /* связанная смонтированная

                                        файловая система */

 struct file_operations *f_op;       /* таблица файловых операций */

 atomic_t               f_count;     /* счетчик ссылок на этот объект */

 unsigned int           f_flags;     /* флаги, указанные

                                        при вызове функции open */

 mode_t                 f_mode;      /* режим доступа к файлу */

 loff_t                 f_pos;       /* смещение в файле

                                        (file pointer, offset) */

 struct fown_struct     f_owner; /* информация о владельце для обработки

                                    сигналов */

 unsigned int           f_uid; /* идентификатор пользователя владельца, UID */

 unsigned int           f_gid; /* идентификатор группы владельца, GID */

 int                    f_error;     /* код ошибки */

 struct file_ra_state   f_ra; /* состояние предварительного считывания */

 unsigned long          f_version;   /* номер версии */

 void                   *f_security; /* модуль безопасности */

 void                   *private_data; /* привязка для

                                          драйвера терминала */

 struct list_head       f_ep_links;  /* список ссылок eventpoll

                                        (опрос событий) */

 spinlock_t             f_ep_lock;   /* блокировка eventpoll */

 struct address_space   *f_mapping;  /* отображение в страничном кэше */

};

По аналогии с объектом элемента каталога объект файла на самом деле не соответствует никакой структуре, которая хранится на жестком диске. Поэтому в этой структуре нет никакого флага, который бы указывал, что объект изменен (dirty) и требует обратной записи на диск. Объект file указывает на связанный с ним объект dentry с помощью указателя f_dentry. Объект dentry в свою очередь содержит указатель на связанный с ним индекс файла, который содержит информацию о том, изменен ли файл.

 

Файловые операции

Как и для других объектов подсистемы VFS, таблица файловых операций является важной структурой. Операции, связанные со структурой struct file, — это знакомые системные вызовы, составляющие основу системных вызовов ОС Unix.

Методы работы с файловым объектом хранятся в структуре file_operations и определены в файле следующим образом.

struct file_operations {

 struct module *owner;

 loff_t (*llseek)(struct file*, loff_t, int);

 ssize_t (*read)(struct file*, char*, size_t, loff_t*);

 ssize_t (*aio_read)(struct kiocb*, char*, size_t, loff_t);

 ssize_t (*write)(struct file*, const char*, size_t, loff_t*);

 ssize_t (*aio_write)(struct kiocb*, const char*, size_t, loff_t);

 int (*readdir)(struct file*, void*, filldir_t);

 unsigned int (*poll)(struct file*, struct poll_table_struct*);

 int (*ioctl)(struct inode*, struct file*, unsigned int, unsigned long);

 int (*mmap)(struct file*, struct vm_area_struct*);

 int (*open)(struct inode*, struct file*);

 int (*flush)(struct file*);

 int (*release)(struct inode*, struct file*);

 int (*fsync)(struct file*, struct dentry*, int);

 int (*aio_fsync)(struct kiocb*, int);

 int (*fasync)(int, struct file*, int);

 int (*lock)(struct file*, int, struct file_lock*);

 ssize_t (*readv)(struct file*, const struct iovec*,

  unsigned long, loff_t*);

 ssize_t (*writev)(struct file*, const struct iovec*,

  unsigned long, loff_t*);

 ssize_t (*sendfile)(struct file*, loff_t*, size_t,

  read_actor_t, void*);

 ssize_t (*sendpage)(struct file*, struct page*, int,

  size_t, loff_t*, int);

 unsigned long (*get_unmapped_area)(struct file*, unsigned long,

  unsigned long, unsigned long, unsigned long);

 int (*check_flags)(int flags);

 int (*dir_notify)(struct file *filp, unsigned long arg);

 int (*flock)(struct file *filp, int cmd, struct file_lock *fl);

};

Файловые системы могут реализовать уникальную функцию для каждой из этих операций или использовать общий существующий метод. Общие методы нормально работают для обычных Unix-подобных файловых систем. Разработчики файловых систем не обязаны реализовать все эти функции, хотя основные методы должны быть реализованы. Если какой-либо метод не представляет интереса, то его можно установить в значение NULL.

Рассмотрим каждую операцию подробнее.

• loff_t llseek(struct file *file, loff_t offset, int origin);

Эта функция устанавливает значения указателя текущей позиции в файле (file pointer) в заданное значение параметра offset. Функция вызывается из системного вызова lseek().

• ssize_t read(struct file *file,

  char *buf, size_t count, loff_t* offset);

Эта функция считывает count байт данных из указанного файла, начиная с позиции, заданной параметром offset, в буфер памяти, на который указывает параметр buf. После этого значение указателя текущей позиции в файле должно быть обновлено. Данная функция вызывается из системного вызова read().

• ssize_t aio_read(struct kiocb *iocb,

  char *buf, size_t count, loff_t offset);

Эта функция запускает асинхронную операцию считывания count байт данных из файла, который описывается параметром iocb, в буфер памяти, описанный параметром buf. Эта функция вызывается из системного вызова aio_read().

• ssize_t write(struct file *file,

  const char *buf, size_t count, loff_t* offset);

Эта функция записывает count байт данных в указанный файл, начиная с позиции offset. Данная функция вызывается из системного вызова write().

• ssize_t aio_write(struct kiocb *iocb,

  const char *buf, size_t count, loff_t offset);

Эта функция запускает асинхронную операцию записи count байт данных в файл, описываемый параметром iocb, из буфера памяти, на который указывает параметр buf. Данная функция вызывается из системного вызова aio_write().

• int readdir(struct file *file, void *dirent, filldir_t filldir);

Эта функция возвращает следующий элемент из списка содержимого каталога. Данная функция вызывается из системного вызова readdir().

• unsigned int poll(struct file *file,

  struct poll_table_struct *poll_table);

Эта функция переводит вызывающий процесс в состояние ожидания для ожидания действий, которые производятся с указанным файлом. Она вызывается из системного вызова poll().

• int ioctl(struct inode *inode,

  struct file *file, unsigned int cmd, signed long arg);

Эта функция используется для того, чтобы отправлять устройствам пары значений команда/аргумент. Функция используется, когда открытый файл — это специальный файл устройства. Данная функция вызывается из системного вызова ioctl().

• int mmap(struct file *file, struct vm_area_struct *vma);

Эта функция отображает указанный файл на область памяти в указанном адресном пространстве и вызывается из системного вызова mmap().

• int open(struct inode *inode, struct file *file);

Эта функция создает новый файловый объект и связывает его с указанным файловым индексом. Она вызывается из системного вызова open().

• int flush(struct file *file);

Эта функция вызывается подсистемой VFS, когда уменьшается счетчик ссылок на открытый файл. Назначение данной функции зависит от файловой системы.

• int release(struct inode *inode, struct file *file);

Эта функция вызывается подсистемой VFS, когда исчезает последняя ссылка на файл, например, когда последний процесс, который использовал соответствующий файловый дескриптор, вызывает функцию close() или завершается. Назначение этой функции также зависит от файловой системы.

• int fsync(struct file *file,

  struct dentry *dentry, int datasync);

Эта функция вызывается из системного вызова fsync() для записи на диск всех закэшированных данных файла.

• int aio_fsync(struct kiocb *iocb, int datasync);

Эта функция вызывается из системного вызова aio_fsync() для записи на диск всех закэшированных данных файла, связанного с параметром iocb.

• int fasync(int fd, struct file *file, int on);

Эта функция разрешает или запрещает отправку сигнала для уведомлении о событиях при асинхронном вводе-выводе.

• int lock(struct file *file, int cmd, struct file_lock *lock);

Эта функция управляет файловыми блокировками для данного файла.

• ssize_t readv(struct file *file,

  const struct iovec *vector, unsigned long count, loff_t* offset);

Эта функция вызывается из системного вызова readv() для считывания данных из указанного файла в count буферов, которые описываются параметром vector. После этого указатель текущей позиции файла должен быть соответственным образом увеличен.

• ssize_t writev(struct file *file,

  const struct iovec *vector, unsigned long count, loff_t *offset);

Эта функция вызывается из системного вызова writev() для записи в указанный файл буферов, описанных параметром vector; количество буферов равно count. После этого должно быть соответственным образом увеличено значение текущей позиции в файле.

• ssize_t sendfile(struct file *file,

  loff_t *of fset, size_t size, read_actor_t actor, void *target);

Эта функция вызывается из системного вызова sendfile() для копирования данных из одного файла в другой. Она выполняет операцию копирования исключительно в режиме ядра и позволяет избежать дополнительного копирования данных в пространство пользователя.

• ssize_t sendpage(struct file *file,

  struct page *page, int offset, size_t size,

  loff_t *pos, int more);

Эта функция используется для отправки данных из одного файла в другой.

• unsigned long get_unmapped_area(struct file*file,

  unsigned long addr, unsigned long len, unsigned long offset,

  unsigned long flags);

Эта функция получает неиспользуемое пространство адресов для отображения данного файла.

• int check_flags(int flags);

Эта функция используется для проверки корректности флагов, которые передаются в системный вызов fcntl(), при использовании команды SETFL. Как и в случае многих операций подсистемы VFS, для файловой системы нет необходимости реализовать функцию check_flags(). Сейчас это сделано только для файловой системы NFS. Эта функция позволяет файловой системе ограничить некорректные значения флагов команды SETFL в обобщенном системном вызове fcntl(). Для файловой системы NFS не разрешается использовать комбинацию флагов O_APPEND и O_DIRECT.

• int flock(struct file *filp, int cmd, struct file_lock *fl);

Эта функция используется для реализации системного вызова flock(), который служит для выполнения рекомендованных блокировок.

 

Структуры данных, связанные с файловыми системами

В дополнение к фундаментальным объектам подсистемы VFS, ядро использует и другие стандартные структуры данных для управления данными, связанными с файловыми системами. Первый объект используется для описания конкретного типа файловой системы, как, например, ext3 или XFS. Вторая структура данных используется для описания каждого экземпляра смонтированной файловой системы.

Поскольку операционная система Linux поддерживает множество файловых систем, то ядро должно иметь специальную структуру для описания возможностей и поведения каждой файловой системы.

struct file_system_type {

 const char      *name;     /* название файловой системы */

 struct subsystem subsys;   /* объект подсистемы sysfs */

 int              fs_flags; /* флаги типа файловой системы */

 /* следующая функция используется для считывания суперблока с диска */

 struct super_block*(*get_sb)(

  struct file_system_type*, int, char*, void*);

 /* эта функция используется для прекращения доступа к суперблоку */

 void (*kill_sb)(struct super_block*);

 struct module     *owner;      /* соответствующий модуль (если есть) */

 struct file_system_type *next; /* следующая файловая система в списке */

 struct list_head   fs_supers;  /* список объектов типа суперблок */

};

Функция get_sb() служит для считывания суперблока с диска и заполнения объекта суперблока соответствующими данными при монтировании файловой системы. Остальные параметры описывают свойства файловой системы.

Для каждого типа файловой системы существует только одна структура file_system_type, независимо от того, сколько таких файловых систем смонтировано и смонтирован ли хотя бы один экземпляр соответствующей файловой системы.

Значительно интереснее становится, когда файловая система монтируется, при этом создается структура vfsmount. Эта структура используется для представления конкретного экземпляра файловой системы, или, другими словами, точки монтирования.

Структура vfsmount определена в файле следующим образом.

struct vfsmount {

 struct list_head   mnt_hash;       /* список хеш-таблицы */

 struct vfsmount   *mnt_parent;     /* родительская файловая система */

 struct dentry     *mnt_mountpoint; /* объект элемента каталога

                                       точки монтирования */

 struct dentry     *mnt_root;       /* объект элемента каталога корня

                                        данной файловой системы */

 struct super_block *mnt_sb; /* суперблок данной файловой системы */

 struct list_head   mnt_mounts;     /* список файловых систем,

                                       смонтированных к данной */

 struct list_head   mnt_child;      /* потомки, связанные с родителем */

 atomic_t           mnt_count;      /* счетчик использования */

 int                mnt_flags;      /* флаги монтирования */

 char               *mnt_devname;   /* имя смонтированного устройства */

 struct list_head   mnt_list;       /* список дескрипторов */

 struct list_head   mnt_fslinkk;    /* истекший список, специфичный

                                       для файловой системы */

 struct namespace   *mnt_namespace; /* связанное пространство имен */

};

Самая сложная задача — это поддержание списка всех точек монтирования и взаимоотношений между данной файловой системой и другими точками монтирования. Эта информация хранится в различных связанных списках структуры vfsmount.

Структура vfsmount также содержит поле mnt_flags. В табл. 12.1 приведен список стандартных флагов монтирования.

Таблица 12.1. Список стандартных флагов монтирования

Флаг Описание
MNT_NOSUID Запрещает использование флагов setuid и setgid для бинарных файлов на файловой системе
MNT_NODEV Запрещает доступ к файлам устройств на файловой системе
MNT_NOEXEC Запрещает выполнение программ на файловой системе

Эти флаги полезны, в основном, для сменных носителей, которым администратор не доверяет.

 

Структуры данных, связанные с процессом

Каждый процесс в системе имеет свои открытые файлы, корневую файловую систем); текущий рабочий каталог, точки монтирования и т.д. Следующие три структуры данных связывают вместе подсистему VFS и процессы, которые выполняются в системе. Это структуры files_struct, fs_struct и namespace.

Структура files_struct определена в файле . Адрес этой структуры хранится в поле files дескриптора процесса. В данной структуре хранится вся информация процесса об открытых файлах и файловых дескрипторах. Эта структура, с комментариями, имеет следующий вид.

struct files_struct {

 atomic_t    count;              /* счетчик ссылок на данную структуру */

 spinlock_t  file_lock; /* блокировка для защиты данной структуры */

 int         max_fds; /* максимальное количество файловых объектов */

 int         max_fdset;          /* максимальное количество

                                    файловых дескрипторов */

 int         next_fd; /* номер следующего файлового дескриптора */

 struct file **fd;               /* массив всех файловых объектов */

 fd_set      *close on exec;     /* файловые дескрипторы, которые должны

                                    закрываться при вызове exec() */

 fd_set      *open_fds; /* указатель на дескрипторы открытых файлов */

 fd_set      close_on_exec init; /* первоначальные файлы для закрытия

                                    при вызове exec() */

 fd_set       open_fds_init;     /* первоначальный набор

                                    файловых дескрипторов */

 struct file *fd_array[NR_OPEN_DEFAULT]; /* массив файловых объектов */

};

Массив fd указывает на список открытых файловых объектов. По умолчанию это массив fd_array. Так как по умолчанию значение константы NR_OPEN_DEFAULT равно 32, то это соответствует 32 файловым объектам. Если процесс открывает больше 32 файловых объектов, то ядро выделяет новый массив и присваивает полю fd указатель на него. При таком подходе доступ к небольшому количеству файловых объектов осуществляется быстро, потому что они хранятся в статическом массиве. В случае, когда процесс открывает аномально большое количество файлов, ядро может создать новый массив. Если большинство процессов в системе открывает больше 32 файлов, то для получения оптимальной производительности администратор может увеличить значение константы NR_OPEN_DEFAULT с помощью директивы препроцессора. Следующая структура данных, связанная с процессом, — это структура fs_struct, которая содержит информацию, связанную с процессом, и на которую указывает поле fs дескриптора процесса. Эта структура определена в файле и имеет следующий вид с поясняющими комментариями.

struct fs_struct {

 atomic_t       count;        /* счетчик ссылок на структуру */

 rwlock_t       lock;         /* блокировка для защиты структуры */

 int             umask;       /* права доступа к файлу, используемые

                                 по умолчанию */

 struct dentry   *root;       /* объект dentry корневого каталога */

 struct dentry   *pwd;        /* объект dentry

                                 текущего рабочего каталога */

 struct dentry   *allroot;    /* объект dentry альтернативного корня */

 struct vfsmount *rootmnt;    /* объект монтирования корневого каталога */

 struct vfsmount *pwdmnt;     /* объект монтирования

                                 текущего рабочего каталога */

 struct vfsmount *altrootmnt; /* объект монтирования

                                 альтернативного корня */

};

Эта структура содержит текущий рабочий каталог и корневой каталог данного процесса.

Третья, и последняя, структура — это структура namespace, которая определена в файле и на экземпляр которой указывает поле namespace дескриптора процесса. Пространства имен, индивидуальные для каждого процесса, были введены в ядрах Linux серии 2.4. Это позволило создать для каждого процесса уникальное представление о смонтированных файловых системах. Иными словами, процесс может иметь не только уникальный корневой каталог, но и полностью уникальную иерархию смонтированных файловых систем, если это необходимо. Как обычно, ниже приведена соответствующая структура данных с комментариями.

struct namespace {

 atomic_t            count; /* счетчик ссылок на структуру */

 struct vfsmount     *root; /* объект монтирования корневого каталога */

 struct list_head    list;  /* список точек монтирования */

 struct rw_semaphore sem;   /* семафор для защиты пространства имен */

};

Поле list представляет собой двухсвязный список смонтированных файловых систем, которые составляют пространство имен.

Каждый дескриптор процесса имеет связанные с ним рассмотренные структуры данных. Для большинства процессов их дескриптор процесса указывает на уникальную структуру files_struct и структуру fs_struct. Однако для процессов, созданных с флагами CLONE_FILES и CLONE_FS, эти структуры являются совместно используемыми. Отсюда следует, что несколько дескрипторов процессов могут указывать на одну и ту же структуру files_struct, или структуру fs_struct. Поле count каждой структуры содержит счетчик использования, что предотвращает уничтожение структуры данных, когда ее использует хотя бы один процесс.

Структура namespace используется несколько по-другому. По умолчанию вес процессы совместно используют одно пространство имен (и соответственно одну иерархию файловых систем). Только когда для системного вызова clone() указан флаг CLONE_NEWNS, для процесса создается уникальная копия пространства имен. Поскольку для большинства процессов этот флаг не указывается, процессы обычно наследуют пространство имен родительского процесса. Следовательно, для большинства систем существует только одно пространство имен.

 

Файловые системы в операционной системе Linux

Операционная система Linux поддерживает большой набор файловых систем, от "родных" ext2 и ext3 до сетевых файловых систем, таких как NFS или Coda. Сейчас в официальном ядре ОС Linux поддерживается более 50 файловых систем. Уровень VFS обеспечивает все эти разнообразные файловые системы общей базой для их реализации и общим интерфейсом для работы со стандартными системными вызовами. Следовательно, уровень виртуальной файловой системы позволяет четким образом реализовать поддержку новых файловых систем в операционной системе Linux, a также дает возможность работать с этими файловыми системами с помощью стандартных системных вызовов Unix.

В этой главе было описано назначение подсистемы VFS и рассмотрены соответствующие структуры данных, включая такие важные объекты, как inode, dentry и superblock. В главе 12, "Виртуальная файловая система", будет рассказано о том, как данные физически поступают на файловые системы.