WebMoney: WMZ Z294115950220 WMR R409981405661 WME E134003968233 |
Visa 4274 3200 2453 6495 |
Чтобы поддерживать много файловых систем, Linux содержит специальный
ядерный уровень интерфейса по имени VFS (Virtual Filesystem, виртуальная
файловая система. Это подобно интерфейсу vnode/vfs, найденному в производных
SVR4 (первоначально это исходило из оригинальных реализаций BSD и Sun).
Linux inode-кэш выполнен в одиночном файле, Структура Linux inode-кэша следующая:
Списки типов закреплены в Все эти списки защищены одиночным spinlock: Подсистема кэширования inode инициализирована, когда функция
Единственная статистическая информация относительно кэша inode: число
неиспользуемых inodes, сохраненное в Мы можем исследовать один из списков из gdb на ядре таким образом:
Обратите внимание, что мы вычитали 8 из адреса 0xdfb5a2e8, чтобы получить
адрес Чтобы понимать, как работает кэш inode, стоит проследить срок службы inode
регулярного файла в файловой системе ext2:
Системный вызов open(2) выполнен в функции
Функция Когда мы открываем файл, мы вызываем Теперь давайте рассмотрим, что случается, когда мы закрываем этот
описатель файла. Системный вызов close(2) выполнен в функции
Работа, выполняемая Ядро Linux обеспечивает механизм для поддержки новой файловой системы,
которая будет написана с минимальными усилиями. Исторические причины этого:
Позвольте нам рассматривать шаги, требуемые, чтобы реализовать файловую
систему для Linux. Код может быть динамически загружаемым модулем или
статически компоноваться с ядром, а путь, которым это выполнено под Linux,
очень ясен. Все, что необходимо, это правильно заполнить структуру
Макрос Структура Поля этого объясняются таким образом:
Работа функции Под Linux имеются несколько уровней перенаправления между описателем файла
пользователя и структурой inode ядра. Когда процесс делает системный вызов
open(2), ядро возвращает маленькое неотрицательное целое число,
которое может использоваться для последующих операций ввода-вывода на этом
файле. Это целое число представляет собой индекс в массиве указателей на
Каждая задача содержит поле Поле Поле Когда файл открыт, структура файла, распределенная для него, установлена в
слот Структура файла объявлена в Рассмотрим различные поля Теперь рассмотрим структуру Под Linux информация относительно установленных файловых систем
сохраняется в двух отдельных структурах: Рассмотрим структуру Различные поля в структуре Операции суперблока описаны в структуре Рассмотрим, что происходит при монтировании дисковой файловой системы
( Как простой пример файловой системы Linux, не требующей блочного
устройства для монтирования, pipefs из Файловая система имеет тип Результатом Результаты вызова Теперь, когда система зарегистрирована и смонтирована в ядре, мы можем
использовать ее. Точка входа в pipefs: системный вызов pipe(2),
реализованный в архитектурно-зависимой функции Каждый системный вызов pipe(2) увеличивает счетчик ссылки на
образце монтирования Под Linux каналы не симметричны, то есть две стороны файла имеют различные
операции Как простой пример дисковой файловой системы в Linux, рассмотрим BFS.
Преамбула модуля BFS находится в Используется специальная макрокоманда fstype объявления
Функция инициализации модуля регистрирует файловую систему в VFS, а
функция очистки (представлена только, когда BFS конфигурирована так, чтобы
быть модулем) отменяет регистрацию системы.
После регистрации файловой системы можно продолжать работу и смонтировать
систему, вызвав метод После успешного завершения работы функции Теперь изучим, что происходит, когда мы делаем ввод-вывод на файловую
систему. Мы уже исследовали, как читаются inode, когда Давайте исследовать путь кода системного вызова link(2). Реализация
системного вызова находится в Другие связанные с inode операции, подобно Linux поддерживает прикладную программу пользователя для загрузки двоичных
с диска. Более то, что интересно двоичный код может быть сохранен в различных
форматах, и операционные системы распознают исполняемый код через системные
вызовы, которые могут отклоняться от нормы как требуется, чтобы подражать
форматам, найденным в других UNIX (COFF и прочие), а также подражать
поведению системных вызовов других систем (Solaris, UnixWare и т.д.).
Каждая Linux-задача имеет индивидуальность, сохраненную в
Меняя индивидуальность, мы можем изменять путь, которым операционная
система обрабатывает некоторые системные вызовы, например, добавляя
Область выполнения представляет собой непрерывный диапазон
индивидуальностей, выполненных одиночным модулем. Обычно одиночная область
выполнения реализует одиночную индивидуальность, но иногда возможно выполнить
"близкие" индивидуальности в одиночном модуле без многих условных выражений.
Области выполнения реализованы в Интерфейс пользователя выполнен через системный вызов
personality(2), который устанавливает индивидуальность актуального
процесса или возвращает значение Ядерный интерфейс к регистрации областей выполнения состоит
из двух функций:
Причина, почему Двоичные форматы выполнены подобным способом, то есть однонаправленный
список форматов определен в Индивидуальность процесса определена при загрузке двоичного формата
соответствующим формату методом Как только индивидуальность (а следовательно
здесь
3. Виртуальная файловая система (Virtual Filesystem, VFS)
3.1 Inode-кэши и взаимодействие с Dcache
fs/inode.c
,
который состоит из 977 строк кода. Интересно обратить внимание, что немного
изменений были сделаны в нем за последние 5-7 лет. Можно все еще распознать
часть кода, сравнивающего последнюю версию с, скажем, 1.3.42.
inode_hashtable
, где каждый
inode хэшируется значением указателя суперблока и 32bit кодом inode. Inode
без суперблока (inode->i_sb==NULL
) вместо этого будут добавлены
к двунаправленно связанному списку, возглавляемому
anon_hash_chain
. Примеры анонимного inode: сокеты, созданные
net/socket.c:sock_alloc()
вызовом
fs/inode.c:get_empty_inode()
.inode_in_use
), который
содержит имеющие силу inode с i_count>0
и
i_nlink>0
. Inode, недавно распределенные
get_empty_inode()
и get_new_inode()
добавлены к
списку inode_in_use
.inode_unused
),
который содержит имеющие силу inode с i_count=0
.sb->s_dirty
), который содержит
имеющие силу inode с i_count>0
, i_nlink>0
и
i_state & I_DIRTY
. Когда inode отмечен как dirty, он
добавлен к списку sb->s_dirty
, если это также хэшировано.
Поддержание списка per-superblock позволяет быстро синхронизировать данные.
inode_cachep
. Как
inode-объекты распределены, они принимаются из этого кэша, а когда они
освобождены, то будут возвращены к этому SLAB-кэшу.inode->i_list
, хэш-таблице из
inode->i_hash
. Каждый inode может быть в хэш-таблице и в одном
(и только в одном!) типе списка (in_use, unused или dirty).
inode_lock
.
inode_init()
вызвана из init/main.c:start_kernel()
.
Функция отмечена как __init
, что означает, что код выполнен
позже. Это получает одиночный параметр: число физических страниц в системе.
Это сделано так, чтобы кэш inode мог конфигурировать себя в зависимости от
того, сколько памяти является доступной, то есть создавать большие
хэш-таблицы, если в системе имеется достаточно памяти.
inodes_stat.nr_unused
и
доступное для программ пользователя через файлы
/proc/sys/fs/inode-nr
и /proc/sys/fs/inode-state
.
(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}
struct inode
(0xdfb5a2e0) согласно определению
макрокоманды list_entry()
из include/linux/list.h
.
fd = open("file", O_RDONLY);
close(fd);
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 сквозь
path_walk()
, который в свою очередь вызывает
real_lookup()
, который вызывает специфический для данной
файловой системы метод inode_operations->lookup()
. Роль этого
метода в том, что он должен найти entry в каталоге предыдущего уровня с
соответствующим именем и затем сделать iget(sb, ino)
, чтобы
получить соответствующий inode, который приносит в кэш inode. Когда inode
читается, dentry меняется посредством d_add(dentry, inode)
.
Обратите внимание, что для файловых систем в стиле UNIX, которые имеют
понятие дисковых чисел inode, это работа метода поисковой таблицы, чтобы
отобразить данные в формат текущего CPU format, например, если число inode
находится raw (fs-специфичной) каталожной записи можно было сделать
форматирование для 32 бит:
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_lock
. Если inode найден, счетчик
ссылок (i_count
) будет увеличен: если это было 0 до приращения,
и inode не грязен, это значение будет удалено из любого списка типа
(inode->i_list
), в который в настоящее время включено. Конечно,
данная запись должна присутствовать в списке inode_unused
.
Затем запись вставляется в список inode_in_use
, и уменьшается
значение inodes_stat.nr_unused
.iget4()
гарантированно вернул пригодный inode.get_new_inode()
,
передавая это указателем на место в хэш-таблице, где это
должно быть вставлено.get_new_inode()
распределяет новый inode из SLAB-кэша
inode_cachep
, но эта операция может блокировать (распределение
GFP_KERNEL
), так что это должно пропустить
inode_lock
spinlock, который охраняет хэш-таблицу. Так как это
пропустило spinlock, это должно повторить поиск inode в хэш-таблице: если он
найден на сей раз, это возвращает (после увеличения ссылки посредством
__iget
) найденный в хэш-таблице результат и уничтожает недавно
распределенный. Если inode все еще не находится в хэш-таблице, то новый
inode, который мы только что распределили, тот, который нужно использовать,
следовательно, он инициализирован требуемыми значениями и специфическим для
файловой системы методом sb->s_op->read_inode()
, чтобы заполнить
остальную часть inode. Это приводит нас из кэша inode обратно к коду файловой
системы. Не забывайте, что мы пришли в кэш inode, когда специфический метод
lookup()
вызвал iget()
. В то время, как метод
s_op->read_inode()
читает inode с диска, inode блокирован
(i_state = I_LOCK
). После возврата метода
read_inode()
блокировка снимается, и все
ждущие будут пробуждены.fs/open.c:sys_close()
, которая вызывает
do_close(fd, 1)
, которая уничтожает (заменяет на NULL)
дескриптор таблицы описателя файла процесса и вызывает функцию
filp_close()
, которая делает большинство работы. Интересные вещи
случаются в fput()
, которая проверяет, было ли это последней
ссылкой к файлу, и если так, то вызывает
fs/file_table.c:_fput()
, который в свою очередь вызывает
__fput()
, который является интерфейсом с dcache (а,
следовательно, и с кэшем inode: не забывайте, что dcache контролирует кэш
inode!). Далее fs/dcache.c:dput()
делает
dentry_iput()
, который приводит нас обратно к кэшу inode через
iput(inode)
, который реализован в
fs/inode.c:iput(inode)
:
sb->s_op->put_inode()
, он
вызывается немедленно без spinlock (так что это может блокировать).inode_lock
spinlock принимается, и уменьшается
i_count
. Если это не было последней ссылкой к этому inode, то мы
просто проверяем, имеется ли слишком много ссылок к этому. Поскольку
i_count
имеет лимит в 32 бита, при его превышении мы печатаем
предупреждение и делаем возврат. Обратите внимание, что мы вызываем
printk()
при работе со inode_lock
spinlock: это
прекрасно потому, что printk()
никогда не может блокировать,
следовательно это может быть вызвано в абсолютно любом контексте (даже из
программ обработки прерывания!).iput()
на последней ссылке к inode
довольно сложна, так что рассмотрим ее в отдельном списке:
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
в том же самом хэше и, если не имеется ни одного, то, если inode не грязен,
мы удаляем его из списка и добавляем к списку inode_unused
,
увеличивая тем самым inodes_stat.nr_unused
. Если имеются inode в
том же самом хэше, мы удаляем его из списка типов и вносим в список
inode_unused
. Если это был анонимный inode (NetApp .snapshot),
то удаляем его из списка и полностью уничтожаем.3.2 Регистрация файловых систем
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;
};
/proc/filesystems
и используется как ключ, чтобы найти файловую
систему по имени, то же самое имя используется для типа файловой системы в
mount(2), и это должно быть уникальным: может (очевидно) иметься
только одна файловая система с данным именем. Для модулей имя указывает на
адресные пространства модуля и не скопировано: это означает, что cat
/proc/filesystems может ошибаться, если модуль был выгружен, но файловая
система все еще зарегистрирована.FS_REQUIRES_DEV
для файловых систем, которые могут быть
смонтированы только на блочном устройстве, FS_SINGLE
для
файловых систем, которые могут иметь только один суперблок,
FS_NOMOUNT
для файловых систем, которые не могут быть
смонтированы пользователем посредством системного вызова mount(2): они
могут быть смонтированы внутренне, используя интерфейс
kern_mount()
, например, pipefs.FS_SINGLE
, где это будет ошибаться в
get_sb_single()
, пробуя передать указатель NULL в
fs_type->kern_mnt->mnt_sb
с
(fs_type->kern_mnt=NULL
).THIS_MODULE
делает все автоматически.FS_SINGLE
. Это
установлено kern_mount()
.file_systems
(подробности в файле fs/super.c
).
Список защищен file_systems_lock
read-write spinlock, и функции
register/unregister_filesystem()
изменяют его, связывая и
убирая запись из списка.read_super()
должна заполнить поля суперблока,
распределить корень inode и инициализировать любую специфическую для этой
файловой системы информацию, связанную с этим установленным образцом системы.
Так, обычно read_super()
сделает:
sb->s_dev
, используя буферный кэш через функцию
bread()
. Если это ожидает читать несколько последующих блоков
метаданных, то имеет смысл использовать breada()
, чтобы
спланировать чтение блоков дополнительного пространства асинхронно.sb->s_op
, чтобы указать на структуру
struct super_block_operations
. Эта структура содержит
специфичные для файловой системы операции выполнения функций для работы с
inode: добавления, удаления и так далее.d_alloc_root()
.sb->s_dirt
в 1 и отметит буфер, содержащий грязный суперблок.
3.3 Управление описателями файлов
struct file
. Каждая структура файла указывает на dentry через
file->f_dentry
. А каждый dentry указывает на inode
через dentry->d_inode
.
tsk->files
, которое является
указателем на struct files_struct
, определенную в файле
include/linux/sched.h
:
/*
* Open file table structure
*/
struct files_struct
{
atomic_t count;
rwlock_t file_lock;
int max_fds;
int max_fdset;
int next_fd;
struct file ** fd; /* current fd array */
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()
делает большее количество работы, обычно необходимой для регулярных файлов,
типа снятия блокировок flock, освобождения dentry и тому подобного в то
время, как put_filp()
только управляет структурами таблицы
файла, то есть уменьшает счетчик, удаляет файл из anon_list
и
добавляет это к free_list
под защитой
files_lock
spinlock.
tsk->files
может быть разделено между родителем и
порожденным, если порожденный поток был создан, используя системный вызов
clone()
с установленным параметром CLONE_FILES
.
Это может быть замечено в kernel/fork.c:copy_files()
(вызывается
do_fork()
), который увеличивает file->count
только
если CLONE_FILES
установлен вместо обычной таблицы описателя
файла копирования во время вызова классического UNIX fork(2).
current->files->fd[fd]
, и бит fd
установлен в
точечном рисунке current->files->open_fds
. Все это выполнено при
защите от записи для current->files->file_lock
. Когда описатель
закрыт, бит fd
очищен в current->files->open_fds
, и
current->files->next_fd
установлен равным fd
для
нахождения первого неиспользуемого описателя, когда в следующий раз этот
процесс захочет открыть файл.
3.4 Управление структурами файлов
include/linux/fs.h
:
struct fown_struct
{
int pid; /* pid or -pgrp where SIGIO should be sent */
uid_t uid, euid; /* uid/euid of process setting the owner */
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;
/* needed for tty driver, and maybe others */
void *private_data;
};
struct file
:
sb->s_files
список всех открытых файлов
на этой файловой системе, если соответствующий inode не анонимен, то
dentry_open()
(вызван filp_open()
) связывает файл с
этим списком, b) fs/file_table.c:free_list
, содержащий
неиспользуемые структуры файла, c) fs/file_table.c:anon_list
,
когда новая структура файла создана get_empty_filp()
, она
помещена в этом списке. Все эти списки защищены
files_lock
spinlock.open_namei()
(или ранее в
path_walk()
, который это вызывает), но фактическое поле
file->f_dentry
установлено dentry_open()
, чтобы
содержать таким образом найденный dentry.vfsmount
файловой
системы, содержащей файл. Это установлено dentry_open()
, но
найдено как часть поисковой таблицы nameidata в open_namei()
(или ранее в path_init()
, который это вызывает).file_operations
, который содержит
различные методы, которые могут вызываться на файле. Это скопировано с
inode->i_fop
, который помещен там специфичным методом
s_op->read_inode()
в течение поиска nameidata. Мы рассмотрим
методы file_operations
более подробно позже в этом разделе.get_file/put_filp/fput
.O_XXX
из системного вызова
open(2), скопированы dentry_open()
(с небольшими
модификациями в filp_open()
). После очистки
O_CREAT
, O_EXCL
, O_NOCTTY
и
O_TRUNC
не имеется никакого смысла в сохранении этих флажков
постоянно, так как они не могут изменяться F_SETFL
(или
запрашиваться F_GETFL
) в вызове fcntl(2).dentry_open()
. Отметка преобразования
должна сохранить чтение и доступ для записи в отдельных битах, так чтобы
можно было делать простые проверки, подобные
(f_mode & FMODE_WRITE)
и
(f_mode & FMODE_READ)
.long long
, то есть значение длиной в 64bit.
SIGIO
(подробности в файле
fs/fcntl.c:kill_fasync()
).get_empty_filp()
. Если файл является сокетом,
используется ipv4 netfilter.fs/nfs/file.c
и проверяется в
mm/filemap.c:generic_file_write()
.event
) всякий раз,
когда изменяется f_pos
.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 *);
};
THIS_MODULE
, файловые системы могут игнорировать это потому, что
их счетчики модуля управляются в mount/umount, а драйверы должны управлять
этим в open/release.fs/read_write.c:default_llseek()
, который
все делает правильно.read(2)
. Файловые
системы могут использовать mm/filemap.c:generic_file_read()
для
регулярных файлов и fs/read_write.c:generic_read_dir()
(который
просто возвращает -EISDIR
) для каталогов.mm/filemap.c:generic_file_write()
для
регулярных файлов и игнорировать для каталогов.FIBMAP
, FIGETBSZ
и FIONREAD
, выполнены
на более высоком уровне, так что они никогда
не читают метод f_op->ioctl()
.dentry_open()
.
Файловые системы редко используют это, например, coda пробует кэшировать файл
в местном масштабе во время открытия.release()
ниже). Единственная файловая система, которая
использует это, клиент NFS. Обратите внимание, что это может возвращать
ошибку, которая будет передана обратно пользователю, который сделал
системный вызов close(2).file->f_count
достигает 0. Хотя определено как
возврат int, значение возврата игнорируется VFS (подробности в файле
fs/file_table.c:__fput()
).file = fget(fd)
) и управления семафором
inode->i_sem
. Файловая система Ext2 в настоящее время игнорирует
последний параметр и делает точно то же самое
для fsync(2) и fdatasync(2).file->f_flags & FASYNC
.posix_lock_file()
), но стандартный код блокировки POSIX может
иметь проблемы при работе на уровне конкретной файловой системы.3.5 Управление суперблоком и точками монтирования
super_block
и
vfsmount
. Причина для этого: позволяет устанавливать ту же самую
файловую систему (блочное устройство) в нескольких точках монтирования, что
означает, что тот же самый super_block
может передаваться
нескольким структурам vfsmount
.
struct super_block
, объявленную в
include/linux/fs.h
:
struct super_block
{
struct list_head s_list; /* Keep this first */
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; /* 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 specific options */
union
{
struct minix_sb_info minix_sb;
struct ext2_sb_info ext2_sb;
..... all filesystems that need sb-private info ...
void *generic_sbp;
} u;
/*
* The next field is for VFS *only*. No filesystems have any business
* even looking at it. You had been warned.
*/
struct semaphore s_vfs_rename_sem; /* Kludge */
/* The next field is used by knfsd when converting a (inode number based)
* file handle into a dentry. As it builds a path in the dcache tree from
* the bottom up, there may for a time be a subpath of dentrys which is not
* connected to the main tree. This semaphore ensure that there is only ever
* one such free path per filesystem. Note that unconnected files (or other
* non-directories) are allowed, but not unconnected diretories.
*/
struct semaphore s_nfsd_free_path_sem;
};
super_block
:
FS_REQUIRES_DEV
, это
i_dev
блочного устройства. Для других (названных анонимными
файловыми системами) это целое число MKDEV(UNNAMED_MAJOR, i)
,
где i
первый не установленный бит в массиве
unnamed_dev_in_use
, между 1 и 255 включительно. Подробности в
fs/super.c:get_unnamed_dev()/put_unnamed_dev()
. Анонимные
файловые системы не должны использовать поле s_dev
.lock_super()/unlock_super()
.struct file_system_type
для
соответствующей файловой системы. Метод read_super()
файловой
системы должен установить это как VFS устанавливает
fs/super.c:read_super()
, если специфический метод
read_super()
прошел нормально, и сборсить в NULL при ошибке.
super_operations
,
которая содержит специфические методы для чтения-записи inode и тому
подобное. Это работа метода read_super()
по корректной
подготовке записи s_op
.read_super()
должен прочитать корень inode с диска и передать
его d_alloc_root()
, чтобы распределить dentry. Некоторые
системы записывают по буквам "корень", отличный от "/", тут следует применять
более универсальную функцию d_alloc()
, чтобы привязать dentry к
имени, например, pipefs монтируется на "pipe:" вместо "/".inode->i_state & I_DIRTY
), то это находится в специфическом
для суперблока грязном списке, связанном через inode->i_list
.
fs/file_table.c:fs_may_remount_ro()
,
который проходит сквозь список sb->s_files
и запрещает
монтирование, если имеются файлы, открытые для записи
(file->f_mode & FMODE_WRITE
), или файлы, ждущие обработки
(inode->i_nlink==0
).FS_REQUIRES_DEV
указывает на структуру
block_device, описывающую устройство, на котором смонтирована система.vfsmount
, по одной
для каждого установленного образца суперблока.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 *);
};
fs/inode.c:get_new_inode()
, iget4()
(а
следовательно и из iget()
). Если файловая система хочет
использовать iget()
, то read_inode()
должен быть
выполнен, иначе get_new_inode()
свалится. В то время, как 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 имеет собственную функцию
ramfs_get_inode()
для всех inode-операций, вызывающих
ее как необходимо.read_inode()
в котором требовалось разместить релевантный блок
на диске и взаимодействовать с буферным кэшем, вызывая
mark_buffer_dirty(bh)
. Этот метод отмечает inode как грязный,
когда inode должен быть синхронизирован индивидуально или как часть
синхронизации всей файловой системы.inode->i_count
и inode->i_nlink
достигнут 0. Файловая система удаляет копию
inode на диске и вызывает clear_inode()
на VFS inode.brelse()
) блок,
содержащий суперблок и (с помощью kfree()
) любые точечные
рисунки, распределенные для свободных блоков, разных inode и т.д.sb-private
) и вызвать mark_buffer_dirty(bh)
. Это
должно также очистить sb->s_dirt
.struct statfs
, переданный
как параметр, представляет собой ядерный указатель, а не указатель
пользователя, так что Вы не должны делать любой I/O в пользовательское
пространство. Если не выполнен, то statfs(2)
будет терпеть
неудачу с кодом ошибки ENOSYS
.clear_inode()
.
Файловые системы, которые присоединяют частные данные к структуре inode
(через поле generic_ip
), должны освободить их здесь.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()
уменьшает
счетчик перед возвратом, так что полный счет растет только на 1 после каждого
монтирования файловой системы.fs_type->fs_flags & FS_REQUIRES_DEV
равно
true, суперблок инициализирован обращением к get_sb_bdev()
,
который получает ссылку на устройство блока и взаимодействует с методом
файловой системы read_super()
, чтобы заполнить суперблок. Если
все идет хорошо, структура super_block
инициализирована, и мы
имеем ссылку дополнительного пространства к модулю файловой системы и ссылку
на основное устройство блока.vfsmount
распределена и связана со списком
sb->s_mounts
и глобальным перечнем vfsmntlist
. Поле
vfsmount
в mnt_instances
позволяет находить все
образцы, установленные на том же самом суперблоке. Поле mnt_list
позволяет находить все образцы для всех разделенных в системе суперблоков.
Поле mnt_sb
указывает на этот суперблок, и mnt_root
имеет новую ссылку на sb->s_root
dentry.3.6 Пример Virtual Filesystem: 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.
register_filesystem()
является то, что
pipe_fs_type
включен в список file_systems
, так что
можно читать /proc/filesystems
и находить запись "pipefs" там с
флажком "nodev", указывающим, что FS_REQUIRES_DEV
не был
установлен. Файл /proc/filesystems
должен на самом деле быть
расширен, чтобы поддерживать все новые флажки FS_
, но это не
может быть выполнено, потому что это разорвет все прикладные программы
пользователя, которые применяют это. Несмотря на ядерные интерфейсы Linux,
изменяющиеся каждую минуту, в плане совместимости на уровне пользовательской
поддержки Linux очень консервативная операционная система, которая позволяет
многим прикладным программам использоваться в течение длительного времени без
того, чтобы перекомпилировать их.
kern_mount()
такие:
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()
, так
что в случае сбоя можно корректировать /proc/sys/fs/super-max
.
pipe_fs_type->read_super()
, то есть
pipefs_read_super()
вызывается. Он распределяет корень inode,
корень dentry sb->s_root
и устанавливает sb->s_op
в
&pipefs_ops
.kern_mount()
вызывает add_vfsmnt(NULL,
sb->s_root, "none")
, который распределяет новую структуру
vfsmount
и связывает ее с vfsmntlist
и с
sb->s_mounts
.pipe_fs_type->kern_mnt
установлен к этой новой структуре
vfsmount
, и это возвращено. Причина, почему значением возврата
kern_mount()
является структура vfsmount
в том, что
даже файловые системы с FS_SINGLE
могут быть смонтированы
несколько раз, так что их mnt->mnt_sb
укажет на то же самое
значение, возвращенное из kern_mount()
.sys_pipe()
, но
реальная работа выполнена переносной функцией
fs/pipe.c:do_pipe()
. Позвольте нам рассматривать
do_pipe()
. Взаимодействие с pipefs случается, когда
do_pipe()
вызывает get_pipe_inode()
, чтобы
распределить новый pipefs inode. Для этого inode inode->i_sb
установлен на суперблок pipefs pipe_mnt->mnt_sb
, операции файла
i_fop
выставлены в rdwr_pipe_fops
, а число
читателей и авторов (сохраненное в inode->i_pipe
) равно 1.
Причина того, почему имеется отдельное inode-поле i_pipe
вместо
того, чтобы хранить это в области fs-private
: каналы и FIFO
совместно используют тот же самый код, и FIFO может существовать на другой
файловой системе, которая использует другие пути доступа внутри того же
самого объединения. Именно так все было в ядрах 2.2.x.
pipe_mnt
.
file->f_op
: read_pipe_fops
и
write_pipe_fops
соответственно. Запись на стороне чтения вернет
ошибку EBADF
, и наоборот.
3.7 Пример Disk Filesystem: 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_type->flags
в FS_REQUIRES_DEV
, чтобы выразить,
что BFS требует, чтобы реальное блочное устройство было установлено.
fs_type->read_super()
, который реализован
в fs/bfs/inode.c:bfs_read_super()
. Это делает следующее:
set_blocksize(s->s_dev, BFS_BSIZE)
: так как мы
собираемся взаимодействовать с уровнем устройства через буферный кэш, мы
должны инициализировать несколько вещей, а именно установить размер блока, а
также сообщить нужные данные VFS через поля s->s_blocksize
и
s->s_blocksize_bits
.bh=bread(dev, 0, BFS_BSIZE)
: мы читаем блок 0 из устройства,
переданного через s->s_dev
. Этот блок представляет собой
суперблок файловой системы.BFS_MAGIC
и, если все в порядке,
сохранен в поле s->su_sbh
(которое является в действительности
s->u.bfs_sb.si_sbh
).kmalloc(GFP_KERNEL)
и очищаем все биты за исключением первых
двух, которые мы устанавливаем в 1, чтобы указать, что мы никогда не должны
распределить inode 0 и 1. Inode 2 представляет собой корневой блок, и
соответствующий бит будет установлен в 1 несколько позже.s->s_op
, а это означает, что мы
можем из этой отметки вызывать кэш inode через iget()
, который
кончается в s_op->read_inode()
. Это находит блок, который
содержит определенный (с помощью inode->i_ino
и
inode->i_dev
) inode и читает его. Если мы терпим неудачу при
получении корневого inode, то мы освобождаем inode bitmap, возвращаем буфер
суперблока обратно в буфер кэша и возвращаем NULL. Если корневой inode
прочитался нормально, то мы распределяем dentry с именем /
и
связываем его с этим inode.iput()
: мы не
задерживаем дольше, чем это необходимо.s->s_dirt
.fs/super.c:read_super()
.read_super()
VFS
получает ссылку на модуль файловой системы через обращение к
get_filesystem(fs_type)
в fs/super.c:get_sb_bdev()
и ссылку на устройство блока.
iget()
вызван, и как они сбрасываются в iput()
. Чтение inode
устанавливает, среди других вещей, inode->i_op
и
inode->i_fop
, открытие файла размножит inode->i_fop
в file->f_op
.
fs/namei.c:sys_link()
:
getname()
, которая делает проверку ошибок.path_init()/path_walk()
работающих с dcache. Результат сохранен
в структурах old_nd
и nd
.old_nd.mnt != nd.mnt
, то вернется EXDEV
:
ссылка не может работать между файловыми системами.nd
с
помощью lookup_create()
.vfs_link()
вызвана, она проверяет,
можем ли мы создавать новый вход в каталоге и вызывает метод
dir->i_op->link()
, который обращается к специфической для
файловой системы функции fs/bfs/dir.c:bfs_link()
.bfs_link()
мы проверяем, пробуем ли мы связывать
каталог и если так, отказываемся с ошибкой EPERM
.bfs_add_entry()
, которая проходит все входы,
вымскивая неиспользуемый слот (de->ino == 0
), и при его
нахождении вписывает пару name/inode в соответствующий блок и отмечает его
как dirty (приоритет non-superblock).inode->i_nlink
, обновляем inode->i_ctime
и отмечаем
этот inode как dirty.unlink()/rename()
,
работают подобным способом, так что не много получим, исследуя
их все в деталях.
3.8 Области выполнения и двоичные форматы
task_struct
(p->personality
). В настоящее время
существует (в официальном ядре или как addon-заплата) поддержка для FreeBSD,
Solaris, UnixWare, OpenServer и многих других популярных операционных систем.
Значение current->personality
поделено на две части:
STICKY_TIMEOUTS
,
WHOLE_SECONDS
и прочее.STICKY_TIMEOUT
в current->personality
заставляем
системный вызов select(2) сохранять значение последнего параметра
(время ожидания) вместо того, чтобы сохранить время работы. Некоторые
не-Linux программы полагаются на операционные системы, так что Linux
обеспечивает способ подражать ошибкам в случаях, где исходный текст
недоступен, и эти ошибки не могут быть исправлены.
kernel/exec_domain.c
и были
полностью переделаны для ядра 2.4, по сравнению с 2.2.x. Список областей
выполнения, в настоящее время поддерживаемых ядром, наряду с диапазоном
индивидуальностей, которые они поддерживают, доступен для чтения через файл
/proc/execdomains
. Области выполнения, за исключением
PER_LINUX
, могут быть выполнены как
динамически загружаемые модули.
current->personality
, если
параметр установлен к невозможной индивидуальности 0xffffffff. Очевидно, что
поведение этого системного вызова непосредственно не
зависит от индивидуальности.
int register_exec_domain(struct exec_domain *)
:
регистрирует область выполнения, внося ее в однонаправленный список
exec_domains
при защите read-write spinlock
exec_domains_lock
. Возвращает 0 при успехе или отличное от нуля
значение при сбое.int unregister_exec_domain(struct exec_domain *)
: убирает
область выполнения из списка exec_domains
.
Возвращает 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()
. Метод load_shlib()
используется только старым системным вызовом uselib(2) в то время, как
метод load_binary()
вызван search_binary_handler()
из do_execve()
, который осуществляет
системный вызов execve(2).
load_binary()
, использующим
некоторую эвристику. Например, чтобы определить исполняемый код для UnixWare7
сначала файлы маркируются с помощью утилиты elfmark(1), которая
устанавливает в ELF-заголовке e_flags
в значение 0x314B4455,
которое обнаружено в ELF во время загрузки, и
current->personality
устанавливается в PER_UW7. Если
эвристический анализ выдает глюк, то применяется более универсальный способ,
типа обработки пути интерпретатора ELF подобно /usr/lib/ld.so.1
или /usr/lib/libc.so.1
, чтобы указать код SVR4, и
индивидуальность установлена в PER_SVR4. Можно было написать небольшую
утилиту, которая использует возможность Linux ptrace(2) для прохода
первого шага программы и принудительно устанавливает любую индивидуальность.
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 в asm-коде,
так что Вы не можете сдвигать поле 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()
представляет собой обертку вокруг
таблицы указателей функций, которые выполняют системные вызовы
uw7_funcs
для этой индивидуальности.
Назад
Вперед
Оглавление
Найди своих коллег! |