Всім привіт, ділимося з вами другою частиною публікації "Віртуальні файлові системи в 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, із вставками C коду, який просто вважає виклики функцій 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