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

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

VFS. Виртуальная файловая система. Часть 1
dentryКак уже говорил, что каталоги представляются так же, как и файлы. В имени пути /bin/bash, и элемент bin, и элемент bash — это файлы, только bin — это специальный файл, который является каталогом, a bash — это обычный файл. Объекты inode служат для представления обоих этих компонентов. Несмотря на такую полезную унификацию, VFS также необходимо выполнять операции, специфичные для каталогов, такие как поиск компонента пути по его имени, проверка того, что указанный элемент пути существует, и переход на следующий компонент пути.
Для решения этой задачи в VFS реализована концепция элемента каталога (directory entry или dentry). dentry — это определенный компонент пути. В примере компоненты /, bin и bash — это объекты элементов каталога. Первые два — это каталоги, а последний — обычный файл. Важным моментом является то, что все объекты dentry — это компоненты пути, включая обычные файлы. Элементы пути также могут включать в себя точки монтирования. В имени пути /mnt/cdrom/foo, компоненты /, mnt, cdrom и foo — это все объекты типа dentry. VFS при выполнении операций с каталогами по необходимости конструирует объекты элементов каталога на лету. dentry никогда не хранятся на носителе.
dentry представлены с помощью структуры struct dentry:
  1. struct dentry {
  2.     unsigned int                    d_flags;                   /* флаги кеша объектов dentry */
  3.     seqcount_t                      d_seq;                     /* per dentry seqlock */
  4.     struct hlist_bl_node            d_hash;                    /* список хеширования */
  5.     struct dentry                  *d_parent;                  /* указатель на родителя */
  6.     struct qstr                     d_name;
  7.     struct inode                   *d_inode;                   /* Указатель на inode */
  8.     unsigned char                   d_iname[DNAME_INLINE_LEN]; /* small names */
  9.  
  10.     /* Ref lookup also touches following */
  11.     struct lockref                  d_lockref;                 /* счётчик ссылок на dentry и защита при совместном доступе */
  12.     const struct dentry_operations *d_op;                      /* вектор операций над dentry */
  13.     struct super_block             *d_sb;                      /* указатель на суперблок */
  14.     unsigned long                   d_time;    
  15.     void                           *d_fsdata;                  /* данные специфичные для клнкретной ФС */
  16.  
  17.     union {
  18.         struct list_head d_lru;    
  19.         wait_queue_head_t *d_wait; 
  20.     };
  21.     struct list_head                d_child;                   /* наше место в списке поддиректорий у родителя */
  22.     struct list_head                d_subdirs;                 /* наши поддиректории */
  23.     /*
  24.      * d_alias and d_rcu can share memory
  25.      */
  26.     union {
  27.         struct hlist_node d_alias; 
  28.         struct hlist_bl_node d_in_lookup_hash; 
  29.         struct rcu_head d_rcu;
  30.     } d_u;
  31. };
