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

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

VFS. Виртуальная файловая система. Часть 2
Общий краткий обзорВиртуальная файловая система (VFS) - это подсистема в Линуксе, прослойка, благодаря которой системные вызовы, такие как open, read, write работают одинаково, независимо от того, как реализована конкретная файловая система, будь то ФС на жёстком диске, на носителе (CD, DVD, дискеты и т. д.), в оперативной памяти, сетевая ФС и так далее. VFS определяет интерфейс, который должны реализовать конкретные файловые системы, чтобы работать в Линуксе. Благодаря такой обобщённости, казалось бы разные файловые системы могут сосуществовать вместе. К примеру, в Линуксе всегда существует корневая ФС типа rootfs ("/"), а в её поддиректории /home может быть смонтирована ФС уже другого типа: ext(2,3,4), reiserfs, nfs да что угодно. А в /home/folder смонтирована ФС уже третьего типа и так далее. Тем не менее пользователь способен совершенно спокойно работать в любой из этих директорий/поддиректорий, совершенно одинаково, используя одни и те же пользовательские программы, такие как rm, mkdir, touch.
Как я уже сказал, VFS определяет базовый набор интерфейсов и структур, и такой набор вы найдёте в любой линуксовой файловой системе. При этом оговорюсь, что каждая ФС не обязана реализовывать абсолютно всё, это не interface в Java, у которого мы должны все методы реализовывать. Если ФС что-то конкретное не реализует, то оно просто не будет работать или Линукс применит реализацию по умолчанию, если она есть. Всю цепочку работы рассмотрим на примере:
Вызываем функцию
  1. write(f, &buf, len);
Она записывает в файл, представленный дескриптором f, данные длинной len из буфера buf. Обратите внимание, что мы не знаем, в какой файл мы пишем: может это файл на жёстком диске, а может файл на другом компьютере в локальной сети, а может файл в оперативной памяти.
Эта функция в свою очередь дёрнет системный вызов ядра sys_write.
Этот системный вызов по дескриптору определит, к какому типу файловой системы принадлежит файл и поищет уже по этому типу реализацию записи в файл для этой конкретной файловой системы.
Цепочка работы происходит так:
screen.pngОсновные структурыВиртуальная файловая система определяет несколько абстракций: собственно сам файл (file), элемент каталога (dentry), индексный узел, (inode) и суперблок.
file - представляет собой открытый файл, связанный с процессом. inode - представляет определённый файл с его метаданными: владелец, дата создания, права доступа и т. д. dentry - представляет элемент каталога (не сам каталог, каталог в линуксе - обычный файл с необычным битом в свойствах :) ). superblock - структура, определяющая смонтированную файловую систему в целом. Каждый из этих объектов имеет вектор операций, который можно производить над объектом, это структуры: file_operations, inode_operations, dentry_operations и super_operations. Каждая из этих структур содержит указатели на функции, то есть операции, которые можно совершать над объектом. В каком-то смысле это ООП-шный стиль, который возможно сделать на C. Пример: Если проявить недюжинную фантазию и представить, что Линукс написан на Java, и у нас смонтирована некая файловая система с суперблоком s, то для синхронизации ФС мы бы написали что-то вроде:
  1. s.syncFS()
Но поскольку у нас С, то код будет выглядеть вот так:
  1. s->s_op->sync_fs(s);
Где s_op - это и есть вектор операций над суперблоком, который находится в структуре super_block.
Кроме перечисленных основных структур есть ещё некоторые. file_system_type - которая описывает тип файловой системы. Не путайте её с суперблоком. Если у вас на машине смонтирована пара ФС ext4, то тип ФС будет один, а суперблоков - несколько. Так же есть структура vfsmount, описывающая точку монтирования. Далее рассмотрим каждую структуру поподробнее.
Тип файловой системы и точки монтированияОбъект используется для описания конкретного типа файловой системы, как, например, ext3. Поскольку Линукс поддерживает множество файловых систем, то ядро должно иметь специальную структуру для описания возможностей и
поведения каждой файловой системы.
  1. struct file_system_type {
  2.     const char *name;
  3.     int         fs_flags;
  4. #define FS_REQUIRES_DEV     1
  5. #define FS_BINARY_MOUNTDATA 2
  6. #define FS_HAS_SUBTYPE      4
  7. #define FS_USERNS_MOUNT     8   /* Can be mounted by userns root */
  8. #define FS_RENAME_DOES_D_MOVE   32768   /* FS will handle d_move() during rename() internally. */
  9.     struct dentry *(*mount) (struct file_system_type *, int,
  10.                const char *, void *);
  11.     void (*kill_sb) (struct super_block *);
  12.     struct module *owner;
  13.     struct file_system_type * next;
  14.     struct hlist_head fs_supers;
  15.  
  16.     struct lock_class_key s_lock_key;
  17.     struct lock_class_key s_umount_key;
  18.     struct lock_class_key s_vfs_rename_key;
  19.     struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];
  20.  
  21.     struct lock_class_key i_lock_key;
  22.     struct lock_class_key i_mutex_key;
  23.     struct lock_class_key i_mutex_dir_key;
  24. };
