RussianLDP Рейтинг@Mail.ru
WebMoney: 
WMZ Z294115950220 
WMR R409981405661 
WME E134003968233 
Visa 
4274 3200 2453 6495 

Назад Вперед Оглавление


3. Виртуальная файловая система (Virtual Filesystem, VFS)

3.1 Inode-кэши и взаимодействие с Dcache

Чтобы поддерживать много файловых систем, Linux содержит специальный ядерный уровень интерфейса по имени VFS (Virtual Filesystem, виртуальная файловая система. Это подобно интерфейсу vnode/vfs, найденному в производных SVR4 (первоначально это исходило из оригинальных реализаций BSD и Sun).

Linux inode-кэш выполнен в одиночном файле, fs/inode.c, который состоит из 977 строк кода. Интересно обратить внимание, что немного изменений были сделаны в нем за последние 5-7 лет. Можно все еще распознать часть кода, сравнивающего последнюю версию с, скажем, 1.3.42.

Структура Linux inode-кэша следующая:

  1. Глобальная хэш-таблица, 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().
  2. Глобальный тип списка in_use (inode_in_use), который содержит имеющие силу inode с i_count>0 и i_nlink>0. Inode, недавно распределенные get_empty_inode() и get_new_inode() добавлены к списку inode_in_use.
  3. Глобальный тип неиспользуемого списка (inode_unused), который содержит имеющие силу inode с i_count=0.
  4. Тип списка per-superblock (sb->s_dirty), который содержит имеющие силу inode с i_count>0, i_nlink>0 и i_state & I_DIRTY. Когда inode отмечен как dirty, он добавлен к списку sb->s_dirty, если это также хэшировано. Поддержание списка per-superblock позволяет быстро синхронизировать данные.
  5. Inode cache proper: SLAB-кэш, названный inode_cachep. Как inode-объекты распределены, они принимаются из этого кэша, а когда они освобождены, то будут возвращены к этому SLAB-кэшу.

Списки типов закреплены в inode->i_list, хэш-таблице из inode->i_hash. Каждый inode может быть в хэш-таблице и в одном (и только в одном!) типе списка (in_use, unused или dirty).

Все эти списки защищены одиночным spinlock: inode_lock.

Подсистема кэширования inode инициализирована, когда функция inode_init() вызвана из init/main.c:start_kernel(). Функция отмечена как __init, что означает, что код выполнен позже. Это получает одиночный параметр: число физических страниц в системе. Это сделано так, чтобы кэш inode мог конфигурировать себя в зависимости от того, сколько памяти является доступной, то есть создавать большие хэш-таблицы, если в системе имеется достаточно памяти.

Единственная статистическая информация относительно кэша inode: число неиспользуемых 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}

Обратите внимание, что мы вычитали 8 из адреса 0xdfb5a2e8, чтобы получить адрес struct inode (0xdfb5a2e0) согласно определению макрокоманды list_entry() из include/linux/list.h.

Чтобы понимать, как работает кэш inode, стоит проследить срок службы inode регулярного файла в файловой системе ext2:

fd = open("file", O_RDONLY);
close(fd);

Системный вызов open(2) выполнен в функции fs/open.c:sys_open, и реальная работа выполнена функцией fs/open.c:filp_open(), которая разделена на две части:

  1. open_namei(): заполняет структуру nameidata, содержащую подструктуры dentry и vfsmount.
  2. 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), который делает следующее:

  1. Пытается найти inode с соответствующим суперблоком и числом inode в хэш-таблице при защите inode_lock. Если inode найден, счетчик ссылок (i_count) будет увеличен: если это было 0 до приращения, и inode не грязен, это значение будет удалено из любого списка типа (inode->i_list), в который в настоящее время включено. Конечно, данная запись должна присутствовать в списке inode_unused. Затем запись вставляется в список inode_in_use, и уменьшается значение inodes_stat.nr_unused.
  2. Если inode в настоящее время блокирован, мы ждем, пока он не освободится, чтобы вызов iget4() гарантированно вернул пригодный inode.
  3. Если inode не найден в хэш-таблице, значит это первый раз, когда мы сталкиваемся с этим inode, так что мы вызываем get_new_inode(), передавая это указателем на место в хэш-таблице, где это должно быть вставлено.
  4. Вызов 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() блокировка снимается, и все ждущие будут пробуждены.