dentry может быть в одном из трех состояний: используемый (used), неиспользуемый (unused) и негативный (negative). Используемый объект соответствует существующему inode (т.е. поле d_inode указывает на связанный объект типа struct inode) и используется один или более раз. Используемый dentry не может быть удален. Неиспользуемый объект типа dentry соответствует существующему объекту inode, но VFS в данный момент не использует этот элемент каталога. Так как dentry указывает на существующий объект, то он сохраняется на случай, если вдруг окажется нужным. Если объект не ликвидировать преждевременно, то его и не нужно будет создавать заново, если вдруг он понадобится в будущем, и поиск по имени пути пройдет быстрее. Когда же появляется необходимость освободить память, то такой объект элемента каталога может быть удален, потому что он никем не используется. Негативный объект dentry не связан с существующим inode (поле d_inode равно значению NULL), потому что или inode был удален, или соответствующий элемент пути никогда не существовал. Такие dentry сохраняются, чтобы в будущем поиск по имени пути проходил быстрее.
Хотя такие объекты и полезны, но они при необходимости могут уничтожаться, поскольку никто их на самом деле не использует.
После того как VFS преодолела все трудности, связанные с переводом всех элементов пути в объекты dentry, и был достигнут конец пути, то было бы расточительно выбрасывать на ветер всю проделанную работу. Поэтому ядро кэширует объекты в кэше, который называют dcache. Он состоит из трех частей.
1 Список "используемых" dentry, которые связаны с определенным inode. Поскольку указанный файловый индекс может иметь несколько ссылок, то ему может соответсвовать несколько объектов dentry, а следовательно используется связанный список.
2 Cписок неиспользуемых и негативных объектов dentry "с наиболее поздним использованием" (last recently used, LRU). Вставки элементов в этот список отсортированы по времени, поэтому элементы, которые находятся в начале списка, — самые новые. Когда ядро должно удалить dentry для освобождения памяти, то эти элементы берутся из конца списка. потому что там находятся элементы, которые использовались наиболее давно и для которых меньше шансов, что они понадобятся в ближайшем будущем.
3 Хеш-таблица и хеш-функция, которые позволяют быстро преобразовать заданный путь в dentry.
Предположим, что мы редактируем файл /home/igor/Documents/articles/vfs.txt. Каждый раз, когда производится доступ к этому файлу (VFS должна пройти через псе dentry в соответствии с путем к файлу: /, home, igor, Documents, articles и vfs.txt. Для того чтобы каждый раз при доступе к этому (и любому другому) имени пути избежать выполнения данной операции, которая требует довольно больших затрат времени, VFS вначале может попытаться найти это имя пути в dentry-кэше. Если поиск проходит успешно, то необходимый конечный элемент каталога нужного пути получается без особых усилий. Если же данного элемента каталога нет в кэше, то VFS должна самостоятельно отследить путь. После завершения поиска найденные объекты dentry помещаются в кэш, чтобы ускорить поиск в будущем.
Кэш dcache также является интерфейсом к кэшу файловых индексов icache. inode связаны с объектами dentry, поскольку объект dentry поддерживает положительное значение счетчика использования для связанного с ним inode. Это в свою очередь позволяет dentry удерживать связанные с ними объекты inode в памяти. Иными словами, если закэширован dentry, то соответственно оказывается закэшированным и соответствующий ему inode. Следовательно, если поиск в кэше для некоторого имени пути прошел успешно, то соответствующие файловые индексы уже закэшированы в памяти.
Как и у других структур, у dentry есть свой вектор операций:
  1. struct dentry_operations {
  2.     int (*d_revalidate)(struct dentry *, unsigned int);
  3.     int (*d_weak_revalidate)(struct dentry *, unsigned int);
  4.     int (*d_hash)(const struct dentry *, struct qstr *);
  5.     int (*d_compare)(const struct dentry *,
  6.             unsigned int, const char *, const struct qstr *);
  7.     int (*d_delete)(const struct dentry *);
  8.     int (*d_init)(struct dentry *);
  9.     void (*d_release)(struct dentry *);
  10.     void (*d_prune)(struct dentry *);
  11.     void (*d_iput)(struct dentry *, struct inode *);
  12.     char *(*d_dname)(struct dentry *, char *, int);
  13.     struct vfsmount *(*d_automount)(struct path *);
  14.     int (*d_manage)(struct dentry *, bool);
  15.     struct dentry *(*d_real)(struct dentry *, const struct inode *,
  16.                  unsigned int);
  17. } ____cacheline_aligned;
int d_revalidate(struct dentry *dentry, int flags) - определяет, является ли указанный объект элемента каталога действительным. VFS вызывает эту функцию, когда она пытается использовать объект dentry из кэша dcache. Для большинства файловых систем этот метод установлен в значение NULL, потому что объекты denry, которые находятся в кэше, всегда действительны. В основном, обратное утверждение будет верно для сетевых ФС, когда кэш хранится на одной машине, а файлы - на другой.
int d_hash(struct dentry *dentry, struct qstr *name) - создает значение хеш-ключа на основании указанного объекта dentry. VFS вызывает эту функцию всякий раз, когда добавляет объект элемента каталога в хеш-таблицу.
int d_compare(struct dentry *dentry, unsigned int, struct qstr *narael, struct qstr *name2) - вызывается VFS для сравнения двух имен файлов namel и name2. Большинство файловых систем используют реализацию по умолчанию, которое соответствует простому сравнению двух строк. Для некоторых файловых систем, таких как FAT, не достаточно простого сравнения строк. FAT не чувствительна к регистру символов в именах файлов, поэтому
появляется необходимость в реализации функции, которая при сравнении не учитывает регистр символов.
int d_delete (struct dentry *dentry) - вызывается VFS, когда количество ссылок на dentry становится равным пулю.
d_release(struct dentry *dentry) - вызывается VFS, когда она собирается освободить указанный dentry. По умолчанию данная функция не выполняет никаких действий.
void d_iput(struct dentry *dentry, struct inode *inode) - вызывается VFS, когда элемент каталога теряет связь со своим файловым индексом. По умолчанию подсистема VFS просто вызывает функцию iput(), чтобы освободить соответствующий объект inode. Если файловая система переопределяет эту функцию, то она также должна вызывать функцию iput() в дополнение к специфичной для файловой системы работе.
fileПоследним рассмотрим объект файла. file используется для представления файлов, которые открыты процессом. Когда мы думаем о VFS с точки зрения пользователя, то файл — это то, что первое приходит в голову. Процессы непосредственно работают именно с файлами, а не с суперблоками, inode или dentry. Не
удивительно, что информация, которая содержится в объекте file, наиболее привычна (такие данные, как режим доступа или текущее смещение), а файловые операции похожи на системные вызовы, такие как read() и write().
file — это представление открытого файла, которое хранится в оперативной памяти. Объект file (а не сам файл) создается в ответ на системный вызов open() и уничтожается в результате системного вызова close(). Все вызовы, связанные с файлом, на самом деле являются методами, которые определены в (сюрприз) векторе операций с файлом. Так как несколько процессов могут одновременно открыть и использовать один и тот же файл, то для одного файла может существовать несколько
объектов file.
Структура файла:
  1. struct file {
  2.     union {
  3.         struct llist_node         fu_llist; /* список объектов file */
  4.         struct rcu_head           fu_rcuhead;
  5.     } f_u;
  6.     struct path                   f_path;
  7.     struct inode                 *f_inode; /* inode в кэше, поскольку file соответствует валидному файлу */
  8.     const struct file_operations *f_op;    /*  вектор операций над файлом */
  9.  
  10.     /*
  11.      * Protects f_ep_links, f_flags.
  12.      * Must not be taken from IRQ context.
  13.      */
  14.     spinlock_t                    f_lock;  /* блокировка, защищающая поля про конкурентном доступе */
  15.     atomic_long_t                 f_count; /* счётчик ссылок на объект */
  16.     unsigned int                  f_flags; /* флаги файла */
  17.     fmode_t                       f_mode;  /* режим доступа к файлу */
  18.     struct mutex                  f_pos_lock;
  19.     loff_t                        f_pos;
  20.     struct fown_struct            f_owner; /* информация о владельце файла */
  21.     const struct cred            *f_cred;
  22.     struct file_ra_state          f_ra;
  23.  
  24.     u64                           f_version;
  25. #ifdef CONFIG_SECURITY
  26.     void                         *f_security;
  27. #endif
  28.     /* needed for tty driver, and maybe others */
  29.     void                         *private_data;
  30.  
  31. #ifdef CONFIG_EPOLL
  32.     /* Used by fs/eventpoll.c to link all the hooks to this file */
  33.     struct list_head              f_ep_links; /* список ссылок eventpoll (опрос событий) */
  34.     struct list_head              f_tfile_llink;
  35. #endif /* #ifdef CONFIG_EPOLL */
  36.     struct address_space         *f_mapping; /* отображение в страничном кэше */
  37. } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
По аналогии с dentry, file не хранится на диске.
Вектор операций над file:
  1. struct file_operations {
  2.     struct module *owner;
  3.     loff_t (*llseek) (struct file *, loff_t, int);
  4.     ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
  5.     ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
  6.     ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
  7.     ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
  8.     int (*iterate) (struct file *, struct dir_context *);
  9.     int (*iterate_shared) (struct file *, struct dir_context *);
  10.     unsigned int (*poll) (struct file *, struct poll_table_struct *);
  11.     long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
  12.     long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
  13.     int (*mmap) (struct file *, struct vm_area_struct *);
  14.     int (*open) (struct inode *, struct file *);
  15.     int (*flush) (struct file *, fl_owner_t id);
  16.     int (*release) (struct inode *, struct file *);
  17.     int (*fsync) (struct file *, loff_t, loff_t, int datasync);
  18.     int (*aio_fsync) (struct kiocb *, int datasync);
  19.     int (*fasync) (int, struct file *, int);
  20.     int (*lock) (struct file *, int, struct file_lock *);
  21.     ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
  22.     unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
  23.     int (*check_flags)(int);
  24.     int (*flock) (struct file *, int, struct file_lock *);
  25.     ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
  26.     ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
  27.     int (*setlease)(struct file *, long, struct file_lock **, void **);
  28.     long (*fallocate)(struct file *file, int mode, loff_t offset,
  29.               loff_t len);
  30.     void (*show_fdinfo)(struct seq_file *m, struct file *f);
  31. #ifndef CONFIG_MMU
  32.     unsigned (*mmap_capabilities)(struct file *);
  33. #endif
  34.     ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
  35.             loff_t, size_t, unsigned int);
  36.     int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
  37.             u64);
  38.     ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
  39.             u64);
  40. };
