Всем привет, делимся с вами второй частью публикации «Виртуальные файловые системы в Linux: зачем они нужны и как они работают?» Первую часть можно прочитать
Как наблюдать за VFS с помощью инструментов eBPF и bcc
Самый простой способ понять, как ядро оперирует файлами sysfs
– это посмотреть за этим на практике, а самый простой способ понаблюдать за ARM64 – это использовать eBPF. eBPF (сокращение от Berkeley Packet Filter) состоит из виртуальной машины, запущенной в query
) из командной строки. Исходники ядра сообщают читателю, что может сделать ядро; запуск инструментов eBPF в загруженной системе показывает, что на самом деле делает ядро.
К счастью, начать использовать eBPF достаточно легко с помощью инструментов bcc
– это скрипты на Python с маленькими вставками кода на С, это означает, что каждый, кто знаком с обоими языками может с легкостью их модифицировать. В bcc/tools
есть 80 Python скриптов, а это значит, что скорее всего разработчик или системный администратор сможет подобрать себе что-нибудь подходящее для решения задачи.
Чтобы получить хотя бы поверхностное представление о том, какую работу выполняют VFS в запущенной системе, попробуйте vfscount
или vfsstat
. Это покажет, допустим, что десятки вызовов vfs_open()
и «его друзей» происходят буквально каждую секунду.
vfsstat.py
– это скрипт на Python, со вставками С кода, который просто считает вызовы функций VFS.
Приведем более тривиальный пример и посмотрим, что бывает, когда мы вставляем USB-флеш накопитель в компьютер и его обнаруживает система.
С помощью eBPF можно посмотреть, что происходит в
/sys
, когда вставлен USB-флеш накопитель. Здесь показан простой и сложный пример.
В примере, показанном сверху, bcc
инструмент sysfs_create_files()
. Мы видим, что sysfs_create_files()
был запущен с помощью kworker
потока в ответ на то, что флешка была вставлена, но какой файл при этом создался? Второй пример показывает всю мощь eBPF. Здесь trace.py
выводит обратную трассировку ядра (kernel backtrace) (опция -K) и имя файла, который был создан sysfs_create_files()
. Вставка в одиночных высказываниях – это код на С, включающий легко распознаваемую строку формата, обеспечиваемую Python скриптом, который запускает LLVM just-in-time компилятор. Эту строку он компилирует и выполняет в виртуальной машине внутри ядра. Полная сигнатура функции sysfs_create_files ()
должна быть воспроизведена во второй команде, чтобы строка формата могла ссылаться на один из параметров. Ошибки в этом фрагменте кода на С приводят к распознаваемым ошибкам C-компилятора. Например, если пропущен параметр -l, то вы увидите «Failed to compile BPF text.» Разработчики, которые хорошо знакомы с С и Python, найдут инструменты bcc
простыми для расширения и изменения.
Когда USB-накопитель вставлен, обратная трассировка ядра покажет, что PID 7711 – это поток kworker
, который создал файл «events»
в sysfs
. Соответственно, вызов с sysfs_remove_files()
покажет, что удаление накопителя привело к удалению файла events
, что соответствует общей концепции подсчета ссылок. При этом, просмотр sysfs_create_link ()
с eBPF во время вставки USB-накопителя покажет, что создано не менее 48 символьных ссылок.
Так в чем же смысл файла events? Использование disk_add_events ()
, и либо "media_change"
, либо "eject_request"
могут быть записаны в файл событий. Здесь блочный слой ядра информирует userspace о появлении и извлечении «диска». Обратите внимание, насколько информативен этот метод исследования на примере вставки USB-накопителя по сравнению с попытками выяснить, как все работает, исключительно из исходников.
Корневые файловые системы только для чтения делают возможными встроенные устройства
Конечно, никто не выключает сервер или свой компьютер, вытаскивая вилку из розетки. Но почему? А все потому что смонтированные файловые системы на физических устройствах хранения могут иметь отложенные записи, а структуры данных, записывающие их состояние, могут не синхронизироваться с записями в хранилище. Когда это случается, владельцам системы приходится ждать следующей загрузки для запуска утилиты fsck filesystem-recovery
и, в худшем случае, потерять данные.
Тем не менее, все мы знаем, что многие IoT устройства, а также маршрутизаторы, термостаты и автомобили теперь работают под управлением Linux. Многие из этих устройств практически не имеют пользовательского интерфейса, и нет никакого способа выключить их «чисто». Представьте себе запуск автомобиля с разряженной батареей, когда питание управляющего устройства на fsck
, когда двигатель наконец начинает работать? А ответ прост. Встроенные устройства полагаются на корневую файловую систему ro-rootfs
(read-only root fileystem)).
ro-rootfs
предлагают множество преимуществ, которые менее очевидны, чем неподдельность. Одно из преимуществ заключается в том, что вредоносное ПО не может писать в /usr
или /lib
, если ни один процесс Linux не может туда писать. Другое заключается в том, что в значительной степени неизменяемая файловая система имеет решающее значение для полевой поддержки удаленных устройств, поскольку вспомогательный персонал пользуется локальными системами, которые номинально идентичны системам на местах. Возможно, самым важным (но и самым коварным) преимуществом является то, что ro-rootfs заставляет разработчиков решать, какие системные объекты будут неизменными, еще на этапе проектирования системы. Работа с ro-rootfs может быть неудобной и болезненной, как это часто бывает с переменными const в языках программирования, но их преимущества легко окупают дополнительные накладные расходы.
Создание rootfs
только для чтения требует некоторых дополнительных усилий для разработчиков встраиваемых систем, и именно здесь на сцену выходит VFS. Linux требует, чтобы файлы в /var
были доступны для записи, и, кроме того, многие популярные приложения, которые запускают встроенные системы, будут пытаться создать конфигурационные dot-files
в $HOME
. Одним из решений для конфигурационных файлов в домашнем каталоге обычно является их предварительная генерация и сборка в rootfs
. Для /var
один из возможных подходов — это смонтировать его в отдельный раздел, доступный для записи, в то время как сам /
монтируется только для чтения. Другой популярной альтернативой является использование связываемых или накладываемых маунтов (bind or overlay mounts).
Связываемые и накладываемые маунты, использование их контейнерами
Выполнение команды man mount
– лучший способ узнать про связываемые и накладываемые маунты, которые дают разработчикам и системным администраторам возможность создавать файловую систему по одному пути, а затем предоставлять ее приложениям в другом. Для встроенных систем это означает возможность хранить файлы в /var
на флеш-накопителе, доступном только для чтения, но накладываемое или связываемое монтирование пути из tmpfs
в /var
при загрузке позволит приложениям записывать туда заметки (scrawl). При следующем включении изменения в /var
будут утеряны. Накладываемое монтирование создает объединение между tmpfs
и нижележащей файловой системой и позволяет делать якобы изменения существующих файлов в ro-tootf
тогда как связываемое монтирование может сделать новые пустые tmpfs
папки видимыми как доступные для записи в ro-rootfs
путях. В то время как overlayfs
это правильный (proper
) тип файловой системы, связываемое монтирование реализовано в
Основываясь на описании накладываемого и связываемого монтирования, никто не удивляется что mountsnoop
от bcc
.
Вызов system-nspawn
запускает контейнер во время работы mountsnoop.py
.
Посмотрим, что получилось:
Запуск mountsnoop
во время «загрузки» контейнера показывает, что среда выполнения контейнера сильно зависит от связываемого монтирования (Отображается только начало длинного вывода).
Здесь systemd-nspawn
предоставляет выбранные файлы в procfs
и sysfs
хоста в контейнер как пути в его rootfs
. Кроме MS_BIND
флага, который устанавливает связывающее монтирование, некоторые другие флаги в монтируемой системе определяют взаимосвязь между изменениями в пространстве имен хоста и контейнера. Например, связываемое монтирование может либо пропускать изменения в /proc
и /sys
в контейнер, либо скрывать их в зависимости от вызова.
Заключение
Понимание внутреннего устройства Linux может казаться невыполнимой задачей, так как само ядро содержит гигантское количество кода, оставляя в стороне приложения пользовательского пространства Linux и интерфейсы системных вызовов в библиотеках на языке C, таких как glibc
. Один из способов добиться прогресса — прочитать исходный код одной подсистемы ядра с акцентом на понимание системных вызовов и заголовков, обращенных к пространству пользователя, а также основных внутренних интерфейсов ядра, к примеру, таблица file_operations
. Файловые операции обеспечивают принцип «все является файлом», поэтому управление ими особенно приятно. Исходные файлы ядра на языке C в каталоге верхнего уровня fs/
представляют реализацию виртуальных файловых систем, которые являются слоем оболочки, обеспечивающим широкую и относительно простую совместимость популярных файловых систем и устройств хранения. Монтирование со связыванием и накладыванием через пространства имен Linux — это волшебство VFS, которое делает возможным создание контейнеров и корневых файловых систем только для чтения. В сочетании с изучением исходного кода, средство ядра eBPF и его интерфейс bcc
делают исследование ядра проще, чем когда-либо.
Друзья, напишите была ли эта статья полезной для вас? Возможно у вас есть какие-то комментарии или замечания? А тех, кому интересен курс «Администратор Linux», приглашаем на
Источник: habr.com