Функция mount() служит для монтирования и заполнения объекта суперблока соответствующими данными Остальные параметры описывают свойства файловой системы. Для каждого типа файловой системы существует только одна структура file_system_type, независимо от того, сколько таких файловых систем смонтировано и смонтирован ли хотя бы один экземпляр соответствующей файловой системы.
Когда файловая система монтируется, создается структура vfsmount. Эта структура используется для представления конкретного экземпляра файловой системы, или, другими словами, точки монтирования.
  1. struct vfsmount {
  2.     struct dentry *mnt_root;    /* точка монтирования */
  3.     struct super_block *mnt_sb; /* указатель на суперблок */
  4.     int mnt_flags;              /* флаги монтирования */
  5. };
Флагами монтирования могут быть:
MNT_NOSUID - запрещает использование флагов setuid и setgid для файлов на файловой системе.
MNT_NODEV - запрещает доступ к файлам устройств на файловой системе.
MNT_NOEXEC - запрещает выполнение программ на файловой системе.
СуперблокСуперблок используется для представления смонтированной ФС. Информация для заполнения суперблока при монтировании может записываться на специальном секторе жёсткого диска либо, если ФС не предназначена для блочных устройств, генерироваться на лету и храниться в оперативной памяти. Я взял структуру суперблока от версии ядра 4.8rc6, опустив некоторые поля, чтобы показать более важные:
  1. struct super_block {
  2.     struct list_head               s_list;           /* Список всех суперблоков в системе */
  3.     dev_t                          s_dev;            /* идентификатор */
  4.     unsigned char                  s_blocksize_bits; /* размер блока в битах */
  5.     unsigned long                  s_blocksize;      /* размер блока в байтах */
  6.     loff_t                         s_maxbytes;       /* Максимальный размер файла для этой ФС */
  7.     struct file_system_type       *s_type;           /* указатель на тип ФС */
  8.     const struct super_operations *s_op;             /* Вектор операций над суперблоком */
  9.     unsigned long                  s_flags;          /* Флаги монтирования */
  10.     unsigned long                  s_magic;          /* Магическое число. Такие числа используется для проверки, что container_of от чего-нибудь дал указатель на начало суперблока */
  11.     struct dentry                 *s_root;           /* Точка монтирования */
  12.     struct rw_semaphore            s_umount;         /* Семафор размонтирования */
  13.     int                            s_count;          /* счётчик ссылок на суперблок */
  14.     atomic_t                       s_active;         /* Счётчик активных ссылок */
  15. #ifdef CONFIG_SECURITY
  16.     void                          *s_security;       /* Модуль безопасности */
  17. #endif
  18.     char                           s_id[32];         /* Текстовое имя */
  19.     void                          *s_fs_info;        /* Информация, специфичная для конкретной ФС. Обычно здесь размещается указатель на суперблок конкретного типа ФС */
  20.     struct list_head               s_inodes;         /* Список всех inode */
  21. };
Но самый важный элемент суперблока - это вектор операций над ним:
  1. struct super_operations {
  2.     struct inode *(*alloc_inode)(struct super_block *sb);
  3.     void (*destroy_inode)(struct inode *);
  4.  
  5.     void (*dirty_inode) (struct inode *, int flags);
  6.     int (*write_inode) (struct inode *, struct writeback_control *wbc);
  7.     int (*drop_inode) (struct inode *);
  8.     void (*evict_inode) (struct inode *);
  9.     void (*put_super) (struct super_block *);
  10.     int (*sync_fs)(struct super_block *sb, int wait);
  11.     int (*freeze_super) (struct super_block *);
  12.     int (*freeze_fs) (struct super_block *);
  13.     int (*thaw_super) (struct super_block *);
  14.     int (*unfreeze_fs) (struct super_block *);
  15.     int (*statfs) (struct dentry *, struct kstatfs *);
  16.     int (*remount_fs) (struct super_block *, int *, char *);
  17.     void (*umount_begin) (struct super_block *);
  18.  
  19.     int (*show_options)(struct seq_file *, struct dentry *);
  20.     int (*show_devname)(struct seq_file *, struct dentry *);
  21.     int (*show_path)(struct seq_file *, struct dentry *);
  22.     int (*show_stats)(struct seq_file *, struct dentry *);
  23. #ifdef CONFIG_QUOTA
  24.     ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
  25.     ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
  26.     struct dquot **(*get_dquots)(struct inode *);
  27. #endif
  28.     int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
  29.     long (*nr_cached_objects)(struct super_block *,
  30.                   struct shrink_control *);
  31.     long (*free_cached_objects)(struct super_block *,
  32.                     struct shrink_control *);
  33. };
