Библиотека сайта rus-linux.net
3. Virtual Filesystem (VFS)
3.1 Кеш Inode и взаимодействие с Dcache
Для поддержки различных файловых систем Linux предоставляет специальный интерфейс уровня ядра, который называется VFS (Virtual Filesystem Switch). Он подобен интерфейсу vnode/vfs, имеющемуся в производных от SVR4 (изначально пришедшему из BSD и реализаций Sun)
Реализация inode cache для Linux находится в единственном файле
fs/inode.c
, длиной в 977 строк (Следует понимать,
что размер файла может колебаться от версии к версии, так например
в ядре 2.4.18, длина этого файла составляет 1323 строки
прим. перев.). Самое интересное, что за
последние 5 - 7 лет этот файл претерпел незначительные изменения, в
нем до сих пор можно найти участки кода, дошедшие до наших дней с
версии, скажем, 1.3.42
Inode cache в Linux представляет из себя:
- Глобальный хеш-массив
inode_hashtable
, в котором каждый inode хешируется по значению указателя на суперблок и 32-битному номеру inode. При отсутсвии суперблока (inode->i_sb == NULL
), вместо хеш-массива inode добавляется к двусвязному спискуanon_hash_chain
. Примером таких анонимных inodes могут служить сокеты, созданные вызовом функцииnet/socket.c:sock_alloc()
, которая вызываетfs/inode.c:get_empty_inode()
. - Глобальный список
inode_in_use
, который содержит допустимые inodes (i_count>0
иi_nlink>0
). Inodes вновь созданные вызовом функцийget_empty_inode()
иget_new_inode()
добавляются в списокinode_in_use
- Глобальный список
inode_unused
, который содержит допустимые inode сi_count = 0
. - Список для каждого суперблока (
sb->s_dirty
) , который содержит inodes сi_count>0
,i_nlink>0
иi_state & I_DIRTY
. Когда inode помечается как "грязный" (здесь и далее под термином "грязный" подразумевается "измененный" прим. перев.), он добавляется к спискуsb->s_dirty
при условии, что он (inode) хеширован. Поддержка такого списка позволяет уменьшить накладные расходы на синхронизацию. - Inode cache суть есть - SLAB cache, который называется
inode_cachep
. Объекты inode могут создаваться и освобождаться, вставляться и изыматься из SLAB cache
Через поле inode->i_list
с inode вставляется в
список определенного типа, через поле inode->i_hash
- в хеш-массив. Каждый inode может входить в хеш-массив и в один и
только в один список типа (in_use, unused или dirty).
Списки эти защищаются блокировкой (spinlock)
inode_lock
.
Подсистема inode cache инициализируется при вызове функции
inode_init()
из
init/main.c:start_kernel()
. Эта функция имеет один
входной параметр - число страниц физической памяти в системе. В
соответсвии с этим параметром inode cache конфигуририруется под
существующий объем памяти, т.е. при большем объеме памяти создается
больший хеш-массив.
Единственная информация о inode cache, доступная пользователю -
это количество неиспользованных inodes из
inodes_stat.nr_unused
. Получить ее можно из файлов
/proc/sys/fs/inode-nr
и
/proc/sys/fs/inode-state
.
Можно исследовать один из списков с помощью gdb:
(gdb) printf "%d\n", (unsigned long)(&((struct inode *)0)->i_list) 8 (gdb) p inode_unused $34 = 0xdfa992a8 (gdb) p (struct list_head)inode_unused $35 = {next = 0xdfa992a8, prev = 0xdfcdd5a8} (gdb) p ((struct list_head)inode_unused).prev $36 = (struct list_head *) 0xdfcdd5a8 (gdb) p (((struct list_head)inode_unused).prev)->prev $37 = (struct list_head *) 0xdfb5a2e8 (gdb) set $i = (struct inode *)0xdfb5a2e0 (gdb) p $i->i_ino $38 = 0x3bec7 (gdb) p $i->i_count $39 = {counter = 0x0}
Заметьте, что от адреса 0xdfb5a2e8 отнимается число 8, чтобы
получить адрес struct inode
(0xdfb5a2e0), согласно
определению макроса list_entry()
из
include/linux/list.h
.
Для более точного понимания принципа работы inode cache, давайте рассмотрим цикл жизни обычного файла в файловой системе ext2 с момента его открытия и до закрытия.
fd = open("file", O_RDONLY); close(fd);
Системный вызов open(2) реализован в виде
функции fs/open.c:sys_open
, но основную работу
выполняет функция fs/open.c:filp_open()
, которая
разбита на две части:
open_namei()
: заполняет структуру nameidata, содержащую структуры dentry и vfsmount.dentry_open()
: с учетом dentry и vfsmount, размещает новуюstruct file
и связывает их между собой; вызывает методf_op->open()
который был установлен вinode->i_fop
при чтении inode вopen_namei()
(поставляет inode черезdentry->d_inode
).
Функция open_namei()
взаимодействует с dentry cache
через path_walk()
, которая, в свою очередь, вызывает
real_lookup()
, откуда вызывается метод
inode_operations->lookup()
. Назначение последнего -
найти вход в родительский каталог и получить соответствующий inode
вызовом iget(sb, ino)
При считывании inode, значение
dentry присваивается посредством d_add(dentry, inode)
.
Следует отметить, что для UNIX-подобных файловых систем,
поддерживающих концепцию дискового номера inode, в ходе выполнения
метода lookup()
. производится преобразование порядка
следования байт числа (endianness) в формат CPU, например, если
номер inode хранится в 32-битном формате с обратным порядком
следования байт (little-endian), то выполняются следующие
действия:
(Считаю своим долгом подробнее остановиться на понятии
endianness. Под этим термином понимается порядок
хранения байт в машинном слове (или двойном слове). Порядок может
быть "прямым" (т.е. 32-битное число хранится так
0x12345678) и тогда говорят "big
endianness" (на отечественном жаргоне это звучит как
"большой конец", т.е. младший байт лежит в старшем
адресе) или "обратным" (т.е. 32-битное число хранится так
0x78563412 - такой порядок следования байт принят в архитектуре
Intel x86) и тогда говорят "little
endianness" (на отечественном жаргоне это звучит как
"маленький конец", т.е. младший байт лежит в младшем
адресе). прим. перев.)
unsigned long ino = le32_to_cpu(de->inode); inode = iget(sb, ino); d_add(dentry, inode);
Таким образом, при открытии файла вызывается iget(sb,
ino)
, которая, фактически, называется iget4(sb, ino,
NULL, NULL)
, эта функция:
- Пытается найти inode в хеш-таблице по номерам суперблока и
inode. Поиск выполняется под блокировкой
inode_lock
. Если inode найден, то увеличивается его счетчик ссылок (i_count
); если счетчик перед инкрементом был равен нулю и inode не "грязный", то он удаляется из любого списка (inode->i_list
), в котором он находится (это конечно же списокinode_unused
) и вставляется в списокinode_in_use
; в завершение, уменьшается счетчикinodes_stat.nr_unused
. - Если inode на текущий момент заблокирован, то выполняется
ожидание до тех пор, пока inode не будет разблокирован, таким
образом,
iget4()
гарантирует возврат незаблокированного inode. - Если поиск по хеш-таблице не увенчался успехом, то вызывается
функция
get_new_inode()
, которой передается указатель на место в хеш-таблице, куда должен быть вставлен inode. get_new_inode()
распределяет память под новый inode в SLAB кэшеinode_cachep
, но эта операция может устанавливать блокировку (в случаеGFP_KERNEL
), поэтому освобождается блокировкаinode_lock
. Поскольку блокировка была сброшена то производится повторный поиск в хеш-таблице, и если на этот раз inode найден, то он возвращается в качестве результата (при этом счетчик ссылок увеличивается вызовом__iget
), а новый, только что распределенный inode уничтожается. Если же inode не найден в хеш-таблице, то вновь созданный inode инициализируется необходимыми значениями и вызывается методsb->s_op->read_inode()
, чтобы инициализировать остальную часть inode Во время чтения метдомs_op->read_inode()
, inode блокируется (i_state = I_LOCK
), после возврата изs_op->read_inode()
блокировка снимается и активируются все ожидающие его процессы.
Теперь рассмотрим действия, производимые при закрытии файлового
дескриптора. Системный вызов close(2) реализуется
функцией fs/open.c:sys_close()
, которая вызывает
do_close(fd, 1)
. Функция do_close(fd, 1)
записывает NULL на место дескриптора файла в таблице дескрипторов
процесса и вызывает функцию filp_close()
, которая и
выполняет большую часть действий. Вызывает интерес функция
fput()
, которая проверяет была ли это последняя ссылка
на файл и если да, то через fs/file_table.c:_fput()
вызывается __fput()
, которая взаимодействует с dcache
(и таким образом с inode cache - не забывайте, что dcache является
"хозяином" inode cache!). Функция
fs/dcache.c:dput()
вызывает
dentry_iput()
, которая приводит нас обратно в inode
cache через iput(inode)
. Разберем
fs/inode.c:iput(inode)
подробнее:
- Если входной параметр NULL, то абсолютно ничего не делается и управление возвращается обратно.
- Если входнй параметр определен, то вызывается специфичный для
файловой системы метод
sb->s_op->put_inode()
без захвата блокировки (так что он может быть блокирован). - Устанавливается блокировка (spinlock) и уменьшается
i_count
. Если это была не последняя ссылка, то просто проверяется - поместится ли количество ссылок в 32-битное поле и если нет - то выводится предупреждение. Отмечу, что поскольку вызов производится под блокировкойinode_lock
, то для вывода предупреждения используется функцияprintk()
, которая никогда не блокируется, поэтому ее можно вызывать абсолютно из любого контекста исполнения (даже из обработчика прерываний!). - Если ссылка была последней, то выполняются дополнительные действия.
Дополнительные действия, выполняемые по закрытию в случае
последней ссылки функцией iput()
, достаточно сложны,
поэтому они рассматриваются отдельно:
- Если
i_nlink == 0
(например файл был удален, пока мы держали его открытым), то inode удаляется из хеш-таблицы и из своего списка. Если имеются какие-либо страницы в кеше страниц, связанные с данным inode, то они удаляются посредствомtruncate_all_inode_pages(&inode->i_data)
. Затем, если определен, то вызывается специфичный для файловой системы методs_op->delete_inode()
, который обычно удаляет дисковую копию inode. В случае отсутствия зарегистрированного методаs_op->delete_inode()
(например ramfs), то вызываетсяclear_inode(inode)
, откуда производится вызовs_op->clear_inode()
, если этот метод зарегистрирован и inode соответствует блочному устройству. Счетчик ссылок на это устройство уменьшается вызовомbdput(inode->i_bdev)
. - Если
i_nlink != 0
, то проверяется - есть ли другие inode с тем же самым хеш-ключом (in the same hash bucket) и если нет, и inode не "грязный", то он удаляется из своего списка типа, вставляется в списокinode_unused
, увеличиваяinodes_stat.nr_unused
. Если имеются inodes с тем же самым хеш-ключом, то inode удаляется из списка типа и добавляется к спискуinode_unused
. Если это анонимный inode (NetApp .snapshot) то он удаляется из списка типа и очищается/удаляется полностью.
3.2 Регистрация/Дерегистрация файловых систем.
Ядро Linux предоставляет механизм, минимизирующий усилия разработчиков по написанию новых файловых систем. Исторически сложилось так, что:
- В мире широко используются различные операционные системы и чтобы люди не потеряли деньги, затреченные на покупку легального программного обеспечения, Linux должен был предоставить поддержку большого количества файловых систем, большинство из которых реализовано исключительно для совместимости.
- Интерфейс для новых файловых систем должен был быть очень простым, чтобы разработчики могли легко перепроектировать существующие файловые системы в их версии "ТОЛЬКО ДЛЯ ЧТЕНИЯ". Linux значительно облегчает создание таких версий, 95% работы над созданием новой файловой системы заключается в добавлении поддержки записи. Вот конкретный пример, я написал файловую систему BFS в версии "ТОЛЬКО ДЛЯ ЧТЕНИЯ" всего за 10 часов, однако мне потребовалось несколько недель, чтобы добавить в нее поддержку записи (и даже сегодня некоторые пуристы говорят о ее незавершенности, поскольку в ней "не реализована поддержка компактификации"). .
- Интерфейс VFS является экспортируемым и поэтому все файловые системы в Linux могут быть реализованы в виде модулей..
Рассмотрим порядок добавления новой файловой системы в Linux.
Код, реализующий файловую систему, может быть выполнен либо в виде
динамически подгружаемого модуля, либо может быть статически связан
с ядром. Все, что требуется сделать - это заполнить struct
file_system_type
и зарегистрировать файловую систему в VFS с
помощью функции register_filesystem()
, как показано
ниже (пример взят из fs/bfs/inode.c
):
#include <linux/module.h> #include <linux/init.h> static struct super_block *bfs_read_super(struct super_block *, void *, int); static DECLARE_FSTYPE_DEV(bfs_fs_type, "bfs", bfs_read_super); static int __init init_bfs_fs(void) { return register_filesystem(&bfs_fs_type); } static void __exit exit_bfs_fs(void) { unregister_filesystem(&bfs_fs_type); } module_init(init_bfs_fs) module_exit(exit_bfs_fs)
Макросы module_init()/module_exit()
, в случае,
когда BFS компилируется как модуль, преобразуют функции
init_bfs_fs()
и exit_bfs_fs()
в
init_module()
и cleanup_module()
соответственно. Если BFS компилируется статически, то код
exit_bfs_fs()
исчезает, поскольку необходимость в нем
отпадает.
Структура struct file_system_type
объявлена в
include/linux/fs.h
:
struct file_system_type { const char *name; int fs_flags; struct super_block *(*read_super) (struct super_block *, void *, int); struct module *owner; struct vfsmount *kern_mnt; /* For kernel mount, if it's FS_SINGLE fs */ struct file_system_type * next; };
Поля структуры:
- name: Удобочитаемое название файловой
системы, которое выводится в файле
/proc/filesystems
и используется как ключ для поиска файловой системы по имени; это же имя используется как аргумент в вызове mount(2) и должно быть уникальным. Для модулей имя указывает на адресное пространство модуля так, что в случае, когда модуль уже выгружен, но файловая система еще остается зарегистрированной, то команда cat /proc/filesystems может вызвать oops. - fs_flags: один или более флагов
(объединенных по OR):
FS_REQUIRES_DEV
для файловых систем, которые могут быть смонтированы только с блочных устройств,FS_SINGLE
для файловых систем, имеющих только один суперблок,FS_NOMOUNT
для файловых систем которые не могут быть смонтированы из пользовательского пространства системным вызовом mount(2), однако такие файловые системы могут быть смонтированы ядром через вызовkern_mount()
, например pipefs. - read_super: указатель на функцию, которая
считывает суперблок в процессе монтирования. Эта функция должна
быть определена обязательно. В случае ее отсутствия, операция
монтирования (независимо от того - из пользовательского ли
пространства или из ядра выполняется монтирование) всегда будет
терпеть неудачу, а в случае установленного флага
FS_SINGLE
попытка монтирования будет приводить к Oops вget_sb_single()
, при попытке получить ссылкуfs_type->kern_mnt->mnt_sb
(в то время какfs_type->kern_mnt = NULL
). - owner: указатель на модуль реализации
файловой системы. Если файловая система связана в ядро
статически, то этот указатель содержит NULL. Нет необходимости
устанавливать его вручную, так как макрос
THIS_MODULE
делает это автоматически. - kern_mnt: только для файловых систем,
имеющих флаг
FS_SINGLE
. Устанавливаетсяkern_mount()
(TODO: вызовkern_mount()
должен отвергать монтирование файловых систем если флагFS_SINGLE
не установлен). - next: поле связи в односвязном списке
file_systems
(см.fs/super.c
). Список защищается "read-write" блокировкойfile_systems_lock
и модифицируется функциямиregister/unregister_filesystem()
.
Функция read_super()
заполняет поля суперблока,
выделяет память под корневой inode и инициализирует специфичную
информацию, связанную с монтируемым экземпляром файловой системы.
Как правило read_super()
:
- Считывает суперблок с устройства, определяемого аргументом
sb->s_dev
, используя функциюbread()
. Если предполагается чтение дополнительных блоков с метаданными, то имеет смысл воспользоваться функциейbreada()
, чтобы прочитать дополнительные блоки асинхронно. - Суперблок проверяется на корректность по "магическим" последовательностям и другим признакам.
- Инициализируется указатель
sb->s_op
на структуруstruct super_block_operations
. Эта структура содержит указатели на функции, специфичные для файловой системы, такие как "read inode", "delete inode" и пр. - Выделяет память под корневой inode и dentry вызовом функции
d_alloc_root()
. - Если файловая система монтируется не как "ТОЛЬКО ДЛЯ
ЧТЕНИЯ", то в
sb->s_dirt
записывается 1 и буфер, содержащий суперблок, помечается как "грязный" (TODO: зачем это делается? Я сделал так в BFS потому, что в MINIX делается то же самое).
3.3 Управление файловыми дескрипторами
В Linux между пользовательским файловым дескриптором и
структурой inode в ядре, существует несколько уровней косвенных
ссылок. Когда процесс открывает файл системным вызовом
open(2), ядро возвращает положительное малое целое
число, которое затем используется в операциях ввода/вывода над
заданным файлом. Это целое число является индексом в массиве
указателей на struct file
. Каждая struct
file
содержит указатель на dentry
file->f_dentry
. Каждая dentry имеет указатель на
inode dentry->d_inode
.
Каждая задача содержит поле tsk->files
которое
указывает на struct files_struct
, определенную в
include/linux/sched.h
:
/* * Структура таблицы открытых файлов */ struct files_struct { atomic_t count; rwlock_t file_lock; int max_fds; int max_fdset; int next_fd; struct file ** fd; /* массив дескрипторов */ fd_set *close_on_exec; fd_set *open_fds; fd_set close_on_exec_init; fd_set open_fds_init; struct file * fd_array[NR_OPEN_DEFAULT]; };
Поле file->count
- это счетчик ссылок,
увеличивается в get_file()
(обычно вызывается из
fget()
) и уменьшается в fput()
и в
put_filp()
.Различие между fput()
и
put_filp()
состоит в том, что fput()
выполняет больший объем работы, необходимый для регулярных файлов,
т.е. освобождение блокировок, освобождение dentry и пр., в то время
как put_filp()
работает только с таблицей файловых
структур, т.е. уменьшает счетчик, удаляет файл из
anon_list
и добавляет его в free_list
,
под блокировкой files_lock
.
Таблица tsk->files
может использоваться
совместно родителем и потомком, если потомок был создан системным
вызовом clone()
с флагом CLONE_FILES
. В
качестве примера можно привести
kernel/fork.c:copy_files()
(вызывается из
do_fork()
), где только лишь увеличивается счетчик
ссылок file->count
. вместо обычного (для
классического fork(2) в UNIX) копирования таблицы
дескрипторов.
При открытии файла в памяти размещается новая файловая
структура, которая устанавливается в слот
current->files->fd[fd]
и взводится бит
fd
в current->files->open_fds
.
Действия эти выполняются под защитой от записи read-write
блокировкой current->files->file_lock
. При
закрытии дескриптора сбрасывается бит fd
в
current->files->open_fds
, а поле
current->files->next_fd
устанавливается равным
fd
на случай поиска первого неиспользуемого
дескриптора при следующем открытии файла.
3.4 Управление файловой структурой
Структура file объявлена в include/linux/fs.h
:
struct fown_struct { int pid; /* pid или -pgrp процесса, которому должен передаваться SIGIO */ uid_t uid, euid; /* uid/euid процесса-владельца */ int signum; /* posix.1b rt signal to be delivered on IO */ }; struct file { struct list_head f_list; struct dentry *f_dentry; struct vfsmount *f_vfsmnt; struct file_operations *f_op; atomic_t f_count; unsigned int f_flags; mode_t f_mode; loff_t f_pos; unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin; struct fown_struct f_owner; unsigned int f_uid, f_gid; int f_error; unsigned long f_version; /* требуется для драйвера tty, а возможно и для других */ void *private_data; };
Остановимся подробнее на полях struct file
:
- f_list: поле связи с одним (и только одним)
из списков:
а)sb->s_files
- список всех открытых файлов в данной файловой системе, если соответствующий inode не является анонимным, тоdentry_open()
(вызываемая изfilp_open()
) вставляет файл в этот список;
б)fs/file_table.c:free_list
- список неиспользуемых структур;
в)fs/file_table.c:anon_list
- в этот список включаются структуры, создаваемые вget_empty_filp()
.
Доступ к этим спискам производится под блокировкойfiles_lock
. - f_dentry: dentry файла. Создается в процессе
поиска nameidata в
open_namei()
(или точнее вpath_walk()
), но в действительности полеfile->f_dentry
заполняется вdentry_open()
. - f_vfsmnt: указатель на структуру
vfsmount
файловой системы, содержащей файл. Заполняется функциейdentry_open()
и является частью nameidata, поиск которой производится вopen_namei()
(или точнее вpath_init()
). - f_op: указатель на список
file_operations
, который содержит адреса методов для работы с файлом. Копируется изinode->i_fop
методомs_op->read_inode()
, вызываемым в процессе поиска nameidata. Более подробно на спискеfile_operations
мы остановимся ниже в этом разделе. - f_count: счетчик ссылок, изменяется в
get_file/put_filp/fput
. - f_flags: флаги
O_XXX
системного вызова open(2), копируются функциейdentry_open()
(с небольшими изменениями вfilp_open()
), при чем флагиO_CREAT
,O_EXCL
,O_NOCTTY
,O_TRUNC
сбрасываются, поскольку они не могут модифицироваться по параметруF_SETFL
(илиF_GETFL
) в системном вызове fcntl(2). - f_mode: комбинация флагов состояния,
устанавливается в
dentry_open()
. Флаги режимов доступа для чтения и записи выведены в отдельные биты, чтобы облегчить контроль состояния:(f_mode & FMODE_WRITE)
и(f_mode & FMODE_READ)
. - f_pos: текущая позиция чтения/записи в
файле. Для архитектуры i386 имеет тип
long long
, т.е. 64 бита. - f_reada, f_ramax, f_raend, f_ralen, f_rawin: поддержка опережающего чтения (readahead) слишком сложна, чтобы обсуждаться простыми смертными ;)
- f_owner: владелец файла, который будет
получать I/O уведомления посредством механизма
SIGIO
(см.fs/fcntl.c:kill_fasync()
). - f_uid, f_gid - user id и group id процесса,
открывшего файл, заполняются во время создания структуры в
get_empty_filp()
. Если файл является сокетом, то эти поля могут быть использованы в ipv4 netfilter. - f_error: используется клиентом NFS для
возврата ошибки записи. Поле устанавливается в
fs/nfs/file.c
и проверяется вmm/filemap.c:generic_file_write()
. - f_version - механизм контроля версий, служит
для синхронизации с кэшем. Увеличивается на единицу (используя
глобальный
event
) всякий раз, когда изменяетсяf_pos
. - private_data: скрытая информация о файле,
может использоваться файловой системой (например coda хранит
здесь удостоверения) или драйверами устройств. Драйверы устройств
(при наличии devfs) могут использовать это поле для различения
нескольких экземпляров вместо классического анализа младшего
номера версии в
file->f_dentry->d_inode->i_rdev
.
Перейдем к рассмотрению списка методов управления файлом
file_operations
. Позволю себе напомнить, что он
копируется из inode->i_fop
методом
s_op->read_inode()
. Структура (список методов)
объявлена в include/linux/fs.h
:
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 (*write) (struct file *, 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 datasync); 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 *); };
- owner: указатель на модуль рассматриваемой подсистемы. Это поле устанавливается только драйверами устройств, файловая система может игнорировать его, поскольку счетчик ссылок модуля файловой системы изменяется во время монтирования/демонтирования, в то время как для драйверов это должно делаться во время открытия/закрытия устройства.
- llseek: реализация системного вызова
lseek(2). Обычно опускается и используется
fs/read_write.c:default_llseek()
. (TODO: Принудительно устанавливать это поле в NULL, тем самым сэкономится лишнийif()
вllseek()
) - read: реализация системного вызова
read(2)
. Файловые системы могут использоватьmm/filemap.c:generic_file_read()
для обычных файлов иfs/read_write.c:generic_read_dir()
(которая просто возвращает-EISDIR
) для каталогов. - write: реализация системного вызова
write(2). Файловые системы могут использовать
mm/filemap.c:generic_file_write()
для обычных файлов и игнорировать его для каталогов. - readdir: используется файловой системой. Реализует системные вызовы readdir(2) и getdents(2) для каталогов и игнорируется для обычных файлов.
- poll: реализация системных вызовов poll(2) и select(2)
- ioctl: реализация специфичного для драйвера
или для файловой системы метода ioctl ( управление
вводом/выводом). Обратите внимание: общие методы ioctl типа
FIBMAP
,FIGETBSZ
,FIONREAD
реализуются на более высоком уровне, поэтому они никогда не пользуются методомf_op->ioctl()
. - mmap: реализация системного вызова mmap(2). Файловая система может использовать generic_file_mmap для обычных файлов и игнорировать это поле для каталогов.
- open: вызывается во время выполнения
open(2) функцией
dentry_open()
. Редко используется файловыми системами, например coda пытается кэшировать файл во время открытия. - flush: вызывается при каждом вызове
close(2) для заданного файла, не обязательно в
последнем (см. метод
release()
ниже). Единственная файловая система, которая вызывает этот метод - это NFS клиент, которая "выталкивает" все измененные страницы. Примечательно, что этот метод может завершаться с кодом ошибки, который передается обратно в пространство пользователя, откуда делался системный вызов close(2). - release: метод вызывается в последнем вызове
close(2) для заданного файла, т.е. когда
file->f_count
станет равным нулю. Хотя и возвращает целое (int) значение, но VFS игнорирует его (см. code>fs/file_table.c:__fput()). - fsync: преобразуется в системные вызовы
fsync(2)/fdatasync(2), причем последний аргумент
определяет сам вызов - fsync или fdatasync. Не выполняет почти
никаких действий за исключением преобразования файлового
дескриптора в файловую структуру (
file = fget(fd)
) и сброса/установки семафораinode->i_sem
. Файловая система Ext2, на сегодняшний день, игнорирует последний аргумент, передаваемый методу и выполняет одни и те же действия как для fsync(2) так и для fdatasync(2). - fasync: этот метод вызывается при изменении
file->f_flags & FASYNC
. - lock: специфичная для файловой системы часть
механизма блокировки области файла POSIX
fcntl(2). Единственная неувязка состоит в том,
что этот метод вызывается перед независимой от типа файловой
системы
posix_lock_file()
, если метод завершается успешно, а стандартный POSIX код блокировки терпит неудачу, то блокировка не будет снята на зависимом от типа файловой системы уровне.. - readv: реализация системного вызова readv(2).
- writev: реализация системного вызова writev(2).
3.5 Управление Суперблоком и точкой монтирования
В Linux, информация о смонтированных файловых системах хранится
в двух различных структурах - super_block
и
vfsmount
. Сделано это для того, чтобы имелась
возможность смонтировать одну и ту же файловую систему к нескольким
точкам монтирования одновременно, это означает, что одна и та же
структура super_block
может соответствовать нескольким
структурам vfsmount
.
В первую очередь рассмотрим структуру struct
super_block
, объявленную в
include/linux/fs.h
:
struct super_block { struct list_head s_list; /* Хранится первым */ kdev_t s_dev; unsigned long s_blocksize; unsigned char s_blocksize_bits; unsigned char s_lock; unsigned char s_dirt; struct file_system_type *s_type; struct super_operations *s_op; struct dquot_operations *dq_op; unsigned long s_flags; unsigned long s_magic; struct dentry *s_root; wait_queue_head_t s_wait; struct list_head s_dirty; /* "грязные" inodes */ struct list_head s_files; struct block_device *s_bdev; struct list_head s_mounts; /* vfsmount(s) of this one */ struct quota_mount_options s_dquot; /* параметры для Diskquota */ union { struct minix_sb_info minix_sb; struct ext2_sb_info ext2_sb; ..... Информация sb-private, необходимая для всех файловых систем ... void *generic_sbp; } u; /* * Следующее поле предназначено *только* для VFS. * Ни одна файловая система не должна изменять его, * даже если она обращается к этому полю. * Вас предупредили. */ struct semaphore s_vfs_rename_sem; /* Kludge */ /* Следующее поле используется демоном knfsd для преобразования(inode number based) * file handle в dentry. Поскольку путь в дереве dcache строится снизу вверх * то в течение некоторого времени путь является неполным, никак не связанным * с главным деревом. Этот семафор гарантирует существование единственного * такого свободного пути в файловой системе. * Заметьте, что такие "несвязанные" файлы допустимы * но не каталоги. */ struct semaphore s_nfsd_free_path_sem; };
Более подробно о полях структуры super_block
:
- s_list: двусвязный список всех активных суперблоков; Заметьте, что я не говорю "всех смонтированных файловых систем", потому что в Linux всем смонтированным экземплярам файловой системы соответствует единственный суперблок.
- s_dev: предназначено для файловых систем,
требующих наличие блочного устройства, т.е. для файловых систем,
зарегистрированных с флагом
FS_REQUIRES_DEV
, это поле представляет собой копиюi_dev
блочного устройства. Для других файловых систем (называемых анонимными) представляет собой целое числоMKDEV(UNNAMED_MAJOR, i)
, гдеi
принадлежит диапазону от 0 до 255 включительно и является порядковым номером первого неустановленного бита в массивеunnamed_dev_in_use
. Смотритеfs/super.c:get_unnamed_dev()/put_unnamed_dev()
. Неоднократно предлагалось отказаться от использования поляs_dev
анонимными файловыми системами. - s_blocksize, s_blocksize_bits: Размер блока и количество бит, необходимое для хранения размера блока (log2(blocksize)).
- s_lock: индикатор блокировки суперблока
функциями
lock_super()/unlock_super()
. - s_dirt: устанавливается при внесении изменений в суперблок и сбрасывается при записи его обратно на диск.
- s_type: указатель на структуру
struct file_system_type
, соответствующую файловой системе. Метод файловой системыread_super()
не должен устанавливать это поле, так как это поле устанавливается VFS в функцииfs/super.c:read_super()
, в случае успешного вызова методаread_super()
конкретной файловой, и сбрасывется в NULL в противном случае. - s_op: указатель на структуру (список)
super_operations
, которая содержит специфичные для заданной файловой системы методы, такие как чтение/запись inode и пр. Корректное заполнение этой структуры - задача метода файловой системыread_super()
. - dq_op: операции по дисковому квотированию.
- s_flags: флаги суперблока.
- s_magic: "магическое" число файловой системы. Используется файловой системой minix для различения разных вариантов ее.
- s_root: dentry корня файловой системы. Метод
read_super()
считывает корневой inode с диска и передает его вd_alloc_root()
, который выделяет память под dentry и заполняет ее. Некоторые файловые системы используют иное обозначение корня, нежели "/", поэтому используется более общая функцияd_alloc()
для образования полного имени, например pipefs использует "pipe:" для обозначения своего корня. - s_wait: очередь ожидания, в которой нахдятся процессы, ожидающие снятия блокировки с суперблока.
- s_dirty: список всех "грязных"
(измененных) inodes. Напомню, что если inode изменился (т.е.
inode->i_state & I_DIRTY
), то этот список связуется черезinode->i_list
. - s_files: список всех открытых файлов в
данном суперблоке. Полезен при принятии решения о
перемонтировании файловой системы в режиме "только для
чтения", см.
fs/file_table.c:fs_may_remount_ro()
, которая просматривает списокsb->s_files
и отвергает возможность перемонтирования если имеется хотя бы один файл, открытый "на запись" (file->f_mode & FMODE_WRITE
) или ожидающий удаления (inode->i_nlink == 0
). - s_bdev: для случая
FS_REQUIRES_DEV
указывает на структуру block_device, описывающую блочное устройство, с которого смонтирована файловая система. - s_mounts: список всех структур
vfsmount
для каждого смонтированного экземпляра данного суперблока. - s_dquot: используется при квотировании диска.
Методы управления суперблоком перечисляются в структуре
super_operations
, объявленной в
include/linux/fs.h
:
struct super_operations { void (*read_inode) (struct inode *); void (*write_inode) (struct inode *, int); void (*put_inode) (struct inode *); void (*delete_inode) (struct inode *); void (*put_super) (struct super_block *); void (*write_super) (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 *); };
- read_inode: операция чтения inode из
файловой системы. Вызывается только в
fs/inode.c:get_new_inode()
изiget4()
(и следовательно изiget()
). Если файловая система предполагает вызовiget()
то методread_inode()
должен быть реализован, в противном случаеget_new_inode()
будет приводить к "впадению в панику" (panic). Во время чтения inode заблокирован (inode->i_state = I_LOCK
). Когда функция возвращает управление, все процессы из очередиinode->i_wait
пробуждаются. В задачу методаread_inode()
входит обнаружение дискового блока, который содержит заданный inode и с помощью функйии буферного кэшаbread()
прочитать его и инициализировать различные поля в структуре inode, напримерinode->i_op
иinode->i_fop
, чтобы уровень VFS "знал" какие операции над inode и соответствующим ему файлом, считаются допустимыми. Имеются файловые системы, в которых методread_inode()
не реализован - это ramfs и pipefs. Так ramfs имеет свою собственную функцию генерации inode (ramfs_get_inode()
). - write_inode: операция записи inode на диск.
так же как и
read_inode()
отыскивает нужный дисковый блок и вызывает функцию буферного кэшаmark_buffer_dirty(bh)
. Этот метод вызывается для "грязных" inode (которые были помечены вызовомmark_inode_dirty()
) при возникновении необходимости синхронизации как отдельно взятого inode, так и файловой системы в целом. - put_inode: вызывается всякий раз при уменьшении счетчика ссылок.
- delete_inode: вызывается всякий раз, когда
inode->i_count
иinode->i_nlink
достигают нулевого значения. Файловая система удаляет дисковую копию inode и вызываетclear_inode()
для VFS inode, чтобы "прекратить его существование окончательно". - put_super: вызывается на последней стадии
работы системного вызова umount(2), чтобы
уведомить файловую систему о том, что любая приватная информация,
удерживаемая ею, должна быть освобождена. Обычно это
brelse()
блока, содержащего суперблок, иkfree()
для освобождения всех ранее размещенных блоков, inodes и т.п. - write_super: вызывается в случае
необходимости записать суперблок на диск. Должен отыскать блок,
содержащий суперблок, (обычно хранится в области
sb-private
) и вызватьmark_buffer_dirty(bh)
. А так же должен сбросить флагsb->s_dirt
flag. - statfs: реализация системного вызова
fstatfs(2)/statfs(2). Заметьте, что указатель на
struct statfs
, передаваемый в качестве аргумента, является указателем пространства ядра а не пользовательского пространства, поэтому не следует выполнять каких либо операций ввода-вывода в пользовательском пространстве. В случае отсутствия этого метода вызовstatfs(2)
будет возвращвть код ошибкиENOSYS
. - remount_fs: вызывается всякий раз при перемонтировании файловой системы.
- clear_inode: вызывается из функции
clear_inode()
уровня VFS. Файловая система должна освободить приватную информацию в структуре inode (присоединенную через полеgeneric_ip
). - umount_begin: вызывается в случае принудительно размонтирования для уведомления файловой системы заранее, чтобы убедиться, что она не занята. В настоящее время используется только NFS. Этот метод не имеет никакого отношения к идее поддержки принудительного размонтирования на уровне VFS.
Теперь рассмотрим последовательность действий, выполняемых при
монтировании дисковой (FS_REQUIRES_DEV
) файловой
системы. Реализация системного вызова mount(2)
находится в fs/super.c:sys_mount()
, которая по сути
является лишь оберткой, которая передает опции монтирования, тип
файловой системы и название устройства в функцию
do_mount()
.
- В случае необходимости, загружается модуль драйвера файловой
системы и увеличивается счетчик ссылок на этот модуль.
Примечательно, что в процессе монтирования счетчик ссылок на
модуль файловой системы увеличивается дважды - один раз в
do_mount()
, вызываемой изget_fs_type()
, и один раз вget_sb_dev()
, вызываемой изget_filesystem()
, еслиread_super()
выполнилась успешно. Первое увеличение предотвращает выгрузку модуля пока выполняется методread_super()
и второе увеличение указывает на то, что модуль используется смонтированным экземпляром. Вполне понятно, что перед завершениемdo_mount()
уменьшает счетчик ссылок на единицу, таким образом суммарное приращение счетчика составляет единицу после каждого монтирования. - Для нашего случая выражение
fs_type->fs_flags & FS_REQUIRES_DEV
истинно, поэтому далее инициализируется суперблок, вызовомget_sb_bdev()
, который получает ссылку на блочное устройство и вызывом методаread_super()
заполняет поля суперблока. Если все прошло гладко, то структураsuper_block
считается инициализированной и мы получаем дополнительно ссылку на модуль файловой системы и ссылку на основное блочное устройство. - В памяти размещается новая структура
vfsmount
и "прицепляется" к спискуsb->s_mounts
и к глобальному спискуvfsmntlist
. С помощью поляmnt_instances
структурыvfsmount
можно найти все смонтированные экземпляры файловой системы для одного и того же суперблока. С помощью спискаmnt_list
можно отыскать все смонтированные экземпляры файловых систем для всех суперблоков в системе. Полеmnt_sb
указывает на данный суперблок, аmnt_root
получает новую ссылку наsb->s_root
dentry.
3.6 Пример виртуальной файловой системы: pipefs
В качестве простого примера файловой системы в Linux рассмотрим
pipefs, которая не требует наличия блочного устройства для своего
монтирования. Реализация pipefs находится в
fs/pipe.c
.
static DECLARE_FSTYPE(pipe_fs_type, "pipefs", pipefs_read_super, FS_NOMOUNT|FS_SINGLE); static int __init init_pipe_fs(void) { int err = register_filesystem(&pipe_fs_type); if (!err) { pipe_mnt = kern_mount(&pipe_fs_type); err = PTR_ERR(pipe_mnt); if (!IS_ERR(pipe_mnt)) err = 0; } return err; } static void __exit exit_pipe_fs(void) { unregister_filesystem(&pipe_fs_type); kern_umount(pipe_mnt); } module_init(init_pipe_fs) module_exit(exit_pipe_fs)
Файловая система принадлежит к типу
FS_NOMOUNT|FS_SINGLE
это означает, что она не может
быть смонтирована из пространства пользователя и в системе может
иметься только один суперблок этой файловой системы. Флаг
FS_SINGLE
означает также что она должна монтироваться
через kern_mount()
после того как будет выполнена
регистрация вызовом register_filesystem()
, что
собственно и выполняется функцией init_pipe_fs()
.
Единственная неприятность - если kern_mount()
завершится с ошибкой (например когда kmalloc()
,
вызываемый из add_vfsmnt()
не сможет распределить
память), то файловая система окажется зарегистрированной но модуль
не будет инициализирован. Тогда команда cat
/proc/filesystems повлечет за собой Oops. (передал Линусу
"заплату", хотя это фактически не является ошибкой,
поскольку на сегодняшний день pipefs не может быть скомпилирована
как модуль, но в будущем вполне может быть добавлена взможность
вынесения pipefs в модуль).
В результате register_filesystem()
,
pipe_fs_type
добавляется к списку
file_systems
, который содержится в
/proc/filesystems
. Прочитав его, вы обнаружите
"pipefs" с флагом "nodev", указывающим на то,
что флаг FS_REQUIRES_DEV
не был установлен. Следовало
бы расширить формат файла /proc/filesystems
с тем,
чтобы включить в него поддержку всех новых FS_
флагов
(и я написал такую "заплату"), но это невозможно,
поскольку такое изменение может отрицательно сказаться на
пользовательских приложениях, которые используют этот файл.
Несмотря на то, что интерфейсы ядра изменяются чуть ли не
ежеминутно, тем не менее когда вопрос касается пространства
пользователя, Linux превращается в очень консервативную
операционную систему, которая позволяет использование программ в
течение длительного времени без необходимости их
перекомпиляции.
В результате выполнения kern_mount()
:
- Создается новое неименованное (anonymous) устройство
установкой бита в
unnamed_dev_in_use
; если в этом массиве не окажется свободного бита, тоkern_mount()
вернется с ошибкойEMFILE
. - Посредством
get_empty_super()
создается новая структура суперблока. Функцияget_empty_super()
проходит по списку суперблоковsuper_block
в поисках свободного места, т.е.s->s_dev == 0
. Если такового не обнаружилось, то резервируется память вызовомkmalloc()
, с приоритетомGFP_USER
. Вget_empty_super()
проверяется превышение максимально возможного количества суперблоков, так что в случае появления сбоев, при монтировании pipefs, можно попробовать подкорректировать/proc/sys/fs/super-max
. - Вызывается метод
pipe_fs_type->read_super()
(т.е.pipefs_read_super()
), который размещает корневой inode и dentrysb->s_root
, а также записывает адрес&pipefs_ops
вsb->s_op
. - Затем вызывается
add_vfsmnt(NULL, sb->s_root, "none")
, которая размещает в памяти новую структуруvfsmount
и включает ее в списокvfsmntlist
иsb->s_mounts
. - В
pipe_fs_type->kern_mnt
заносится адрес новой структурыvfsmount
и он же и возвращается в качестве результата. Причина, по которой возвращаемое значение является указателем наvfsmount
состоит в том, что даже не смотря на флагFS_SINGLE
, файловая система может быть смонтирована несколько раз, вот только ихmnt->mnt_sb
будут указывать в одно и то же место.
После того как файловая система зарегистрирована и смонтирована,
с ней можно работать. Точкой входа в файловую систему pipefs
является системный вызов pipe(2), реализованный
платформо-зависимой функцией sys_pipe()
, которая в
свою очередь передает управление платформо-независимой функции
fs/pipe.c:do_pipe()
. Взаимодействие
do_pipe()
с pipefs начинается с размещения нового
inode вызовом get_pipe_inode()
. В поле
inode->i_sb
этого inode заносится указатель на
суперблок pipe_mnt->mnt_sb
, в список
i_fop
файловых операций заносится
rdwr_pipe_fops
, а число "читателей" и
"писателей" (содержится в inode->i_pipe
)
устанавливается равным 1. Причина, по которой имеется отдельное
поле i_pipe
, вместо хранения этой информации в
приватной области fs-private
, заключается в том, что
каналы (pipes) и FIFO (именованные каналы) совместно используют
один и тот же код, а FIFO могут существовать и в другой файловой
системе, которые используют другие способы доступа в пределах этого
объединения (fs-private
) и могут работать, что
называется "на удачу". Так, в ядре 2.2.x, все перестает
работать, стоит только слегка изменить порядок следования полей в
inode.
Каждый системный вызов pipe(2) увеличивает
счетчик ссылок в структуре pipe_mnt
.
В Linux каналы (pipes) не являются симметричными, т.е. с каждого
конца канал имеет различный набор файловых операций
file->f_op
- read_pipe_fops
и
write_pipe_fops
. При попытке записи со стороны канала,
открытого на чтение, будет возвращена ошибка EBADF
, то
же произойдет и при попытке чтения с конца канала, открытого на
запись.
3.7 Пример дисковой файловой системы: BFS
В качестве примера дисковой файловой системы рассмотрим BFS.
Преамбула модуля BFS в файле fs/bfs/inode.c
:
static DECLARE_FSTYPE_DEV(bfs_fs_type, "bfs", bfs_read_super); static int __init init_bfs_fs(void) { return register_filesystem(&bfs_fs_type); } static void __exit exit_bfs_fs(void) { unregister_filesystem(&bfs_fs_type); } module_init(init_bfs_fs) module_exit(exit_bfs_fs)
Макрокоманда DECLARE_FSTYPE_DEV()
взводит флаг
FS_REQUIRES_DEV
в fs_type->flags
, это
означает, что BFS может быть смонтирована только с реального
блочного устройства.
Функция инициализации модуля регистрирует файловую систему в VFS, а функция завершения работы модуля - дерегистрирует ее (эта функция компилируется только когда поддержка BFS включена в ядро в виде модуля).
После регистрации файловой системы, она становится доступной для
монтирования, в процессе монтирования вызывается метод
fs_type->read_super()
, который выполняет следующие
действия:
set_blocksize(s->s_dev, BFS_BSIZE)
: поскольку предполагается взаимодействие с уровнем блочного устройства через буферный кэш, следует выполнить некоторые действия, а именно указать размер блока и сообщить о нем VFS через поляs->s_blocksize
иs->s_blocksize_bits
.bh = bread(dev, 0, BFS_BSIZE)
: читается нулевой блок с устройстваs->s_dev
. Этот блок является суперблоком файловой системы.- Суперблок проверяется на наличие сигнатуры
("магической" последовательности)
BFS_MAGIC
, если все в порядке, то он сохраняется в полеs->su_sbh
(на самом деле этоs->u.bfs_sb.si_sbh
). - Далее создается новая битовая карта inode вызовом
kmalloc(GFP_KERNEL)
и все биты в ней сбрасываются в 0, за исключением двух первых, которые указывают на то, что 0-й и 1-й inode никогда не должны распределяться. Inode с номером 2 является корневым, установка соответствующего ему бита производится несколькими строками ниже, в любом случае файловая система должна получить корневой inode во время монтирования! - Инициализируется
s->s_op
, и уже после этого можно вызватьiget()
, которая обратится кs_op->read_inode()
. Она отыщет блок, который содержит заданный (поinode->i_ino
иinode->i_dev
) inode и прочитает его. Если при запросе корневого inode произойдет ошибка, то память, занимаемая битовой картой inode, будет освобождена, буфер суперблока возвратится в буферный кэш и в качестве результата будет возвращен "пустой" указатель - NULL. Если корневой inode был успешно прочитан, то далее размещается dentry с именем/
и связывается с этим inode. - После этого последовательно считываются все inode в файловой
системе и устанвливаются соответствующие им биты в битовой карте,
а так же подсчитываются некоторые внутренние параметры, такие как
смещение последнего inode и начало/конец блоков последнего файла.
Все прочитанные inode возвращаются обратно в кэш inode вызовом
iput()
- ссылка на них не удерживается дольше, чем это необходимо. - Если файловая система была смонтирована как
"read/write", то буфер суперблока помечается как
"грязный" (измененный прим.
перев.) и устанавливается флаг
s->s_dirt
(TODO: Для чего? Первоначально я сделал это потому, что это делалось вminix_read_super()
, но ни minix ни BFS кажется не изменяют суперблок вread_super()
). - Все складывается удачно, так что далее функция возвращает
инициализированный суперблок уровню VFS, т.е.
fs/super.c:read_super()
.
После успешного завершения работы функции
read_super()
VFS получает ссылку на модуль файловой
системы через вызов get_filesystem(fs_type)
в
fs/super.c:get_sb_bdev()
и ссылку на блочное
устройство.
Рассмотрим, что происходит при выполнении опреаций ввода/вывода
над файловой системой. Мы уже знаем, что inode читается функцией
iget()
и что они освобождаются вызовом
iput()
. Чтение inode приводит, кроме всего прочего, к
установке полей inode->i_op
и
inode->i_fop
; открытие файла вызывает копирование
inode->i_fop
в file->f_op
.
Рассмотрим последовательность действий системного вызова
link(2). Реализация системного вызова находится в
fs/namei.c:sys_link()
:
- Имена из пользовательского пространства копируются в
пространство ядра функцией
getname()
, которая выполняет проверку на наличие ошибок. - Эти имена преобразуются в nameidata с помощью
path_init()/path_walk()
. Результат сохраняется в структурахold_nd
иnd
- Если
old_nd.mnt != nd.mnt
, то возвращается "cross-device link"EXDEV
- невозможно установить ссылку между файловыми системами, в Linux это означает невозможность установить ссылку между смонтированными экземплярами одной файловой системы (или, особенно, между различными файловыми системами). - Для
nd
создается новый dentry вызовомlookup_create()
. - Вызывается универсальная функция
vfs_link()
, которая проверяет возможность создания новой ссылки по заданному пути и вызывает методdir->i_op->link()
, который приводит нас вfs/bfs/dir.c:bfs_link()
. - Внутри
bfs_link()
, производится проверка - не делается ли попытка создать жесткую ссылку на директорию и если это так, то возвращается код ошибкиEPERM
. Это как стандарт (ext2). - Предпринимается попытка добавить новую ссылку в заданную
директорию вызовом вспомогательной функции
bfs_add_entry()
, которая отыскивает неиспользуемый слот (de->ino == 0
) и если находит, то записывает пару имя/inode в соответствующий блок и помечает его как "грязный". - Если ссылка была добавлена, то далее ошибки возникнуть уже не
может, поэтому увеличивается
inode->i_nlink
, обновляетсяinode->i_ctime
и inode помечается как "грязный" твк же как и inode приписанный новому dentry.
Другие родственные операции над inode, подобные
unlink()/rename()
, выполняются аналогичным образом,
так что не имеет большого смысла рассматривать их в деталях.
3.8 Домены исполнения и двоичные форматы
Linux поддерживает загрузку пользовательских приложений с диска. Самое интересное, что приложения могут храниться на диске в самых разных форматах и реакция Linux на системные вызовы из программ тоже может быть различной (такое поведение является нормой в Linux) как того требует эмуляция форматов, принятых в других UNIX системах (COFF и т.п.), а так же эмуляция поведения системных вызовов (Solaris, UnixWare и т.п.). Это как раз то, для чего служит поддержка доменов исполнения и двоичных форматов.
Каждая задача в Linux хранит свою "индивидуальность"
(personality) в task_struct
(p->personality
). В настоящее время существует
(либо официально в ядре, либо в виде "заплат") поддержка
FreeBSD, Solaris, UnixWare, OpenServer и многих других популярных
операционных систем. Значение current->personality
делится на две части:
- старшие три байта - эмуляция "ошибок":
STICKY_TIMEOUTS
,WHOLE_SECONDS
и т.п. - младший байт - соответствующая "индивидуальность" (personality), уникальное число.
Изменяя значение personality можно добиться изменения способа
исполнения некоторых системных вызовов, например: добавление
STICKY_TIMEOUT
в current->personality
приведет к тому, что последний аргумент (timeout), передаваемый в
select(2) останется неизменным после возврата, в
то время как в Linux в этом аргументе принято возвращать
неиспользованное время. Некоторые программы полагаются на
соответствующее поведение операционных систем (не Linux) и поэтому
Linux предоставляет возможность эмуляции "ошибок" в
случае, когда исходный код программы не доступен и такого рода
поведение программ не может быть исправлено.
Домен исполнения - это непрерывный диапазон "индивидуальностей", реализованных в одном модуле. Обычно один домен исполнения соответствует одной "индивидуальности", но иногда оказывается возможным реализовать "близкие индивидуальности" в одном модуле без большого количества условий.
Реализация доменов исполнения находится в
kernel/exec_domain.c
и была полностью переписана, по
сравнению с ядром 2.2.x. Список доменов исполнения и диапазоны
"индивидуальностей", поддерживаемых доменами, можно найти
в файле /proc/execdomains
. Домены исполнения могут
быть реализованы в виде подгружаемых модулей, кроме одного -
PER_LINUX
.
Интерфейс с пользователем осуществляется через системный вызов
personality(2), который изменяет
"индивидуальность" текущего процесса или возвращает
значение current->personality
, если в качестве
аргумента передать значение несуществующей
"индивидуальности" 0xffffffff. Очевидно, что поведение
самого этого системного вызова не зависит от
"индивидуальности" вызывающего процесса.
Действия по регистрации/дерегистрации доменов исполнения в ядре выполняются двумя функциями:
int register_exec_domain(struct exec_domain *)
: регистрирует домен исполнения, добавляя его в односвязный списокexec_domains
под защитой от записи read-write блокировкойexec_domains_lock
. Возвращает 0 в случае успеха и ненулевое в случае неудачи.int unregister_exec_domain(struct exec_domain *)
: дерегистрирует домен исполнения, удаляя его из спискаexec_domains
, опять же под read-write блокировкойexec_domains_lock
полученной в режиме защиты от записи. Возвращает 0 в случае успеха.
Причина, по которой блокировка exec_domains_lock
имеет тип read-write, состоит в том, что только запросы на
регистрацию и дерегистрацию модифицируют список доменов, в то время
как команда cat /proc/filesystems вызывает
fs/exec_domain.c:get_exec_domain_list()
, которой
достаточен доступ к списку в режиме "только для чтения".
Регистрация нового домена определяет "обработчик lcall7"
и карту преобразования номеров сигналов. "Заплата" ABI
расширяет концепцию доменов исполнения, включая дополнительную
информацию (такую как опции сокетов, типы сокетов, семейство
адресов и таблицы errno (коды ошибок)).
Обработка двоичных форматов реализована похожим образом, т.е. в
виде односвязного списка форматов и определена в
fs/exec.c
. Список защищается read-write блокировкой
binfmt_lock
. Как и exec_domains_lock
,
блокировка binfmt_lock
, в большинстве случаев, берется
"только на чтение" за исключением
регистрации/дерегистрации двоичного формата. Регистрация нового
двоичного формата расширяет системный вызов
execve(2) новыми функциями
load_binary()/load_shlib()
так же как и
core_dump()
. Метод load_shlib()
используется только в устаревшем системном вызове
uselib(2), в то время как метод
load_binary()
вызывается функцией
search_binary_handler()
из do_execve()
,
который и является реализацией системного вызова
execve(2).
"Индивидуальность" процесса определяется во время
загрузки двоичного формата соответствующим методом
load_binary()
с использованием некоторых эвристик.
Например, формат UnixWare7 при создании помечается утилитой
elfmark(1), которая заносит "магическую"
последовательность 0x314B4455 в поле e_flags
ELF-заголовка. Эта последовательность затем определяется во время
загрузки приложения и в результате
current->personality
принимает значение PER_UW7.
Если эта эвристика не подходит, то более универсальная обрабатывает
пути интерпретатора ELF, подобно /usr/lib/ld.so.1
или
/usr/lib/libc.so.1
для указания используемого формата
SVR4 и personality принимает значение PER_SVR4. Можно написать
небольшую утилиту, которая использовала бы возможности
ptrace(2) Linux для пошагового прохождения по коду
и принудительно запускать программы в любой
"индивидуальности".
Поскольку "индивидуальность" (а следовательно и
current->exec_domain
) известна, то и системные
вызовы обрабатываются соответственно. Предположим, что процесс
производит системный вызов через шлюз lcall7. Такой вызов передает
управление в точку ENTRY(lcall7)
в файле
arch/i386/kernel/entry.S
, поскольку она задается в
arch/i386/kernel/traps.c:trap_init()
. После
преобразования размещения стека, entry.S:lcall7
получает указатель на exec_domain
из
current
и смещение обработчика lcall7 внутри
exec_domain
(которое жестко задано числом 4 в
ассемблерном коде, так что вы не сможете изменить смещение поля
handler
в C-объявлении struct
exec_domain
) и переходит на него. Так на C это выглядело бы
как:
static void UW7_lcall7(int segment, struct pt_regs * regs) { abi_dispatch(regs, &uw7_funcs[regs->eax & 0xff], 1); }
где abi_dispatch()
- это обертка вокруг таблицы
указателей на функции, реализующих системные вызовы для personality
uw7_funcs
.
Вперед Назад Содержание