Теперь давайте рассмотрим, что случается, когда мы закрываем этот описатель файла. Системный вызов close(2) выполнен в функции 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):

  1. Если параметр, переданный нам, равен NULL, мы не делаем абсолютно ничего и возвращает управление.
  2. Если имеется специфический метод sb->s_op->put_inode(), он вызывается немедленно без spinlock (так что это может блокировать).
  3. inode_lock spinlock принимается, и уменьшается i_count. Если это не было последней ссылкой к этому inode, то мы просто проверяем, имеется ли слишком много ссылок к этому. Поскольку i_count имеет лимит в 32 бита, при его превышении мы печатаем предупреждение и делаем возврат. Обратите внимание, что мы вызываем printk() при работе со inode_lock spinlock: это прекрасно потому, что printk() никогда не может блокировать, следовательно это может быть вызвано в абсолютно любом контексте (даже из программ обработки прерывания!).
  4. Если это было последней активной ссылкой, то некоторая работа должна быть выполнена.

Работа, выполняемая iput() на последней ссылке к inode довольно сложна, так что рассмотрим ее в отдельном списке:

  1. Если 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)).
  2. Если i_nlink!=0, то мы проверяем, имеются ли другой inode в том же самом хэше и, если не имеется ни одного, то, если inode не грязен, мы удаляем его из списка и добавляем к списку inode_unused, увеличивая тем самым inodes_stat.nr_unused. Если имеются inode в том же самом хэше, мы удаляем его из списка типов и вносим в список inode_unused. Если это был анонимный inode (NetApp .snapshot), то удаляем его из списка и полностью уничтожаем.

3.2 Регистрация файловых систем

Ядро Linux обеспечивает механизм для поддержки новой файловой системы, которая будет написана с минимальными усилиями. Исторические причины этого:

  1. В мире, где люди все еще используют не-Linux операционные системы, чтобы защитить их капиталовложения в программное обеспечение, Linux должна обеспечить взаимодействие, обеспечивая поддержку множества файловых систем, основная масса которых нужна только для совместимости с существующими не-Linux операционными системами.
  2. Интерфейс для авторов файловых систем должен быть очень прост так, чтобы люди могли бы пробовать перепроектировать существующие платные файловые системы, создавая версии только для их чтения. Следовательно, Linux VFS делает это очень простым. Как конкретный пример, я записал BFS только для чтения для Linux приблизительно за 10 часов, но требуется несколько недель, чтобы завершить это, чтобы иметь полную поддержку записи.
  3. Интерфейс VFS экспортируется, а следовательно все файловые системы в Linux могут быть легко выполнены как модули для ядра.

Позвольте нам рассматривать шаги, требуемые, чтобы реализовать файловую систему для 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 может ошибаться, если модуль был выгружен, но файловая система все еще зарегистрирована.
  • fs_flags: один или несколько (OR) флагов: FS_REQUIRES_DEV для файловых систем, которые могут быть смонтированы только на блочном устройстве, FS_SINGLE для файловых систем, которые могут иметь только один суперблок, FS_NOMOUNT для файловых систем, которые не могут быть смонтированы пользователем посредством системного вызова mount(2): они могут быть смонтированы внутренне, используя интерфейс kern_mount(), например, pipefs.
  • read_super: указатель на функцию, которая читает суперблок в течение операции монтирования. Эта функция обязательна: если она не обеспечивается, операция монтирования всегда будет терпеть неудачу за исключением ситуации FS_SINGLE, где это будет ошибаться в get_sb_single(), пробуя передать указатель NULL в fs_type->kern_mnt->mnt_sb с (fs_type->kern_mnt=NULL).
  • owner: указатель на модуль, который реализует эту файловую систему. Если он статически вкомпонован в ядро, то это равно NULL. Вы не должны устанавливать это вручную, поскольку макрокоманда THIS_MODULE делает все автоматически.
  • kern_mnt: только для файловых систем FS_SINGLE. Это установлено kern_mount().
  • next: ссылка на отдельно-связанный список, возглавляемый file_systems (подробности в файле fs/super.c). Список защищен file_systems_lock read-write spinlock, и функции register/unregister_filesystem() изменяют его, связывая и убирая запись из списка.