struct inode * alloc_inode(struct super_block *sb) — создает и инициализирует новый объект inode, связанного с данным суперблоком.
void destroy_inode(struct inode *inode) — уничтожает данный inode.
void dirty_inode(struct inode *inode, int flags) — эта функция вызывается подсистемой VFS, когда в индекс вносятся изменения (dirty).
void write_inode(struct inode *inode, struct writeback_control *wbc) — эта функция записывает указанный индекс на устройство.
void drop_inode(struct inode *inode) — вызывается подсистемой VFS, когда исчезает последняя ссылка на inode.
void evict_inode(struct inode *inode) — освобождает inode.
void put_super(struct super_block *sb) — вызывается подсистемой VFS при раэмонтировании файловой системы, чтобы освободить указанный суперблок.
int sync_fs(struct super_block *sb, int wait) — синхронизирует метаданные файловой системы с данными на диске. Параметр wait указывает, должна ли операция быть синхронной или асинхронной.
int statfs(struct dentry *, struct kstatfs *) — вызывается подсистемой VFS для получения статистики файловой системы, Статистика указанной файловой системы записывается в структуру kstatfs.
int remount_fs(struct super_block *sb, int *flags, char *data) — вызывается подсистемой VFS, когда файловая система монтируется с другими параметрами монтирования.
Некоторые из этих функций являются необязательными. Файловая система может установить их значения в структуре операций суперблока равными NULL. Если соответствующий указатель равен NULL, то подсистема VFS или вызывает общий вариант функции, или не происходит ничего, в зависимости от операции.
inodeЭтот объект содержит информацию, необходимую ядру для работы с файлами и каталогами:
  1. /*
  2.  * Keep mostly read-only and often accessed (especially for
  3.  * the RCU path lookup and 'stat' data) fields at the beginning
  4.  * of the 'struct inode'
  5.  */
  6. struct inode {
  7.     umode_t                        i_mode;    /* Права доступа */
  8.     kuid_t                         i_uid;     /* Идентификатор владельца */
  9.     kgid_t                         i_gid;     /* Идентификатор группы */
  10.     unsigned int                   i_flags;   /* Разные флаги */
  11.  
  12. #ifdef CONFIG_FS_POSIX_ACL /* Если ядро собрано с поддержкой Access Control Lists */
  13.     struct posix_acl              *i_acl;
  14.     struct posix_acl              *i_default_acl;
  15. #endif
  16.  
  17.     const struct inode_operations *i_op;      /* Вектор операций над inode */
  18.     struct super_block            *i_sb;      /* Указатель на суперблок */
  19.     unsigned long                  i_ino;     /* Номер inode */
  20.     union {
  21.         const unsigned int         i_nlink;   /* Счётчик жёстких ссылок на inode */
  22.         unsigned int             __i_nlink;
  23.     };
  24.     dev_t                          i_rdev;    /* Связанное устройство */
  25.     loff_t                         i_size;    /* Размер файла в байтах */
  26.     struct timespec                i_atime;   /* Время последнего доступа к файлу */
  27.     struct timespec                i_mtime;   /* Время последнего  изменения файла */
  28.     struct timespec                i_ctime;   /* Время изменения inode */
  29.     spinlock_t                     i_lock;    /* блокировка для защиты полей при доступе */
  30.     unsigned short                 i_bytes;   /* Количество использованных байтов */
  31.     unsigned int                   i_blkbits; /* Размер блока в битах */
  32.     blkcnt_t                       i_blocks;  /* Размер файла в блоках */
  33.     u64                            i_version; /* Номер версии */
  34.     atomic_t                       i_count;   /* Счётчик ссылок на inode */
  35. };
Так же как и в случае операций суперблока, важным является поле inode_operations, в котором описаны функции файловой системы, которые могут быть вызваны подсистемой VFS для объекта файлового индекса. Как и для суперблока, операции с файловыми индексами могут быть вызваны следующим образом.
  1. inode->i_op->permission(...)
