Внутреннее устройство ядра Linux 2.4

Пример дисковой файловой системы: BFS


В качестве примера дисковой файловой системы рассмотрим BFS. Преамбула модуля BFS в файле fs/bfs/inode.c:

static DECLARE_FSTYPE_DEV(bfs_fs_type, "bfs", bfs_read_super);

static int __init init_bfs_fs(void) { return register_filesystem(&bfs_fs_type); }

static void __exit exit_bfs_fs(void) { unregister_filesystem(&bfs_fs_type); }

module_init(init_bfs_fs) module_exit(exit_bfs_fs)

Макрокоманда DECLARE_FSTYPE_DEV() взводит флаг FS_REQUIRES_DEV в fs_type->flags, это означает, что BFS может быть смонтирована только с реального блочного устройства.

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

После регистрации файловой системы, она становится доступной для монтирования, в процессе монтирования вызывается метод fs_type->read_super(), который выполняет следующие действия:

  • set_blocksize(s->s_dev, BFS_BSIZE): поскольку предполагается взаимодействие с уровнем блочного устройства через буферный кэш, следует выполнить некоторые действия, а именно указать размер блока и сообщить о нем VFS через поля s->s_blocksize и s->s_blocksize_bits.
  • bh = bread(dev, 0, BFS_BSIZE): читается нулевой блок с устройства s->s_dev. Этот блок является суперблоком файловой системы.
  • Суперблок проверяется на наличие сигнатуры ("магической" последовательности) BFS_MAGIC, если все в порядке, то он сохраняется в поле s->su_sbh (на самом деле это s->u.bfs_sb.si_sbh).
  • Далее создается новая битовая карта inode вызовом kmalloc(GFP_KERNEL) и все биты в ней сбрасываются в 0, за исключением двух первых, которые указывают на то, что 0-й и 1-й inode никогда не должны распределяться. Inode с номером 2 является корневым, установка соответствующего ему бита производится несколькими строками ниже, в любом случае файловая система должна получить корневой inode во время монтирования!
  • Инициализируется s->s_op, и уже после этого можно вызвать iget(), которая обратится к s_op->read_inode(). Она отыщет блок, который содержит заданный (по inode->i_ino и inode->i_dev) inode и прочитает его. Если при запросе корневого inode произойдет ошибка, то память, занимаемая битовой картой inode, будет освобождена, буфер суперблока возвратится в буферный кэш и в качестве результата будет возвращен "пустой" указатель - NULL. Если корневой inode был успешно прочитан, то далее размещается dentry с именем / и связывается с этим inode.



  • После этого последовательно считываются все inode в файловой системе и устанвливаются соответствующие им биты в битовой карте, а так же подсчитываются некоторые внутренние параметры, такие как смещение последнего inode и начало/конец блоков последнего файла. Все прочитанные inode возвращаются обратно в кэш inode вызовом iput() - ссылка на них не удерживается дольше, чем это необходимо.

  • Если файловая система была смонтирована как "read/write", то буфер суперблока помечается как "грязный" (измененный прим. перев.) и устанавливается флаг s->s_dirt (TODO: Для чего? Первоначально я сделал это потому, что это делалось в minix_read_super(), но ни minix ни BFS кажется не изменяют суперблок в read_super()).

  • Все складывается удачно, так что далее функция возвращает инициализированный суперблок уровню VFS, т.е. fs/super.c:read_super().

  • После успешного завершения работы функции read_super() VFS получает ссылку на модуль файловой системы через вызов get_filesystem(fs_type) в fs/super.c:get_sb_bdev() и ссылку на блочное устройство.
    Рассмотрим, что происходит при выполнении опреаций ввода/вывода над файловой системой. Мы уже знаем, что inode читается функцией iget() и что они освобождаются вызовом iput(). Чтение inode приводит, кроме всего прочего, к установке полей inode->i_op и inode->i_fop; открытие файла вызывает копирование inode->i_fop в file->f_op.
    Рассмотрим последовательность действий системного вызова link(2). Реализация системного вызова находится в fs/namei.c:sys_link():
  • Имена из пользовательского пространства копируются в пространство ядра функцией getname(), которая выполняет проверку на наличие ошибок.

  • Эти имена преобразуются в nameidata с помощью path_init()/path_walk(). Результат сохраняется в структурах old_nd и nd

  • Если old_nd.mnt != nd.mnt, то возвращается "cross-device link" EXDEV - невозможно установить ссылку между файловыми системами, в Linux это означает невозможность установить ссылку между смонтированными экземплярами одной файловой системы (или, особенно, между различными файловыми системами).



  • Для nd создается новый dentry вызовом lookup_create() .

  • Вызывается универсальная функция vfs_link(), которая проверяет возможность создания новой ссылки по заданному пути и вызывает метод dir->i_op->link(), который приводит нас в fs/bfs/dir.c:bfs_link().

  • Внутри bfs_link(), производится проверка - не делается ли попытка создать жесткую ссылку на директорию и если это так, то возвращается код ошибки EPERM. Это как стандарт (ext2).

  • Предпринимается попытка добавить новую ссылку в заданную директорию вызовом вспомогательной функции bfs_add_entry(), которая отыскивает неиспользуемый слот (de->ino == 0) и если находит, то записывает пару имя/inode в соответствующий блок и помечает его как "грязный".

  • Если ссылка была добавлена, то далее ошибки возникнуть уже не может, поэтому увеличивается inode->i_nlink, обновляется inode->i_ctime и inode помечается как "грязный" твк же как и inode приписанный новому dentry.

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

    Содержание раздела