Файловые системы могут реализовать уникальную функцию для каждой из этих операций или использовать общий существующий метод. Общие методы нормально работают для обычных Unix-подобных файловых систем. Разработчики файловых систем не обязаны реализовать все эти функции, хотя основные методы должны быть реализованы. Если какой-либо метод не представляет интереса, то его можно установить в значение NULL.
loff_t llseek(struct file *file, loff_t offset, int origin) - устанавливает значения указателя текущей позиции в файле в заданное значение параметра offset. Функция вызывается из системного вызова lseek().
ssize_t read(struct file *file, char *buf, size_t count, loff_t *offset) - считывает count байт данных из указанного файла, начиная с позиции, заданной параметром offset, в буфер памяти, на который указывает параметр buf. После этого значение указателя текущей позиции в файле должно быть обновлено. Данная функция вызывается из системного вызова read().
ssize_t read_iter(struct kiocb *, struct iov_iter *) - поскольку эта функция заменила собой aio_read, то предполагаю, это функция для асинхронного чтения из файла.
ssize_t write(struct file *file, const char *buf, size_t count, loff_t *offset) - записывает count байт данных в указанный файл, начиная с позиции offset. Данная функция вызывается из системного вызова write().
ssize_t write_iter(struct kiocb *, struct iov_iter *) - поскольку эта функция заменила собой aio_write, то предполагаю, это функция для асинхронной записи в файл.
[mono]unsigned int poll(struct file *file, struct poll_table_struct *poll_table) - переводит вызывающий процесс в состояние ожидания для ожидания действий, которые производятся с указанным файлом. Она вызывается из системного вызова poll().
[mono]ssize_t sendpage(struct file *file, struct page *page, int offset, size_t size, loff_t *pos, int more)
- используется для отправки данных из одного файла в другой.
int check_flags(int flags) - используется для проверки корректности флагов, которые передаются в системный вызов fcntl(), при использовании команды SETFL.
int flock(struct file *filp, int cmd, struct file_lock *fl) - используется для реализации системного вызова flock(), который служит для выполнения рекомендованных блокировок.
[mono]int mmap(struct file *file, struct vra_area_struct *vma)
- отображает указанный файл на область памяти в указанном адресном пространстве и вызывается из системного вызова mmap().
int open struct file *file, struct inode *inode) - создает новый файловый объект и связывает его с указанным файловым индексом. Она вызывается из системного вызова open().
[mono]int flush(struct file *file, fl_owner_t id) - вызывается VFS, когда уменьшается счетчик ссылок на открытый файл. Назначение данной функции зависит от файловой системы.
[mono]int release(struct file *file, struct inode *inode)
- вызывается VFS, когда исчезает последняя ссылка на файл, например, когда последний процесс, который использовал соответствующий файловый дескриптор, вызывает функцию close() или завершается. Назначение этой функции также зависит от файловой системы.
int fsync(struct file *, loff_t, loff_t, int datasync) - вызывается из системного вызова fsync() для записи на диск всех закэшированных данных файла.
int aio_fsync(struct kiocb *iocb, int datasync) - вызывается из системного вызова aiofsync() для записи на диск всех закэшированных данных файла, связанного с параметром iocb.
int fasync(int fd, struct file *file, int on) - разрешает или запрещает отправку сигнала для уведомлении о событиях при асинхронном вводе-выводе.
int lock(struct file *file, int cmd, struct file_lock *lock) - управляет файловыми блокировками для данного файла.
ЗаключениеРассмотрено устройство виртуальной файловой системы в Линуксе.
Рассмотрены основные структуры, существующие в каждой ФС, и принципы, которым они должны следовать, чтобы работать в этой ОС.
Источники1. Роберт Лав. "Разработка ядра Linux".
2. https://github.com/torvalds/linux
    * Documentation/filesystems/vfs.txt
    * include/linux/fs.h
    * include/linux/dcache.h
  • +5
  • views 5672