где переменная inode содержит указатель на определенный объект inode. В данном случае для inode выполняется операция permisson(), которая определена для файловой системы, в которой находится inode.
  1. struct inode_operations {
  2.     struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
  3.     const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *);
  4.     int (*permission) (struct inode *, int);
  5.     struct posix_acl * (*get_acl)(struct inode *, int);
  6.  
  7.     int (*readlink) (struct dentry *, char __user *,int);
  8.  
  9.     int (*create) (struct inode *,struct dentry *, umode_t, bool);
  10.     int (*link) (struct dentry *,struct inode *,struct dentry *);
  11.     int (*unlink) (struct inode *,struct dentry *);
  12.     int (*symlink) (struct inode *,struct dentry *,const char *);
  13.     int (*mkdir) (struct inode *,struct dentry *,umode_t);
  14.     int (*rmdir) (struct inode *,struct dentry *);
  15.     int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
  16.     int (*rename) (struct inode *, struct dentry *,
  17.             struct inode *, struct dentry *);
  18.     int (*rename2) (struct inode *, struct dentry *,
  19.             struct inode *, struct dentry *, unsigned int);
  20.     int (*setattr) (struct dentry *, struct iattr *);
  21.     int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
  22.     int (*setxattr) (struct dentry *, struct inode *,
  23.              const char *, const void *, size_t, int);
  24.     ssize_t (*getxattr) (struct dentry *, struct inode *,
  25.                  const char *, void *, size_t);
  26.     ssize_t (*listxattr) (struct dentry *, char *, size_t);
  27.     int (*removexattr) (struct dentry *, const char *);
  28.     int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
  29.               u64 len);
  30.     int (*update_time)(struct inode *, struct timespec *, int);
  31.     int (*atomic_open)(struct inode *, struct dentry *,
  32.                struct file *, unsigned open_flag,
  33.                umode_t create_mode, int *opened);
  34.     int (*tmpfile) (struct inode *, struct dentry *, umode_t);
  35.     int (*set_acl)(struct inode *, struct posix_acl *, int);
  36. } ____cacheline_aligned;
int create(struct inode *,struct dentry *, umode_t, bool) - вызывается подсистемой VFS из системных вызовов creat() и open() для создания нового inode, который имеет указанный режим доступа (mode) и связан с указанным элементом каталога (dentry). Что именно делает четвёртый параметр, не знаю, но говорится, что локальные ФС могут его игнорировать, это только для сетевых распределённых ФС.
struct dentry * lookup(struct inode *, struct dentry *, unsigned int) - производит поиск файлового индекса в указанном каталоге. inode должен соответствовать имени файла, хранящемуся в указанном объекте dentry.
int (*link) (struct dentry *old, struct inode *dir, struct dentry *dentry) - вызывается из системного вызова link() для создания жесткой ссылки (hard link) на файл, соответствующий элементу каталога old_dentry в каталоге dir. Новая ссылка должна иметь имя, которое хранится в указанном dentry.
int unlink(struct inode *, struct dentry *) - вызывается из системного вызова unlink() для удаления inode, соответствующего dentry в каталоге dir.
symlink(struct inode *dir, struct dentry *dentry, const char *symname) - вызывается из системного вызова symlink() для создания символьной ссылки с именем symname на файл, которому соответствует элемент каталога dentry в каталоге dir.
int mkdir(struct inode *dir, struct dentry *dentry, umode_t) - вызывается из системного вызова mkdir() для создания нового каталога с указанным режимом доступа.
int rmdir(struct inode *dir, struct dentry *dentry) - вызывается из системного вызова rmdir() для удаления каталога, на который указывает элемент каталога dentry из каталога dir.
int mknod (struct inode *dir, struct dentry *dentry, umode_t, dev_t) - вызывается из системного вызова mknod() для создания специального файла (файла устройства, именованного конвейера или сокета), информация о котором хранится в параметре dev_t. Файл должен быть создан в каталоге dir с именем, указанным в параметре dentry, и режимом доступа umode_t.
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 permission(struct inode *inode, int mask) - проверяет, разрешен ли указанный режим доступа к файлу, на который ссылается объект inode. Функция должна возвращать нулевое значение, если доступ разрешен, и отрицательное значение кода ошибки в противном случае. Для большинства файловых систем данное поле устанавливается в значение NULL, и при этом используется общий метод VFS, который просто сравнивает биты поля режима доступа inode с указанной маской. Более сложные файловые системы, которые поддерживают списки контроля доступа (ACL), реализуют свой метод permission().
int setattr(struct dentry *dentry, struct iattr *attr) - вызывается функцией notify_change() для уведомления о том, что произошло "событие изменения" ("change event") после модификации inode.
int getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) - вызывается подсистемой VFS при уведомлении, что inode быть обновлен с диска.
int setxattr(struct dentry *dentry, struct inode *, const char *name, const void *value, size_t size, int flags) - вызывается подсистемой VFS для установки одного из расширенных атрибутов (extended attributes) с именем name в значение value для файла, соответствующего элементу каталога dentry.
int getxattr (struct dentry *dentry, struct inode *inode, 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.
removexattr(struct dentry *dentry, const char *name) - удаляет указанный атрибут для указанного файла.
  • +4
  • views 8659