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

Пример виртуальной файловой системы: pipefs


В качестве простого примера файловой системы в Linux рассмотрим pipefs, которая не требует наличия блочного устройства для своего монтирования. Реализация pipefs находится в fs/pipe.c.

static DECLARE_FSTYPE(pipe_fs_type, "pipefs", pipefs_read_super, FS_NOMOUNT|FS_SINGLE);

static int __init init_pipe_fs(void) { int err = register_filesystem(&pipe_fs_type); if (!err) { pipe_mnt = kern_mount(&pipe_fs_type); err = PTR_ERR(pipe_mnt); if (!IS_ERR(pipe_mnt)) err = 0; } return err; }

static void __exit exit_pipe_fs(void) { unregister_filesystem(&pipe_fs_type); kern_umount(pipe_mnt); }

module_init(init_pipe_fs) module_exit(exit_pipe_fs)

Файловая система принадлежит к типу FS_NOMOUNT|FS_SINGLE это означает, что она не может быть смонтирована из пространства пользователя и в системе может иметься только один суперблок этой файловой системы. Флаг FS_SINGLE означает также что она должна монтироваться через kern_mount() после того как будет выполнена регистрация вызовом register_filesystem(), что собственно и выполняется функцией init_pipe_fs(). Единственная неприятность - если kern_mount()

завершится с ошибкой (например когда kmalloc(), вызываемый из add_vfsmnt() не сможет распределить память), то файловая система окажется зарегистрированной но модуль не будет инициализирован. Тогда команда cat /proc/filesystems повлечет за собой Oops. (передал Линусу "заплату", хотя это фактически не является ошибкой, поскольку на сегодняшний день pipefs не может быть скомпилирована как модуль, но в будущем вполне может быть добавлена взможность вынесения pipefs в модуль).

В результате register_filesystem(), pipe_fs_type добавляется к списку file_systems, который содержится в /proc/filesystems. Прочитав его, вы обнаружите "pipefs" с флагом "nodev", указывающим на то, что флаг FS_REQUIRES_DEV не был установлен. Следовало бы расширить формат файла /proc/filesystems с тем, чтобы включить в него поддержку всех новых FS_ флагов (и я написал такую "заплату"), но это невозможно, поскольку такое изменение может отрицательно сказаться на пользовательских приложениях, которые используют этот файл. Несмотря на то, что интерфейсы ядра изменяются чуть ли не ежеминутно, тем не менее когда вопрос касается пространства пользователя, Linux превращается в очень консервативную операционную систему, которая позволяет использование программ в течение длительного времени без необходимости их перекомпиляции.


В результате выполнения kern_mount():
  • Создается новое неименованное (anonymous) устройство установкой бита в unnamed_dev_in_use; если в этом массиве не окажется свободного бита, то kern_mount()
    вернется с ошибкой EMFILE.

  • Посредством get_empty_super() создается новая структура суперблока. Функция get_empty_super()
    проходит по списку суперблоков super_block в поисках свободного места, т.е. s->s_dev == 0. Если такового не обнаружилось, то резервируется память вызовом kmalloc(), с приоритетом GFP_USER. В get_empty_super() проверяется превышение максимально возможного количества суперблоков, так что в случае появления сбоев, при монтировании pipefs, можно попробовать подкорректировать /proc/sys/fs/super-max.

  • Вызывается метод pipe_fs_type->read_super()
    (т.е. pipefs_read_super()), который размещает корневой inode и dentry sb->s_root, а также записывает адрес &pipefs_ops в sb->s_op.

  • Затем вызывается add_vfsmnt(NULL, sb->s_root, "none"), которая размещает в памяти новую структуру vfsmount и включает ее в список vfsmntlist и sb->s_mounts.

  • В pipe_fs_type->kern_mnt заносится адрес новой структуры vfsmountи он же и возвращается в качестве результата. Причина, по которой возвращаемое значение является указателем на vfsmount состоит в том, что даже не смотря на флаг FS_SINGLE, файловая система может быть смонтирована несколько раз, вот только их mnt->mnt_sb будут указывать в одно и то же место.

  • После того как файловая система зарегистрирована и смонтирована, с ней можно работать. Точкой входа в файловую систему pipefs является системный вызов pipe(2), реализованный платформо-зависимой функцией sys_pipe(), которая в свою очередь передает управление платформо-независимой функции fs/pipe.c:do_pipe(). Взаимодействие do_pipe() с pipefs начинается с размещения нового inode вызовом get_pipe_inode(). В поле inode->i_sb этого inode заносится указатель на суперблок pipe_mnt->mnt_sb, в список i_fop файловых операций заносится rdwr_pipe_fops, а число "читателей" и "писателей" (содержится в inode->i_pipe) устанавливается равным 1. Причина, по которой имеется отдельное поле i_pipe, вместо хранения этой информации в приватной области fs-private, заключается в том, что каналы (pipes) и FIFO (именованные каналы) совместно используют один и тот же код, а FIFO могут существовать и в другой файловой системе, которые используют другие способы доступа в пределах этого объединения (fs-private) и могут работать, что называется "на удачу". Так, в ядре 2.2.x, все перестает работать, стоит только слегка изменить порядок следования полей в inode.
    Каждый системный вызов pipe(2) увеличивает счетчик ссылок в структуре pipe_mnt.
    В Linux каналы (pipes) не являются симметричными, т.е. с каждого конца канал имеет различный набор файловых операций file->f_op - read_pipe_fops и write_pipe_fops. При попытке записи со стороны канала, открытого на чтение, будет возвращена ошибка EBADF, то же произойдет и при попытке чтения с конца канала, открытого на запись.

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