Работа функции read_super() должна заполнить поля суперблока, распределить корень inode и инициализировать любую специфическую для этой файловой системы информацию, связанную с этим установленным образцом системы. Так, обычно read_super() сделает:

  1. Прочитает суперблок из устройства, определенного через параметр sb->s_dev, используя буферный кэш через функцию bread(). Если это ожидает читать несколько последующих блоков метаданных, то имеет смысл использовать breada(), чтобы спланировать чтение блоков дополнительного пространства асинхронно.
  2. Проверит, что суперблок содержит имеющий силу код и полные данные.
  3. Инициализирует sb->s_op, чтобы указать на структуру struct super_block_operations. Эта структура содержит специфичные для файловой системы операции выполнения функций для работы с inode: добавления, удаления и так далее.
  4. Распределит корень inode и корень dentry, используя d_alloc_root().
  5. Если файловая система не установлена только для чтения, затем установит sb->s_dirt в 1 и отметит буфер, содержащий грязный суперблок.

3.3 Управление описателями файлов

Под Linux имеются несколько уровней перенаправления между описателем файла пользователя и структурой inode ядра. Когда процесс делает системный вызов open(2), ядро возвращает маленькое неотрицательное целое число, которое может использоваться для последующих операций ввода-вывода на этом файле. Это целое число представляет собой индекс в массиве указателей на 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:

  1. f_list: это поле связывает структуру файла с одним (и только одним!) из списков: a) 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.
  2. f_dentry: dentry соответствующий этому файлу. Dentry создан во время поиска nameidata в open_namei() (или ранее в path_walk(), который это вызывает), но фактическое поле file->f_dentry установлено dentry_open(), чтобы содержать таким образом найденный dentry.
  3. f_vfsmnt: указатель на структуру vfsmount файловой системы, содержащей файл. Это установлено dentry_open(), но найдено как часть поисковой таблицы nameidata в open_namei() (или ранее в path_init(), который это вызывает).
  4. f_op: указатель на file_operations, который содержит различные методы, которые могут вызываться на файле. Это скопировано с inode->i_fop, который помещен там специфичным методом s_op->read_inode() в течение поиска nameidata. Мы рассмотрим методы file_operations более подробно позже в этом разделе.
  5. f_count: счетчик ссылок, управляемый get_file/put_filp/fput.
  6. f_flags: флаги O_XXX из системного вызова open(2), скопированы dentry_open() (с небольшими модификациями в filp_open()). После очистки O_CREAT, O_EXCL, O_NOCTTY и O_TRUNC не имеется никакого смысла в сохранении этих флажков постоянно, так как они не могут изменяться F_SETFL (или запрашиваться F_GETFL) в вызове fcntl(2).
  7. f_mode: комбинация флажков пользовательского пространства и режима, установленного в dentry_open(). Отметка преобразования должна сохранить чтение и доступ для записи в отдельных битах, так чтобы можно было делать простые проверки, подобные (f_mode & FMODE_WRITE) и (f_mode & FMODE_READ).
  8. f_pos: текущая позиция в файле для следующего чтения или записи. В i386 это имеет тип long long, то есть значение длиной в 64bit.
  9. f_reada, f_ramax, f_raend, f_ralen, f_rawin: для поддержки readahead.
  10. f_owner: владелец файла I/O, чтобы получить асинхронные сообщения I/O через механизм SIGIO (подробности в файле fs/fcntl.c:kill_fasync()).
  11. f_uid, f_gid устанавливаются к идентификатору пользователя и идентификатору группы процесса, который открыл файл, когда структура файла создана в get_empty_filp(). Если файл является сокетом, используется ipv4 netfilter.
  12. f_error: использован клиентом NFS, чтобы возвратить ошибки записи. Это установлено в fs/nfs/file.c и проверяется в mm/filemap.c:generic_file_write().
  13. f_version механизм для лишения законной силы кэшей, увеличенных (посредством глобального event) всякий раз, когда изменяется f_pos.
  14. private_data: частные данные для каждого файла, которые могут использоваться файловой системой или драйверами устройств. Драйверы устройств (в присутствии 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 *);
};

  1. owner: указатель на модуль, который обладает рассматриваемой подсистемой. Только драйверы должны установить это к THIS_MODULE, файловые системы могут игнорировать это потому, что их счетчики модуля управляются в mount/umount, а драйверы должны управлять этим в open/release.
  2. llseek: реализация системного вызова lseek(2). Обычно это опущено, и применяется fs/read_write.c:default_llseek(), который все делает правильно.
  3. read: реализация системного вызова read(2). Файловые системы могут использовать mm/filemap.c:generic_file_read() для регулярных файлов и fs/read_write.c:generic_read_dir() (который просто возвращает -EISDIR) для каталогов.
  4. write: реализация системного вызова write(2). Файловые системы могут использовать mm/filemap.c:generic_file_write() для регулярных файлов и игнорировать для каталогов.
  5. readdir: применяется файловыми системами. Игнорирован для регулярных файлов и реализаций системных вызовов readdir(2) и getdents(2) для каталогов.
  6. poll: реализация системных вызовов poll(2) и select(2).
  7. ioctl: реализация драйвера или специфического для файловой системы ioctl. Обратите внимание, что универсальные файловые ioctl, подобные FIBMAP, FIGETBSZ и FIONREAD, выполнены на более высоком уровне, так что они никогда не читают метод f_op->ioctl().
  8. mmap: реализация системного вызова mmap(2). Файловые системы могут использовать generic_file_mmap для регулярных файлов и игнорировать для каталогов.
  9. open: вызывает open(2) во время dentry_open(). Файловые системы редко используют это, например, coda пробует кэшировать файл в местном масштабе во время открытия.
  10. flush: вызывается при каждом выполнении close(2) для этого файла, не обязательно последнем (подробнее смотрите метод release() ниже). Единственная файловая система, которая использует это, клиент NFS. Обратите внимание, что это может возвращать ошибку, которая будет передана обратно пользователю, который сделал системный вызов close(2).
  11. release: вызывается при последнем close(2) для этого файла, то есть когда file->f_count достигает 0. Хотя определено как возврат int, значение возврата игнорируется VFS (подробности в файле fs/file_table.c:__fput()).
  12. fsync: отображает непосредственно к системным вызовам fsync(2)/fdatasync(2) с последним параметром, определяющим, является ли это fsync или fdatasync. Почти никакая работа не выполнена VFS для этого, за исключением отображения описателя файла к структуре файла (file = fget(fd)) и управления семафором inode->i_sem. Файловая система Ext2 в настоящее время игнорирует последний параметр и делает точно то же самое для fsync(2) и fdatasync(2).
  13. fasync: этот метод вызван, когда изменяется file->f_flags & FASYNC.
  14. lock: специфичная для этой файловой системы порция POSIX fcntl(2), блокирующие механизм. Единственная ошибка здесь в том, что это вызвано прежде, чем выполнена независимая от файловой системы порция (posix_lock_file()), но стандартный код блокировки POSIX может иметь проблемы при работе на уровне конкретной файловой системы.
  15. readv: реализация системного вызова readv(2).
  16. 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;     /* 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:

  1. s_list: двусторонне связанный список всех активных суперблоков, но не всех смонтированных файловых систем: один суперблок может соответствовать нескольким файловым системам.
  2. s_dev: для файловых систем, которые требуют блокировки для монтирования, то есть для 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.
  3. s_blocksize, s_blocksize_bits: blocksize и log2(blocksize).
  4. s_lock: указывает, блокирован ли суперблок в настоящее время lock_super()/unlock_super().
  5. s_dirt: установится, когда суперблок изменен, и очиститься всякий раз, когда он записан.
  6. s_type: указатель на struct file_system_type для соответствующей файловой системы. Метод read_super() файловой системы должен установить это как VFS устанавливает fs/super.c:read_super(), если специфический метод read_super() прошел нормально, и сборсить в NULL при ошибке.
  7. s_op: указатель на структуру super_operations, которая содержит специфические методы для чтения-записи inode и тому подобное. Это работа метода read_super() по корректной подготовке записи s_op.
  8. dq_op: дисковые квоты.
  9. s_flags: флаги суперблока.
  10. s_magic: код файловой системы. Использован minix, чтобы дифференцировать различные версии.
  11. s_root: dentry корня файловой системы. Метод read_super() должен прочитать корень inode с диска и передать его d_alloc_root(), чтобы распределить dentry. Некоторые системы записывают по буквам "корень", отличный от "/", тут следует применять более универсальную функцию d_alloc(), чтобы привязать dentry к имени, например, pipefs монтируется на "pipe:" вместо "/".
  12. s_wait: очередь ожидания процессов, ждущих суперблок, который нужно разблокировать.
  13. s_dirty: список всех грязных inode. Если inode грязный (inode->i_state & I_DIRTY), то это находится в специфическом для суперблока грязном списке, связанном через inode->i_list.
  14. s_files: список всех открытых файлов на этом суперблоке. Полезен для решения, может ли файловая система быть повторно смонтирована только для чтения, подробности в fs/file_table.c:fs_may_remount_ro(), который проходит сквозь список sb->s_files и запрещает монтирование, если имеются файлы, открытые для записи (file->f_mode & FMODE_WRITE), или файлы, ждущие обработки (inode->i_nlink==0).
  15. s_bdev: для FS_REQUIRES_DEV указывает на структуру block_device, описывающую устройство, на котором смонтирована система.
  16. s_mounts: перечень всех структур vfsmount, по одной для каждого установленного образца суперблока.
  17. 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 *);
};

  1. read_inode: читает inode из файловой системы. Это вызвано только из 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-операций, вызывающих ее как необходимо.
  2. write_inode: пишет inode на диск. Подобно read_inode() в котором требовалось разместить релевантный блок на диске и взаимодействовать с буферным кэшем, вызывая mark_buffer_dirty(bh). Этот метод отмечает inode как грязный, когда inode должен быть синхронизирован индивидуально или как часть синхронизации всей файловой системы.
  3. put_inode: вызван всякий раз, когда счетчик ссылок уменьшен.
  4. delete_inode: вызван всякий раз, когда inode->i_count и inode->i_nlink достигнут 0. Файловая система удаляет копию inode на диске и вызывает clear_inode() на VFS inode.
  5. put_super: вызван в последней стадии системного вызова umount(2), чтобы сообщить файловой системе, что любая частная информация, задержанная системой относительно этого образца должна быть освобождена. Обычно это освобождает (с помощью brelse()) блок, содержащий суперблок и (с помощью kfree()) любые точечные рисунки, распределенные для свободных блоков, разных inode и т.д.
  6. write_super: вызван, когда суперблок должен быть записан на диск. Это должно найти блок, содержащий суперблок (обычно сохраняемый в области sb-private) и вызвать mark_buffer_dirty(bh). Это должно также очистить sb->s_dirt.
  7. statfs: реализация системных вызовов fstatfs(2)/statfs(2). Обратите внимание, что указатель на struct statfs, переданный как параметр, представляет собой ядерный указатель, а не указатель пользователя, так что Вы не должны делать любой I/O в пользовательское пространство. Если не выполнен, то statfs(2) будет терпеть неудачу с кодом ошибки ENOSYS.
  8. remount_fs: вызван всякий раз, когда файловая система перемонтируется.
  9. clear_inode: вызван из уровня VFS clear_inode(). Файловые системы, которые присоединяют частные данные к структуре inode (через поле generic_ip), должны освободить их здесь.
  10. umount_begin: вызван в течение принудительного umount, чтобы сообщить файловой системе, чтобы проверить, все ли свободно. В настоящее время используется только NFS. Это не имеет никакого отношения к идеи относительно универсального уровня VFS.

Рассмотрим, что происходит при монтировании дисковой файловой системы (FS_REQUIRES_DEV). Реализация системного вызова mount(2) дислоцирована в fs/super.c:sys_mount(), который является просто оберткой и на деле лишь копирует параметры, тип файловой системы и имя устройства в функцию do_mount(), которая делает реальную работу:

  1. Драйвер файловой системы загружен, если необходимо, и счетчик ссылок модуля увеличен. Обратите внимание, что в течение операции монтирования счетчик ссылок модуля увеличен дважды: do_mount() при вызове get_fs_type() и get_sb_dev() при вызове get_filesystem(), если read_super() прошел нормально. Первое приращение должно предотвратить выгрузку модуля в то время, как мы работаем внутри метода read_super(), а второе приращение состоит в том, чтобы указать, что модуль находится в использовании этим установленным образцом. Очевидно, что do_mount() уменьшает счетчик перед возвратом, так что полный счет растет только на 1 после каждого монтирования файловой системы.
  2. В нашем случае fs_type->fs_flags & FS_REQUIRES_DEV равно true, суперблок инициализирован обращением к get_sb_bdev(), который получает ссылку на устройство блока и взаимодействует с методом файловой системы read_super(), чтобы заполнить суперблок. Если все идет хорошо, структура super_block инициализирована, и мы имеем ссылку дополнительного пространства к модулю файловой системы и ссылку на основное устройство блока.
  3. Новая структура vfsmount распределена и связана со списком sb->s_mounts и глобальным перечнем vfsmntlist. Поле vfsmount в mnt_instances позволяет находить все образцы, установленные на том же самом суперблоке. Поле mnt_list позволяет находить все образцы для всех разделенных в системе суперблоков. Поле mnt_sb указывает на этот суперблок, и mnt_root имеет новую ссылку на sb->s_root dentry.

3.6 Пример Virtual Filesystem: pipefs

Как простой пример файловой системы Linux, не требующей блочного устройства для монтирования, 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() такие:

  1. Новый неназванный (анонимный) номер устройства распределен, устанавливая бит в точечном рисунке unnamed_dev_in_use. Если не имеется больше битов, то kern_mount() вылетает с ошибкой EMFILE.
  2. Новая структура суперблока распределена посредством get_empty_super(). Функция get_empty_super() проходит сквозь список суперблоков, возглавляемый super_block, и ищет пустую запись, то есть s->s_dev==0. Если такой пустой суперблок не найден, то новый будет распределен, используя kmalloc() с приоритетом GFP_USER. Максимальное число суперблоков в системе проверяется get_empty_super(), так что в случае сбоя можно корректировать /proc/sys/fs/super-max.
  3. Специфичный для файловой системы метод pipe_fs_type->read_super(), то есть pipefs_read_super() вызывается. Он распределяет корень inode, корень dentry sb->s_root и устанавливает sb->s_op в &pipefs_ops.
  4. Затем kern_mount() вызывает add_vfsmnt(NULL, sb->s_root, "none"), который распределяет новую структуру vfsmount и связывает ее с vfsmntlist и с sb->s_mounts.
  5. pipe_fs_type->kern_mnt установлен к этой новой структуре vfsmount, и это возвращено. Причина, почему значением возврата kern_mount() является структура vfsmount в том, что даже файловые системы с FS_SINGLE могут быть смонтированы несколько раз, так что их mnt->mnt_sb укажет на то же самое значение, возвращенное из kern_mount().

Теперь, когда система зарегистрирована и смонтирована в ядре, мы можем использовать ее. Точка входа в pipefs: системный вызов pipe(2), реализованный в архитектурно-зависимой функции 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(2) увеличивает счетчик ссылки на образце монтирования pipe_mnt.

Под Linux каналы не симметричны, то есть две стороны файла имеют различные операции file->f_op: read_pipe_fops и write_pipe_fops соответственно. Запись на стороне чтения вернет ошибку EBADF, и наоборот.

3.7 Пример Disk Filesystem: BFS

Как простой пример дисковой файловой системы в Linux, рассмотрим 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)

Используется специальная макрокоманда fstype объявления DECLARE_FSTYPE_DEV(), которая устанавливает fs_type->flags в FS_REQUIRES_DEV, чтобы выразить, что BFS требует, чтобы реальное блочное устройство было установлено.

Функция инициализации модуля регистрирует файловую систему в VFS, а функция очистки (представлена только, когда BFS конфигурирована так, чтобы быть модулем) отменяет регистрацию системы.

После регистрации файловой системы можно продолжать работу и смонтировать систему, вызвав метод fs_type->read_super(), который реализован в fs/bfs/inode.c:bfs_read_super(). Это делает следующее:

  1. set_blocksize(s->s_dev, BFS_BSIZE): так как мы собираемся взаимодействовать с уровнем устройства через буферный кэш, мы должны инициализировать несколько вещей, а именно установить размер блока, а также сообщить нужные данные VFS через поля s->s_blocksize и s->s_blocksize_bits.
  2. bh=bread(dev, 0, BFS_BSIZE): мы читаем блок 0 из устройства, переданного через s->s_dev. Этот блок представляет собой суперблок файловой системы.
  3. Суперблок утвержден через BFS_MAGIC и, если все в порядке, сохранен в поле s->su_sbh (которое является в действительности s->u.bfs_sb.si_sbh).
  4. Затем мы распределяем inode bitmap, используя kmalloc(GFP_KERNEL) и очищаем все биты за исключением первых двух, которые мы устанавливаем в 1, чтобы указать, что мы никогда не должны распределить inode 0 и 1. Inode 2 представляет собой корневой блок, и соответствующий бит будет установлен в 1 несколько позже.
  5. Затем мы инициализируем s->s_op, а это означает, что мы можем из этой отметки вызывать кэш inode через iget(), который кончается в s_op->read_inode(). Это находит блок, который содержит определенный (с помощью inode->i_ino и inode->i_dev) inode и читает его. Если мы терпим неудачу при получении корневого inode, то мы освобождаем inode bitmap, возвращаем буфер суперблока обратно в буфер кэша и возвращаем NULL. Если корневой inode прочитался нормально, то мы распределяем dentry с именем / и связываем его с этим inode.
  6. Теперь мы проходим все inode в файловой системе и читаем их всех, чтобы установить соответствующие биты в нашем внутреннем inode bitmap, а также вычислять некоторые другие внутренние параметры, подобно смещению последнего inode и блоков начала/конца последнего файла. Каждый inode, который мы читаем, возвращен обратно в кэш inode через iput(): мы не задерживаем дольше, чем это необходимо.
  7. Если файловая система не была смонтирована только для чтения, мы отмечаем суперблок как dirty и устанавливаем флаг s->s_dirt.
  8. Все хорошо, так что мы возвращаем этот инициализированный суперблок обратно на уровне 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():

  1. Пользовательские имена скопированы в ядерное пространство посредством функции getname(), которая делает проверку ошибок.
  2. Эти имена преобразованы в nameidata, используя path_init()/path_walk() работающих с dcache. Результат сохранен в структурах old_nd и nd.
  3. Если old_nd.mnt != nd.mnt, то вернется EXDEV: ссылка не может работать между файловыми системами.
  4. Новый dentry создан, соответствуя nd с помощью lookup_create().
  5. Универсальная функция vfs_link() вызвана, она проверяет, можем ли мы создавать новый вход в каталоге и вызывает метод dir->i_op->link(), который обращается к специфической для файловой системы функции fs/bfs/dir.c:bfs_link().
  6. Внутри bfs_link() мы проверяем, пробуем ли мы связывать каталог и если так, отказываемся с ошибкой EPERM.
  7. Мы пытаемся добавлять новый вход каталога к определенному каталогу, вызывая функцию bfs_add_entry(), которая проходит все входы, вымскивая неиспользуемый слот (de->ino == 0), и при его нахождении вписывает пару name/inode в соответствующий блок и отмечает его как dirty (приоритет non-superblock).
  8. Если мы успешно добавили вход каталога, то у операции не имеется никакого способа потерпеть неудачу, так что мы увеличиваем inode->i_nlink, обновляем inode->i_ctime и отмечаем этот inode как dirty.

Другие связанные с inode операции, подобно unlink()/rename(), работают подобным способом, так что не много получим, исследуя их все в деталях.

3.8 Области выполнения и двоичные форматы

Linux поддерживает прикладную программу пользователя для загрузки двоичных с диска. Более то, что интересно двоичный код может быть сохранен в различных форматах, и операционные системы распознают исполняемый код через системные вызовы, которые могут отклоняться от нормы как требуется, чтобы подражать форматам, найденным в других UNIX (COFF и прочие), а также подражать поведению системных вызовов других систем (Solaris, UnixWare и т.д.).

Каждая Linux-задача имеет индивидуальность, сохраненную в task_struct (p->personality). В настоящее время существует (в официальном ядре или как addon-заплата) поддержка для FreeBSD, Solaris, UnixWare, OpenServer и многих других популярных операционных систем. Значение current->personality поделено на две части:

  1. Старшие три байта эмуляция ошибок: STICKY_TIMEOUTS, WHOLE_SECONDS и прочее.
  2. Младший байт задает соответствующую индивидуальность, уникальный код.

Меняя индивидуальность, мы можем изменять путь, которым операционная система обрабатывает некоторые системные вызовы, например, добавляя STICKY_TIMEOUT в current->personality заставляем системный вызов select(2) сохранять значение последнего параметра (время ожидания) вместо того, чтобы сохранить время работы. Некоторые не-Linux программы полагаются на операционные системы, так что Linux обеспечивает способ подражать ошибкам в случаях, где исходный текст недоступен, и эти ошибки не могут быть исправлены.

Область выполнения представляет собой непрерывный диапазон индивидуальностей, выполненных одиночным модулем. Обычно одиночная область выполнения реализует одиночную индивидуальность, но иногда возможно выполнить "близкие" индивидуальности в одиночном модуле без многих условных выражений.

Области выполнения реализованы в kernel/exec_domain.c и были полностью переделаны для ядра 2.4, по сравнению с 2.2.x. Список областей выполнения, в настоящее время поддерживаемых ядром, наряду с диапазоном индивидуальностей, которые они поддерживают, доступен для чтения через файл /proc/execdomains. Области выполнения, за исключением PER_LINUX, могут быть выполнены как динамически загружаемые модули.

Интерфейс пользователя выполнен через системный вызов personality(2), который устанавливает индивидуальность актуального процесса или возвращает значение 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 для этой индивидуальности.


Назад Вперед Оглавление

Поиск

 

Найди своих коллег!