Глава 7. systemd (Часть I)
Содержание
- Глава 7. systemd (Часть I)
- Структура
- Как systemd снижает время загрузки?
- Поток systemd внутри initramfs
Вот что нам известно относительно последовательности запуска к данному моменту:
-
Начальный загрузчик запускает в памяти необходимые ядро и initramfs.
-
Запущенное ядро будет загружено в определённом местоположении (неком зависящем от архитектуры месте), в то время как initramfs будет загружаться в любом доступном месте.
-
Это ядро распаковывает себя само применяя свой заголовок из файла
vmlinuz
. -
Установленное ядро распаковывает initramfs в оперативной памяти (
init/initramfs.c
) и монтирует её в качестве временной корневой файловой системы (/
) в основной памяти. -
Запущенное ядро запускает (
init/main.c
) соответствующий systemd в качестве самого первого процесса с PID-1 из временной корневой файловой системы. -
systemd обнаруживает необходимую корневую файловую систему пользователя и выполняет
chroot
в неё.
Данная глава направлена на то, как systemd, которая ответвляется из initramfs, управляет монтированием корневой файловой системы пользователя, а также мы увидим подробную последовательность запуска systemd из initramfs. Однако перед этим нам надлежит разобраться с systemd в качестве процесса.
Я предоставлю здесь слово странице руководства:
"После того как обнаружена и смонтирована корневая
файловая система initrd
передаёт управление системному диспетчеру хоста
(например, systemd(1)), хранимому в смонтированной корневой файловой системе, который далее отвечает за проверку
всего оставшегося оборудования, монтируя все необходимые файловые системы и порождая все настраиваемые службы."
systemd впервые был представлен в Fedora 15. Все мы знаем, что systemd выступил заменой для сценариев
init
(буквально, /sbin/init
это
теперь символическая ссылка на /usr/lib/systemd/systemd
) и впечатляющим
образом снизил время запуска. Однако, на практике sytemd намного больше простой замены для
init
. Вот что выполняет systemd:
-
Он сопровождает журналирование в
journalctl
. -
Он интенсивно применяет
cgroups
версии 1 и 2. -
Он снижает время запуска.
-
Он управляет элементами (units).
service
это всего лишь один из видов элементов, которые обрабатывает systemd. Ниже приводятся структурные элементы, которые предоставляются и управляются со стороны systemd:Таблица 7-1. Структурные элементы systemd Элемент Назначение systemd.service
Управление службой
systemd.socket
Создание сокета и управление им
systemd.device
Создание устройства и управление им на основании входных данных
udev
systemd.mount
Для монтирования определённой файловой системы
systemd.automount
Для автоматического монтирования определённой файловой системы
systemd.swap
Построение устройств подкачки и управление ими
systemd.target
Группирование служб вместо runlevels
systemd.path
Сведения относительно отслеживаемого systemd пути для основанной на пути активации
systemd.timer
Для активации на основании таймера
systemd.slice
Управление для элементов служб такими ресурсами как ЦПУ, память, ввод/ вывод
Файлы структурных элементов будут храниться и загружаться из следующих местоположений:
Элемент | Назначение |
---|---|
|
Локальная конфигурация |
|
Элементы времени исполнения |
|
Устанавливаемые пакетами элементы |
/etc/systemd/system
это местоположение некого администратора, в то
время как /usr/lib/systemd/system
это местоположение производителя
приложения. Это подразумевает, что местоположение администратора получит преимущество перед местоположением
производителя приложения когда один и тот же файл элемента присутствует в обоих местоположениях. Обратите,
пожалуйста, внимание, что в этой главе все команды выполняются из каталога из которого был извлечён initramfs.
# tree etc/systemd/
etc/systemd/
├── journald.conf
└── system.conf
0 directories, 2 files
#ls usr/lib/systemd/system | column
basic.target plymouth-switch-root.service
cryptsetup.target poweroff.target
ctrl-alt-del.target poweroff.target.wants
default.target reboot.target
dracut-cmdline-ask.service reboot.target.wants
dracut-cmdline.service remote-fs-pre.target
dracut-emergency.service remote-fs.target
dracut-initqueue.service rescue.service
dracut-mount.service rescue.target
dracut-pre-mount.service rescue.target.wants
dracut-pre-pivot.service rpcbind.target
dracut-pre-trigger.service shutdown.target
dracut-pre-udev.service sigpwr.target
emergency.service slices.target
emergency.target sockets.target
emergency.target.wants sockets.target.wants
final.target swap.target
halt.target sysinit.target
halt.target.wants sysinit.target.wants
initrd-cleanup.service sys-kernel-config.mount
initrd-fs.target syslog.socket
initrd-parse-etc.service systemd-ask-password-console.path
initrd-root-device.target systemd-ask-password-console.service
initrd-root-fs.target systemd-ask-password-console.service.wants
initrd-switch-root.service systemd-ask-password-plymouth.path
initrd-switch-root.target systemd-ask-password-plymouth.service
initrd-switch-root.target.wants systemd-ask-password-plymouth.service.wants
initrd.target systemd-fsck@.service
initrd.target.wants systemd-halt.service
initrd-udevadm-cleanup-db.service systemd-journald-audit.socket
kexec.target systemd-journald-dev-log.socket
kexec.target.wants systemd-journald.service
kmod-static-nodes.service systemd-journald.socket
local-fs-pre.target systemd-kexec.service
local-fs.target systemd-modules-load.service
multi-user.target systemd-poweroff.service
multi-user.target.wants systemd-random-seed.service
network-online.target systemd-reboot.service
network-pre.target systemd-sysctl.service
network.target systemd-tmpfiles-setup-dev.service
nss-lookup.target systemd-tmpfiles-setup.service
nss-user-lookup.target systemd-udevd-control.socket
paths.target systemd-udevd-kernel.socket
plymouth-halt.service systemd-udevd.service
plymouth-kexec.service systemd-udev-settle.service
plymouth-poweroff.service systemd-udev-trigger.service
plymouth-quit.service systemd-vconsole-setup.service
plymouth-quit-wait.service timers.target
plymouth-reboot.service umount.target
plymouth-start.service
Третье местоположение, /run/systemd/system
, это временное местоположение
и оно будет применяться внутренним образом systemd для управления элементами. Например, оно будет интенсивно
использоваться при создании необходимых сокетов. На практике /run
это отдельная файловая система, вводимая systemd для хранения данных времени исполнения. На данный момент этот
каталог /run
initramfs пустой, что очевидно, поскольку initramfs не
применяется.
#ls run/
<no_output>
Кроме того, ожидается что имеется несколько файлов элементов, которые присутствуют в initramfs, которые являются
тем, что доступно в имеющейся корневой файловой системе пользователя. Dracut собирает лишь те файлы элементов
systemd, которые необходимы для монтирования этой корневой файловой системы пользователя. Например, не имеет смысла
добавлять в initramfs файлы элементов systemd, относящиеся к httpd
и mysql
. Давайте попробуем разобраться с одним из файлов элементов
service
systemd, как это показано здесь:
# cat /usr/lib/systemd/system/sshd.service
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.target
Wants=sshd-keygen.target
[Service]
Type=notify
EnvironmentFile=-/etc/crypto-policies/back-ends/opensshserver.config
EnvironmentFile=-/etc/sysconfig/sshd-permitrootlogin
EnvironmentFile=-/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS $CRYPTO_POLICY $PERMITROOTLOGIN
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s
[Install]
WantedBy=multi-user.target
Этот файл элемента службы sshd
не будет частью initramfs, так как
вам не требуется служба ssh
при монтировании корневой файловой системы
пользователя. Файл элемента service
подразделяется на три части:
[unit]
, [service]
,
[install]
.
-
[unit]
- After=network.target sshd-keygen.target
Служба
sshd
будет запущена только когда будут успешно запущеныnetwork.target
(перечисляемые элементы) иsshd-keygen
(перечисляемые элементы)ю Если одна из них откажет, тогда службаsshd
также завершится неудачно.- Wants=sshd-keygen.target
Это менее строгая версия
Requires
. Когда отказывает любой из указанных вwants
элементов, тогда также будет запущена службаsshd
(или эта конкретная служба), в то время какRequires
службыsshd
будет запущена только когда упоминаемые вRequires
элементы успешно стартовалиBefore
выступает противоположностьюAfter
. ВсеWants
,After
,Before
иRequires
работают независимо друг от друга. Распространённой практикой является совместное применениеWants
иAfter
.- Conflicts=
Может применяться для перечисления тех элементов, которые конфликтуют с текущим элементом. Запуск данного элемента способен остановить такие перечисленные конфликтующие элементы.
- OnFailure=
Элементы
OnFailure
запускаются только если всякий заданный элемент достигает состояния отказа.
-
[Service]
- ExecStart=/usr/sbin/sshd
Запуск элемента службы
sshd
просто стартует упоминаемый послеExecStart
исполняемый файл.
-
[Install]
Данный раздел
Install
некого файла элемента не используется systemd. Вместо этого он применяется командойsystemctl
enable
илиdisable
. Он будет использованsystemctl
для создания или ликвидации соответствующих символических ссылок.
Леннарт Поеттеринг, создатель systemd, представил классический пример того как systemd снижает общее время запуска в своём блоге. Этот блог является одним из наилучших ресурсов когда вы желаете поглубже вникнуть в мир самого systemd.
Имеются четыре демона: syslog
, dbus
,
avahi
и bluetooth
.
syslog
требуется всем демонам для регисрации своих сообщений. Поэтому
syslog
выступает в качестве необходимого для всех прочих демонов.
avahi
нуждается в запущенных syslog
dbus
. Для bluetooth
требуются
запущенными dbus
и syslog
,
однако avahi
не должен исполняться. При использовании модели
Sysv/init
происходит следующее:
-
Первым запускается
syslog
. -
Когда он успешно готов, будет запущена служба
dbus
. -
После
dbus
будет запущенаavahi
. -
Наконец, будет запускаться служба
bluetooth
. Смотрите Рисунок 7-1.
bluetooth
и avahi
не
зависят друг от друга, однако bluetooth
приходится дожидаться пока не
будет запущена avahi
. Подобные Ubuntu дистрибутивы применяют вместо
init
upstart
, который улучшает до
некоторой степени значение времени запуска. В upstart
те службы, которые
не зависят друг от друга будут запускаться параллельно, что означает что avahi
и bluetooth
запустятся совместно. Для справки обратитесь к
Рисунку 7-2.
В systemd
все имеющиеся службы запускаются в одно и то же самое время
при помощи sockets
. Вот некий пример:
-
systemd
создаст некий сокет дляsyslog
, который был заменёнjournald
. -
Сокет
/dev/log
является символической ссылкой на/run/systemd/journal/dev-log
.# file /dev/log /dev/log: symbolic link to /run/systemd/journal/dev-log # file /run/systemd/journal/dev-log /run/systemd/journal/dev-log: socket
Как уже упоминалось ранее, для создания файла сокета systemd будет применяться файловая система
run
. -
Для
dbus
в/run/dbus/system_bus_socket
создаётся его сокет. Для запускаdbus
нуждается в запущенномjournald
, но так как данная система всё ещё запускается иjournald/syslog
ещё пока не полностью запущен,dbus
будет регистрировать свои сообщения в сокетеjournald
dbus
, а когдаjournald
полностью готов, он осуществит выборку всех сообщений из этого сокета. -
То же самое касается и службы
bluetooth
; для её запуска требуется запущенной соответствующая службаdbus
. Поэтому systemd, перед тем как будет запущена службаdbus
, создаст сокет/run/dbus/system_bus_socket
. Службаbluetooth
не будет дожидаться запускаdbus
. Для лучшего понимания вы можете обратиться к Рисунку 7-3.
-
Если у созданного
systemd
сокета заканчивается буфер, тогда службаbluetooth
будет заблокирована до тех пор пока не станет доступным её сокет. Такой подход с сокетами существенным образом снижает значение времени запуска.
Подход на основе сокетов первоначально был предпринят в macOS. В то время он носил название
launchd
Из него и почерпнул вдохновение Леннарт Поеттеринг.
systemd предоставляет инструмент systemd-analyze
для проверки значения
времени, требующегося системе для запуска.
# systemd-analyze
Startup finished in 1.576s (kernel) + 1.653s (initrd) + 11.574s (userspace) = 14.805s
graphical.target reached after 11.561s in userspace
Как вы можете видеть, моей системе Fedora требуется 1.5 секунды для инициализации своего ядра; далее она тратит 1.6 секунды внутри initramfs и занимает почти 11 секунд для запуска необходимых служб или инициализации необходимого пространства пользователя. Общее время занимало почти 15 секунд. Значение общего времени вычисляется прямо из начального загрузчика в общую графическую цель.
Вот некоторые важные соображения:
-
Общее время не содержит значений времён, занимаемых средами рабочего стола, такими как GNOME, KDE, Cinnamon и т.п.. Это на самом деле имеет смысл, поскольку среды рабочего стола не обрабатываются systemd, поэтому инструмент systemd не способен рассчитать значение времени, затрачиваемое средами рабочего стола.
-
Кроме того, имеется вероятность, что по причине подхода systemd с сокетами, службы всё ещё запускаются даже по истечению общего времени (14.805 секунд).
Итак, чтобы предоставить более информативные и чистые сведения systemd-analyse
предоставляет инструментарий blame
(ответственность).
# systemd-analyze blame
31.202s dnf-makecache.service
10.517s pmlogger.service
9.264s NetworkManager-wait-online.service
4.977s plymouth-switch-root.service
2.994s plymouth-quit-wait.service
1.674s systemd-udev-settle.service
1.606s lightdm.service
1.297s pmlogger_check.service
938ms docker.service
894ms dracut-initqueue.service
599ms pmcd.service
590ms lvm2-monitor.service
568ms abrtd.service
482ms firewalld.service
461ms systemd-logind.service
430ms lvm2-pvscan@259:3.service
352ms initrd-switch-root.service
307ms bolt.service
290ms systemd-machined.service
288ms registries.service
282ms udisks2.service
269ms libvirtd.service
255ms sssd.service
209ms systemd-udevd.service
183ms systemd-journal-flush.service
180ms docker-storage-setup.service
169ms systemd-journald.service
156ms polkit.service
.
.
</snip>
Вывод blame
может запросто запутать; т.е. две службы могут инициализироваться
в одно и то же время, а следовательно значение затрачиваемого на инициализацию обеих служб время намного меньше
чем сумма сочетания обоих индивидуальных значений времени. Для более точных сведений вы можете воспользоваться
инструментарием plot
systemd-analyse
,
который выработает свой график и предоставит намного больше подробностей относительно времени запуска. На
Рисунке 7-4
вы можете видеть такой сгенерированный график запуска.
# systemd-analyze plot > plot.svg
# eog plot.svg
Далее приводятся некоторые из прочих инструментариев, предоставляемых systemd-analyse
,
которые можно применять для выявления времени запуска.
systemd-analyze <tool> | Описание |
---|---|
|
Выводит на печать время, затрачиваемое ядром |
|
Выводит на печать список запущенных элементов, упорядоченных по времени
на |
|
Выводит на печать дерево своих критичных ко времени цепочек элементов |
|
Выводит SVG график, отображающий инициализацию служб |
|
Выводит график зависимостей в формате
|
|
Выводит на печать список запущенных элементов, упорядоченных по времени
на |
|
Получает/ устанавливает пороговые значения регистрации для диспетчера |
|
Получает/ устанавливает цель регистрации для диспетчера |
|
Выводит состояние сериализации диспетчера служб |
|
Отображает файл настроек и вставки |
|
Перечисляет файлы и символические ссылки для элементов |
|
Перечисляет каталоги загрузки для элементов |
|
Перечисляет определения состояний выхода |
|
Выводит на печать список syscalls в фильтре seccomp |
|
Выполняет оценку условий и объявлений |
|
Проверяет правильность файлов элемента |
|
Получает/ устанавливает состояние сторожевой схемы службы |
|
Проверяет повторяемые по календарю события |
|
Проверяет значение временной отметки |
|
Проверяет временной интервал |
|
Анализирует безопасность элемента |
Проблема:: Система успешно запускается, однако служба
nagios
отказывает при запуске в момент загрузки.
Вот основные этапы для решения данной проблемы:
-
Для начала нам требуется изолировать данную проблему. при появлении на своём экране GRUB удалите параметры командной строки ядра
rhgb quiet
. -
Детализированный журнал показывает что данная система способна запускаться, однако служба
nagios
отказывает в старте при запуске. Как вы можете видеть, ответственная за сетевую среду служба systemdNetworkManager
успешно запустилась. Это означает что нет проблем с сетевым взаимодействием.13:23:52 systemd: Starting Network Manager... 13:23:52 systemd: Started Kernel Samepage Merging (KSM) Tuning Daemon. 13:23:52 systemd: Started Install ABRT coredump hook. 13:23:52 abrtd: Init complete, entering main loop 13:23:52 systemd: Started Load CPU microcode update. 13:23:52 systemd: Started Authorization Manager. 13:23:53 NetworkManager[1356]: <info> [1534389833.1078] NetworkManager is starting... (for the first time) 13:23:53 NetworkManager[1356]: <info> [1534389833.1079] Read config: /etc/NetworkManager/NetworkManager.conf (lib: 00-server.conf, 10-slaves-order.conf) 13:23:53 NetworkManager[1356]: <info> [1534389833.1924] manager[0x558b0496a0c0]: monitoring kernel firmware directory '/lib/firmware'. 13:23:53 NetworkManager[1356]: <info> [1534389833.2051] dns-mgr[0x558b04971150]: init: dns=default, rc-manager=file 13:23:53 systemd: Started Network Manager.
-
Служба
nagios
предпринимает попытку выполнения сразу после службыNetworkManager
. Это означает, чтоnagios
должен иметь в своём файле элемента службы упоминаниеafter=Network.target
. Однако службеnagios
не удаётся стартовать.13:24:03 nagios: Nagios 4.2.4 starting... (PID=5006) 13:24:03 nagios: Local time is Thu 13:24:03 AEST 2018 13:24:03 nagios: LOG VERSION: 2.0 13:24:03 nagios: qh: Socket '/usr/local/nagios/var/rw/nagios.qh' successfully initialized 13:24:03 nagios: qh: core query handler registered 13:24:03 nagios: nerd: Channel hostchecks registered successfully 13:24:03 nagios: nerd: Channel servicechecks registered successfully 13:24:03 nagios: nerd: Channel opathchecks registered successfully 13:24:03 nagios: nerd: Fully initialized and ready to rock! Nagios Can't ping devices (not 100% packet loss at the end of each line) 13:24:04 nagios: HOST ALERT: X ;DOWN;SOFT;1;CRITICAL - X: Host unreachable @ X. rta nan, lost 100%
Решение: странность состоит в том, что сообщение об
ошибке nagios
утверждает что она отказалась запускаться по причине того что не
способна осуществить подключение с сетевой средой, а что касается NetworkManager
,
она успешно запущена и данная система уже была помещена в сетевую среду.
Ясно, что данная проблема создаётся подходом "ускоренной процедуры запуска" sytemd. Для помещения своей системы в сетевой среде systemd должен осуществить большую работу: проинициализировать свои сетевые карты, активировать полученное соединение, поместить с соответствующую карту NIC значение IP, убедиться что нет уже доступным никакого дублирующего IP, начать взаимодействие с сетевой средой и т.п. Очевидно, что для завершения всех этих подробностей systemd потребуется какое- то время. В моей тестовой системе требуется почти 20 секунд для полного заполнения данных сетевой среды. Естественно, systemd не имеет возможности приостанавливать свою последовательность запуска на всё это время. Если systemd дожидается пока сетевые сведения не заполнятся целиком, тогда одна из основных сторон инновации systemd по ускорению процесса запуска будет утрачена.
systemd при помощи NetworkManager
сделает всё возможное чтобы убедиться что мы
находимся в сетевой среде, но он не будет дожидаться порождения определяемой пользователем сетевой среды и не будет
топтаться пока будут достигнуты все прамила топологии.
При некоторых подобных данным обстоятельствах проблемы "can’t boot", может быть возможным, что
NetworkManager
требует от systemd проинициализировать
nagios
, который имеет зависимость от network.target
,
однако сама сетевая среда ещё пока поднята не полностью, а потому nagios
может оказаться не способным взаимодействовать со своими серверами.
-
Для решения такой проблемы systemd предлагает включать
NetworkManager-wait-online.service
. Эта служба предпримет ожиданиеNetworkManager
пока сетевая среда не поднимется полностью. После того как необходимая сетевая среда полностью наполнена,NetworkManager
сигнализирует systemd запускать все службы, которые зависят отnetwork.target
.# cat /usr/lib/systemd/system/NetworkManager-wait-online.service [Unit] Description=Network Manager Wait Online Documentation=man:nm-online(1) Requires=NetworkManager.service After=NetworkManager.service Before=network-online.target [Service] Type=oneshot ExecStart=/usr/bin/nm-online -s -q --timeout=30 RemainAfterExit=yes [Install] WantedBy=network-online.target
Здесь просто вызывается исполняемый файл
nm-online
и в него передаётся переключатель-s
. Данная служба будет удерживатьNetworkManager
не более 30 секунд.Вот что сообщает об
nm-online
страница руководства:"Дождитесь окончательного завершения запуска NetworkManager вместо того чтобы дожидаться подключения к сетевой среде. Запуск считается оконченным после того как NetworkManager активировал (или попытался активировать) все автоматически активируемые подключения, которые доступны в данном текущем состоянии сетевой среды. (Как правило, это полезно только в момент запуска, по окончанию запуска nm-online -s сразу же осуществит возврат, причём вне зависимости от значения текущего состояния сети.)"
-
После включения
NetworkManager-wait-online-service
данная проблема была разрешена, но время запуска слегка снизилось. Как вы можете видеть из Рисунка 7-5, большая часть времени запуска была съеденаNetworkManager-wait-online-service
, который выполняет ожидание.
>
systemd предоставляет ещё один инструмент, bootchart
, который в целом
является демоном, через который вы можете проводить анализ производительности процесса запуска Linux. Он будет
собирать сведения времени запуска и составлять ни их основе некий график. Вы можете рассматривать
bootchart
в качестве некоторой расширенной версии
systemd-analyze plot
. Для применения данного инструмента, как это показано на
Рисунке 7-6,
вам потребуется передать значение полного пути исполняемого файла systemd-bootchart
в параметр командной строки ядра init
.
После успешного процесса запуска, как это можно видеть на
Рисунке 7-7,
данный инструмент создаст подробный графический образ в /run/log/bootchart*
.
После выработки этого образа, systemd-bootchart
передаст управление в
systemd, а systemd продолжит свою процедуру запуска.
Поскольку мы теперь разобрались с основами systemd, мы можем продолжить своб приостановленную последовательность запуска. На данный момент мы достигли того этапа, на котором наше ядро распаковало initramfs в ОЗУ и запустило из него исполняемый файл systemd. После запуска процесса systemd, он будет следовать обычной последовательности запуска.
systemd будет запущен из initramfs и протекает показанной на Рисунке 7-8 последовательностью запуска. Гаральд Хойер (создавший dracut initramfs и являющийся ведущим разработчиком systemd) разработал эту блок- схему, которая также доступна и на страницах руководства systemd.
Эта блок- схема взята со страницы руководства dracut. Окончательная цель systemd состоит в процедуре запуска и
монтирования корневой файловой системы пользователя внутри initramfs (sysroot
)
с последующим переключением в неё. После того как systemd был switch_root
ed
в новую корневую файловую систему (пользователя), он покидает свою среду initramfs и продолжает свою процедуру
запуска стартуя службы пространства пользователя, такие как httpd
,
mysql
и т.п.. Он также отрисует рабочий стол/ GUI если его пользователь
запускает данную систему в графическом режиме. Сфера действий данной книги заключается в охвате последовательности
запуска вплоть до монтирования systemd своей корневой файловой системы пользователя и последующего переключения в неё.
Существует ряд причин не охватывать эту последовательность запуска после
switch_root
. Я упоминаю тут те причины, которые очень важны:
-
Окончательная цель запуска состоит в монтировании корневой файловой системы пользователя и присутствия в ней самого пользователя, которые и рассматривает подробно эта книга.
-
Все действия systemd, выполняемые после initramfs просто понять, так как systemd предпринимает подобные действия, просто в новой среде корневой файловой системы.
-
Промышленные системы, как правило, не запускаются в графическом режиме.
-
Linux обладает несколькими рабочими столами, такими как GNOME, KDE, Cinnamon, Unity и т.п.. Всякий пользователь имеет свой собственный предпочтительный ему рабочий стол и практически невозможно документировать все этапы каждого из рабочих столов при запуске.
Итак, осознавая всё это, в данной главе мы рассмотрим всю последовательность запуска вплоть до
basic.target
. Рассмотрите, пожалуйста,
Рисунок 7-9.
Все процессы обязаны регистрировать свои сообщения. На самом деле, процесс, служба или демон запустятся только если
имеется возможность регистрации его сообщений в механизме журналирования самой ОС. В наши дни таким механизмом ведения
журналов ОС выступает journald
. Таким образом, очевидно, что служба
journald
должна запускаться первой, однако, как мы знаем, systemd не желает
дожидаться окончательного запуска своих служб. Для ускорения данной процедуры он применяет подход сокета. Следовательно,
systemd обязан запускать сокеты journald
первыми. Эта служба
journald
создаёт четыре таких сокета и ожидает в них сообщения:
-
systemd-journald.socket
-
systemd-journald-dev-log.socket
-
systemd-journald-audit.socket
-
syslog.socket
Эти сокеты будут применяться демонами, приложениями и всеми процессами для регистрации их сообщений.
# # vim usr/lib/systemd/system/systemd-journald.socket
# SPDX-License-Identifier: LGPL-2.1+
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Journal Socket
Documentation=man:systemd-journald.service(8) man:journald.conf(5)
DefaultDependencies=no
Before=sockets.target
# Mount and swap units need this. If this socket unit is removed by an
# isolate request the mount and swap units would be removed too,
# hence let's exclude this from isolate requests.
IgnoreOnIsolate=yes
[Socket]
ListenStream=/run/systemd/journal/stdout
ListenDatagram=/run/systemd/journal/socket
SocketMode=0666
PassCredentials=yes
PassSecurity=yes
ReceiveBuffer=8M
Service=systemd-journald.service
# cat usr/lib/systemd/system/systemd-journald-dev-log.socket | grep -v '#'
[Unit]
Description=Journal Socket (/dev/log)
Documentation=man:systemd-journald.service(8) man:journald.conf(5)
DefaultDependencies=no
Before=sockets.target
IgnoreOnIsolate=yes
[Socket]
Service=systemd-journald.service
ListenDatagram=/run/systemd/journal/dev-log
Symlinks=/dev/log
SocketMode=0666
PassCredentials=yes
PassSecurity=yes
ReceiveBuffer=8M
SendBuffer=8M
Мы уже обсуждали тот способ, которым работают сокеты, в частности, сокет /dev/log
наш следующий шаг в последовательности запуска это dracut-cmdline.service
.
После инициализации сокетов journald
systemd собирает все параметры
командной строки ядра, такие как переменные root
,
rflags
и fstype
через
usr/lib/systemd/system/dracut-cmdline.service
Это также имеет название специальной точки входа cmdline, о которой
упоминалось в конце Главы 6. Эта специальная точка входа может
вызываться передачей значения cmdline
в rd.break
(параметр командной строки dracut). Мы изучим этот этап своего процесса запуска с применением специальной точки
входа cmdline
. Нам требуется передать значение параметра командной строки
dracut rd.break=cmdline
в своё ядро в момент его запуска.
Внутри initramfs systemd вызывает эту специальную точку входа из
usr/lib/systemd/system/dracut-cmdline.service
.
# cat usr/lib/systemd/system/dracut-cmdline.service
# This file is part of dracut.
#
# See dracut.bootup(7) for details
[Unit]
Description=dracut cmdline hook
Documentation=man:dracut-cmdline.service(8)
DefaultDependencies=no
Before=dracut-pre-udev.service
After=systemd-journald.socket
Wants=systemd-journald.socket
ConditionPathExists=/usr/lib/initrd-release
ConditionPathExistsGlob=|/etc/cmdline.d/*.conf
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/cmdline
ConditionKernelCommandLine=|rd.break=cmdline
ConditionKernelCommandLine=|resume
ConditionKernelCommandLine=|noresume
Conflicts=shutdown.target emergency.target
[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-cmdline
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes
# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash
# terminates cleanly.
KillSignal=SIGHUP
Как вы можете видеть, systemd обладает вызываемым сценарием dracut-cmdline
.
Это сценарий доступен в самом initramfs, и он будет собирать параметры командной строки своего ядра.
# vim bin/dracut-cmdline
24 # Get the "root=" parameter from the kernel command line, but differentiate
25 # between the case where it was set to the empty string and the case where it
26 # wasn't specified at all.
27 if ! root="$(getarg root=)"; then
28 root_unset='UNSET'
29 fi
30
31 rflags="$(getarg rootflags=)"
32 getargbool 0 ro && rflags="${rflags},ro"
33 getargbool 0 rw && rflags="${rflags},rw"
34 rflags="${rflags#,}"
35
36 fstype="$(getarg rootfstype=)"
37 if [ -z "$fstype" ]; then
38 fstype="auto"
39 fi
40
41 export root
42 export rflags
43 export fstype
44
45 make_trace_mem "hook cmdline" '1+:mem' '1+:iomem' '3+:slab' '4+:komem'
46 # run scriptlets to parse the command line
47 getarg 'rd.break=cmdline' -d 'rdbreak=cmdline' && emergency_shell -n cmdline "Break before cmdline"
48 source_hook cmdline
49
50 [ -f /lib/dracut/parse-resume.sh ] && . /lib/dracut/parse-resume.sh
51
52 case "${root}${root_unset}" in
53 block:LABEL=*|LABEL=*)
54 root="${root#block:}"
55 root="$(echo $root | sed 's,/,\\x2f,g')"
56 root="block:/dev/disk/by-label/${root#LABEL=}"
57 rootok=1 ;;
58 block:UUID=*|UUID=*)
59 root="${root#block:}"
60 root="block:/dev/disk/by-uuid/${root#UUID=}"
61 rootok=1 ;;
62 block:PARTUUID=*|PARTUUID=*)
63 root="${root#block:}"
64 root="block:/dev/disk/by-partuuid/${root#PARTUUID=}"
65 rootok=1 ;;
66 block:PARTLABEL=*|PARTLABEL=*)
67 root="${root#block:}"
68 root="block:/dev/disk/by-partlabel/${root#PARTLABEL=}"
69 rootok=1 ;;
70 /dev/*)
71 root="block:${root}"
72 rootok=1 ;;
73 UNSET|gpt-auto)
74 # systemd's gpt-auto-generator handles this case.
75 rootok=1 ;;
76 esac
77
78 [ -z "${root}${root_unset}" ] && die "Empty root= argument"
79 [ -z "$rootok" ] && die "Don't know how to handle 'root=$root'"
80
81 export root rflags fstype netroot NEWROOT
82
83 export -p > /dracut-state.sh
84
85 exit 0
В целом имеется три параметра (параметра командной строки ядра), которые будут экспортированы в эту специальную точку входа:
-
root = название корневой файловой системы пользователя
-
rflags = флаги корневой файловой системы пользователя (
ro
илиrw
) -
fstype = Auto (автоматическое монтирование или нет)
Давайте рассмотрим как эти параметры обнаруживаются iniytramfs (или в специальной точке входа cmdline initramfs).
Для получения этих трёх параметров командной строки ядра будет использоваться функция с названием
getarg
.
root="$(getarg root=)
rflags="$(getarg rootflags=)
fstype="$(getarg rootfstype=)"
. .
export root
export rflags
export fstype
Функция getarg
определяется в файле initramfs
usr/lib/dracut-lib.sh
.
#vim usr/lib/dracut-lib.sh
201 getarg() {
202 debug_off
203 local _deprecated _newoption
204 while [ $# -gt 0 ]; do
205 case $1 in
206 -d) _deprecated=1; shift;;
207 -y) if _dogetarg $2 >/dev/null; then
208 if [ "$_deprecated" = "1" ]; then
209 [ -n "$_newoption" ] && warn "Kernel command line option '$2' is deprecated, use '$_newoption' instead." || warn "Option '$2' is deprecated."
210 fi
211 echo 1
212 debug_on
213 return 0
214 fi
215 _deprecated=0
216 shift 2;;
217 -n) if _dogetarg $2 >/dev/null; then
218 echo 0;
219 if [ "$_deprecated" = "1" ]; then
220 [ -n "$_newoption" ] && warn "Kernel command line option '$2' is deprecated, use '$_newoption=0' instead." || warn "Option '$2' is deprecated."
221 fi
222 debug_on
223 return 1
224 fi
225 _deprecated=0
226 shift 2;;
227 *) if [ -z "$_newoption" ]; then
228 _newoption="$1"
229 fi
230 if _dogetarg $1; then
231 if [ "$_deprecated" = "1" ]; then
232 [ -n "$_newoption" ] && warn "Kernel command line option '$1' is deprecated, use '$_newoption' instead." || warn "Option '$1' is deprecated."
233 fi
234 debug_on
235 return 0;
236 fi
237 _deprecated=0
238 shift;;
239 esac
240 done
241 debug_on
242 return 1
243 }
Данная функция getarg
вызывает функцию _dogetarg
из того же самого файла.
165 _dogetarg() {
166 local _o _val _doecho
167 unset _val
168 unset _o
169 unset _doecho
170 CMDLINE=$(getcmdline)
171
172 for _o in $CMDLINE; do
173 if [ "${_o%%=*}" = "${1%%=*}" ]; then
174 if [ -n "${1#*=}" -a "${1#*=*}" != "${1}" ]; then
175 # if $1 has a "=<value>", we want the exact match
176 if [ "$_o" = "$1" ]; then
177 _val="1";
178 unset _doecho
179 fi
180 continue
181 fi
182
183 if [ "${_o#*=}" = "$_o" ]; then
184 # if cmdline argument has no "=<value>", we assume "=1"
185 _val="1";
186 unset _doecho
187 continue
188 fi
189
190 _val="${_o#*=}"
191 _doecho=1
192 fi
193 done
194 if [ -n "$_val" ]; then
195 [ "x$_doecho" != "x" ] && echo "$_val";
196 return 0;
197 fi
198 return 1;
199 }
Затем функция _dogetarg
вызывает функцию с названием
getcmdline
, которая собирает все реальные параметры командной строки
ядра из /proc/cmdline
.
137 getcmdline() {
138 local _line
139 local _i
140 local CMDLINE_ETC_D
141 local CMDLINE_ETC
142 local CMDLINE_PROC
143 unset _line
144
145 if [ -e /etc/cmdline ]; then
146 while read -r _line || [ -n "$_line" ]; do
147 CMDLINE_ETC="$CMDLINE_ETC $_line";
148 done </etc/cmdline;
149 fi
150 for _i in /etc/cmdline.d/*.conf; do
151 [ -e "$_i" ] || continue
152 while read -r _line || [ -n "$_line" ]; do
153 CMDLINE_ETC_D="$CMDLINE_ETC_D $_line";
154 done <"$_i";
155 done
156 if [ -e /proc/cmdline ]; then
157 while read -r _line || [ -n "$_line" ]; do
158 CMDLINE_PROC="$CMDLINE_PROC $_line"
159 done </proc/cmdline;
160 fi
161 CMDLINE="$CMDLINE_ETC_D $CMDLINE_ETC $CMDLINE_PROC"
162 printf "%s" "$CMDLINE"
163 }
Вот последовательность запуска к данному моменту:
-
Наш начальный загрузчик собирает параметры командной строки от своего пользователя и хранимые им в его собственном файле настроек (
grub.cfg
). -
Он передаст эти параметры командной строки в само ядро заполняя заголовок самого ядра.
-
Запущенное ядро распаковывает себя само и копирует обнаруженные в заголовке ядра параметры командной строки.
-
Это ядро распаковывает initramfs в память и применяет его в качестве временной корневой файловой системы.
-
В той же самой процедуре наше ядро подготавливает виртуальные файловые системы, такие как
proc
,sys
,dev
devpts
,shm
и т.п.. -
Запущенное ядро сохраняет параметры своей командной строки в файле
/proc/cmdline
. -
systemd
собирает параметры командной строки своего ядра считывая файл/proc/cmdline
и сохраняя их в переменныхroot
,rootfs
иfstype
.
Мы можем проверить эту процедуру при помощи специальной точки входа cmdline
.
Возвращаясь к сценарию /bin/dracut-cmdline
давайте рассмотрим:
41 export root
42 export rflags
43 export fstype
44
45 make_trace_mem "hook cmdline" '1+:mem' '1+:iomem' '3+:slab' '4+:komem'
46 # run scriptlets to parse the command line
47 getarg 'rd.break=cmdline' -d 'rdbreak=cmdline' && emergency_shell -n cmdline "Break before cmdline"
48 source_hook cmdline
49
50 [ -f /lib/dracut/parse-resume.sh ] && . /lib/dracut/parse-resume.sh
Это условие сообщает, что когда пользователь передал этот параметр rd.break=cmdline
в раздел ядра GRUB, затем выполняет функцию emergency_shell
.
Рисунок 7-10
показывает данное условие.
Когда наш пользователь передал rd.break=cmdline
, тогда этот сценарий
вызывает функцию с названием emergency_shell
. Как и предполагает само
название, она будет представлять отладочную оболочку и если эта отладочная оболочка успешно запущена, тогда
она вызывает другую функцию с названием source_hook
и передаёт в неё
параметр cmdline
. Написавший этот код для предоставления пользователям
отладочной оболочки - гениальный програмист!
На данном этапе мы не будем обсуждать функцию оболочки критической ситуации так как сначала нам требуется побольше разобраться с systemd. Следовательно, мы обсудим её более подробно в Главе 8.
Рисунок 7-11
показывает блок- схему работающих элементов dracut-cmdline.service
.
Двинемся далее, корневой файловой системой может пользователя быть просто /dev/sda5
,
однако на то же самое устройство sda5
можно ссылаться через
uuid
, partuuid
и
label
. В конце концов, все прочие ссылки sda5
обязаны достичь /dev/sda5
; следовательно наше ядро готовит файлы символических
ссылок для всех этих различных названий устройств под /dev/disk/
.
Обратитесь, пожалуйста, к
Рисунку 7-12.
Тот же самый сценарий /bin/dracut-cmdline
преобразовывает название
корневой файловой системы sda5
в
/dev/disk/by-uuid/6588b8f1-7f37-4162-968c-8f99eacdf32e
.
52 case "${root}${root_unset}" in
53 block:LABEL=*|LABEL=*)
54 root="${root#block:}"
55 root="$(echo $root | sed 's,/,\\x2f,g')"
56 root="block:/dev/disk/by-label/${root#LABEL=}"
57 rootok=1 ;;
58 block:UUID=*|UUID=*)
59 root="${root#block:}"
60 root="block:/dev/disk/by-uuid/${root#UUID=}"
61 rootok=1 ;;
62 block:PARTUUID=*|PARTUUID=*)
63 root="${root#block:}"
64 root="block:/dev/disk/by-partuuid/${root#PARTUUID=}"
65 rootok=1 ;;
66 block:PARTLABEL=*|PARTLABEL=*)
67 root="${root#block:}"
68 root="block:/dev/disk/by-partlabel/${root#PARTLABEL=}"
69 rootok=1 ;;
70 /dev/*)
71 root="block:${root}"
72 rootok=1 ;;
73 UNSET|gpt-auto)
74 # systemd's gpt-auto-generator handles this case.
75 rootok=1 ;;
76 esac
77
78 [ -z "${root}${root_unset}" ] && die "Empty root= argument"
79 [ -z "$rootok" ] && die "Don't know how to handle 'root=$root'"
80
81 export root rflags fstype netroot NEWROOT
82
83 export -p > /dracut-state.sh
84
85 exit 0
Давайте рассмотрим специальную точку входа cmdline
в действии. Как показано на
Рисунке 7-13,
передаётся rd.break=cmdline
в фрагмент GRUB ядра.
Запущенное ядро распакует initramfs, запустит процесс systemd, systemd проинициализирует сокеты
journald
, как вы можете видеть на
Рисунке 7-14,
systemd сбросит нас в оболочку командной строки, поскольку мы запросили systemd прервать (hook) свою
последовательность запуска перед выполнением dracut-cmdline
.
В настоящий момент мы находимся внутри initramfs и мы приостановили (dracut hooked последовательность запуска
systemd под systemd-journal.socket
. Поскольку
dracut-cmdline.service
ещё пока не стартовала, systemd пока ещё не собрал
параметры командной строки ядра, такие как root
,
rsflags
и fstype
из
/proc/cmdline
. Для лучшего понимания обратитесь, пожалуйста, к
Рисунку 7-15.
Кроме того необходимая символическая ссылка /dev/disk
пока ещй не была
создана dracut.
Поскольку systemd пока ещё не собрал название корневой файловой системы пользователя, нет никаких сомнений в том,
что вы не обнаружите корневую файловую систему пользователя смонтированной внутри initramfs.
sysroot
это каталог внутри initramfs, в котором монтируется корневая файловая
система пользователя. Отсылаем вас к
Рисунку 7-16.
Однако если мы не передадим никаких аргументов в rd.break
или просто
выйдем из своей текущей оболочки командной строки, мы будем сброшены в оболочку
switch_root
. Эта оболочка switch_root
является окончательным этапом последовательности запуска systemd внутри initramfs. На
Рисунке 7-17
вы можете видеть что мы передаём rd.break
без каких бы то ни было
аргументов.
Как вы можете видеть на
Рисунке 7-18,
в своей оболочке switch_root
, благодаря тому что
dracut-cmdline.service
уже был выполнен, вы обнаружите параметры командной
строки ядра, собранные systemd. Кроме того, корневая файловая система пользователя была смонтирована внутри
initramfs под sysroot
.
Если мы выполним выход на этом этапе, switch_root
(pivot_root
) будет выполнен systemd, и покинет свою среду initramfs.
Рисунок 7-19
в конечном счёте отобразит рабочий стол.
Возвращаясь к нашей последовательности запуска на данный момент, мы достигли этапа
pre-udev
. Для этого вы можете обратиться к
Рисунку 7-20.
Затем systemd имеет дело с подключёнными устройствами. Для этого systemd должен запустить демон
udev
, однако перед запуском этой службы udev
он проверяет желают ли пользователи остановить процесс загрузки прежде чем запустится
udev
. Если пользователь передал параметр командной строки
dracut
rd.break=pre-udev
,
sytemd остановит данную последовательность запуска непосредственно перед запуском демона
udev
.
# cat usr/lib/systemd/system/dracut-pre-udev.service | grep -v '#'
[Unit]
Description=dracut pre-udev hook
Documentation=man:dracut-pre-udev.service(8)
DefaultDependencies=no
Before=systemd-udevd.service dracut-pre-trigger.service
After=dracut-cmdline.service
Wants=dracut-cmdline.service
ConditionPathExists=/usr/lib/initrd-release
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-udev
ConditionKernelCommandLine=|rd.break=pre-udev
ConditionKernelCommandLine=|rd.driver.blacklist
ConditionKernelCommandLine=|rd.driver.pre
ConditionKernelCommandLine=|rd.driver.post
ConditionPathExistsGlob=|/etc/cmdline.d/*.conf
Conflicts=shutdown.target emergency.target
[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-pre-udev
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes
KillSignal=SIGHUP
Это сбросит нас в оболочку pre-udev
. Обратите внимание на переменные
after
, before
и
wants
. Выполнение dracut-pre-udev.service
всего лишь запускает исполняемый файл /bin/dracut-pre-udev
из initramfs. На
Рисунка 7-21
мы передали rd.break=pre-udev
как параметр командной строки ядра.
Чтобы разобраться со специальной точкой входа pre-udev
вы просто можете
просмотреть список содержимого /dev
и на
Рисунке 7-22
вы можете обратить внимание на то что нет устройства с названием sda
sda
это наш HDD, в котором у нас имеется корневая файловая система.
Основная причина отсутствия файлов устройства sda
из- за того, что наш
демон udev
ещё пока не стартовал. Этот демон запускается фалом элемента
/usr/lib/systemd/system/systemd-udevd.service
, который запускается после
специальной точки входа pre-udev
.
# cat usr/lib/systemd/system/systemd-udevd.service | grep -v '#'
[Unit]
Description=udev Kernel Device Manager
Documentation=man:systemd-udevd.service(8) man:udev(7)
DefaultDependencies=no
After=systemd-sysusers.service systemd-hwdb-update.service
Before=sysinit.target
ConditionPathIsReadWrite=/sys
[Service]
Type=notify
OOMScoreAdjust=-1000
Sockets=systemd-udevd-control.socket systemd-udevd-kernel.socket
Restart=always
RestartSec=0
ExecStart=/usr/lib/systemd/systemd-udevd
KillMode=mixed
WatchdogSec=3min
TasksMax=infinity
PrivateMounts=yes
ProtectHostname=yes
MemoryDenyWriteExecute=yes
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
RestrictRealtime=yes
RestrictSUIDSGID=yes
SystemCallFilter=@system-service @module @raw-io
SystemCallErrorNumber=EPERM
SystemCallArchitectures=native
LockPersonality=yes
IPAddressDeny=any
Давайте попытаемся понять как работает udev
и как он создаёт файлы
устройств под /dev
.
Именно само ядро определяет подключённые к данной системе оборудование; более точно, имеются такие драйверы,
которые скомпилированы внутри ядер те модули, которые вставляются позже, которые и будут определять своё
оборудование и зарегистрирует свои объекты в sysfs
(с точкой монтирования
/sys
). Благодаря этой точкой монтирования /sys
такие данные становятся доступными пространству пользователя и инструментам, подобным
udev
. Итак, именно ядро определяет оборудование посредстовм драйверов и
создаёт некий файл устройств в /dev
, который составляет
devfs
. После этого отправляет uevent
в
udevd
и udevd
изменяет названия файла
устройства, владельца или группу, либо настраивает надлежащие полномочия в соответствии с теми правилами, которые
определены здесь:
/etc/udev/rules.d,
/lib/udev/rules.d, and
/run/udev/rules.d
# ls etc/udev/rules.d/
59-persistent-storage.rules 61-persistent-storage.rules
# ls lib/udev/rules.d/
50-udev-default.rules 70-uaccess.rules 75-net-description.rules 85-nm-unmanaged.rules
60-block.rules 71-seat.rules 80-drivers.rules 90-vconsole.rules
60-persistent-storage.rules 73-seat-late.rules 80-net-setup-link.rules 99-systemd.rules
initramfs обладает несколькими фалами правил udev
по сравнению с доступными
правилами udev
, имеющимися в корневой файловой системе пользователя. В основном она
обладает лишь теми правилами, которые требуются для управления самими устройствами подключаемой корневой файловой
системы пользователя. Как только управление передаётся udevd
, он вызывает
соответствующие элементы systemd из lib/udev/rules.d/99-systemd.rules
. Вот некий
образец:
# cat lib/udev/rules.d/99-systemd.rules
SUBSYSTEM=="net", KERNEL!="lo", TAG+="systemd", ENV{SYSTEMD_ALIAS}+="/sys/subsystem/net/devices/$name"
SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_ALIAS}+="/sys/subsystem/bluetooth/devices/%k"
SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_WANTS}+="bluetooth.target", ENV{SYSTEMD_USER_WANTS}+="bluetooth.target"
ENV{ID_SMARTCARD_READER}=="?*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="smartcard.target", ENV{SYSTEMD_USER_WANTS}+="smartcard.target"
SUBSYSTEM=="sound", KERNEL=="card*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sound.target", ENV{SYSTEMD_USER_WANTS}+="sound.target"
SUBSYSTEM=="printer", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target", ENV{SYSTEMD_USER_WANTS}+="printer.target"
SUBSYSTEM=="usb", KERNEL=="lp*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target", ENV{SYSTEMD_USER_WANTS}+="printer.target"
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:0701??:*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target", ENV{SYSTEMD_USER_WANTS}+="printer.target"
SUBSYSTEM=="udc", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}+="usb-gadget.target"
Основное правило помечено тегом systemd
. Это означает что всякий раз,
когда определяется устройство bluetooth
, udevd
вызовет bluetooth.target
systemd. bluetooth.target
выполнит исполняемый файл /usr/libexec/bluetooth/bluetoothd
, который и позаботится
об остальной обработке устройства bluetooth
. Поэтому полная последовательность
обработки udevd
устройства bluetooth
такова:
-
Когда пользователь обладает подключённым в момент запуска к своей системе устройство bluetooth, именно само ядро, либо скомпилированные в драйвере ядра или вставляемы позднее модули выявляют это устройство bluetooth и регистрируют его объект в
/sys
. -
Позднее имеющееся ядро создаст некий файл устройства в точке монтирования
/dev
. После создания такого файла устройства, это ядро отправитuevent
вudevd
. -
udevd
будет ссылаться наlib/udev/rules.d/99-systemd.rules
из initrafs и вызовет systemd. Что касается этого тега, systemd предполагает обработку всего что в нём остаётся. -
systemd исполнит
bluetooth.target
, который выполнит соответствующий исполняемый файлbluetoothd
и имеющееся оборудование bluetooth будет готово к применению.
Конечно, bluetooth не является тем оборудованием, которое требуется в момент запуска. Я взял его просто в качестве примера для более простого понимания.
Итак, мы достигли systemd-udev.service
. systemd продолжит нашу
последовательность запуска и исполнит dracut-pre-trigger.service
.
Вы можете видеть общую последовательность запуска на
Рисунке 7-23.
Последовательность запуска systemd из initramfs будет прервана (захвачена) когда ваш пользователь передал параметр
командной строки dracut rd.break=pre-trigger
. Вы можете видеть на
Рисунке 7-24
что мы передали pre-trigger
в качестве аргумента для параметра командной строки
ядра rd.break
.
Это сбросит нас в оболочку pre-trigger
, которая появляется сразу после запуска службы
udevd
. Вначале давайте посмотрим как происходит сброс в оболочку
pre-trigger
.
# cat usr/lib/systemd/system/dracut-pre-trigger.service | grep -v '#'
[Unit]
Description=dracut pre-trigger hook
Documentation=man:dracut-pre-trigger.service(8)
DefaultDependencies=no
Before=systemd-udev-trigger.service dracut-initqueue.service
After=dracut-pre-udev.service systemd-udevd.service systemd-tmpfiles-setup-dev.service
Wants=dracut-pre-udev.service systemd-udevd.service
ConditionPathExists=/usr/lib/initrd-release
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-trigger
ConditionKernelCommandLine=|rd.break=pre-trigger
Conflicts=shutdown.target emergency.target
[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-pre-trigger
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes
KillSignal=SIGHUP
Обратите, пожалуйста, внимание на разделы After
, Before
и wants
файла элемента самой слубы. Этот файл службы запустит /bin/dracut-pre-trigger
из initramfs если каталог
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-trigger
имеется и если наш пользователь передал
rd.break=pre-trigger
в качестве параметра командной строки.
[root@fedorab boot]# cat bin/dracut-pre-trigger
#!/usr/bin/sh
export DRACUT_SYSTEMD=1
if [ -f /dracut-state.sh ]; then
. /dracut-state.sh 2>/dev/null
fi
type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
source_conf /etc/conf.d
make_trace_mem "hook pre-trigger" '1:shortmem' '2+:mem' '3+:slab' '4+:komem'
source_hook pre-trigger
getarg 'rd.break=pre-trigger' 'rdbreak=pre-trigger' && emergency_shell -n pre-trigger "Break pre-trigger"
udevadm control --reload >/dev/null 2>&1 || :
export -p > /dracut-state.sh
exit 0
Как вы можете видеть, выполняется проверка передаваемых параметров командной строки dracut
(rd.break=pre-trigger
) посредством функции getarg
. Ранее в этой главе мы уже видели как
рабботает getarg
. Когда пользователь передал rd.break=pre-trigger
, тогда он вызовет
функцию emergency_shell
с pre-trigger
в качестве переданного ей параметра. Сама функция
emergency_shell
записана в файле dracut-lib.sh
. Эта функция предоставит нам собственно
оболочку pre-trigger
. Глава 8
рассмотрит соответствующую процедуру, стоящую за предоставлением некой аварийной оболочки.
Как и предполагает название pre-trigger
, а также как вы можете разглядеть на
Рисунке 7-25,
мы остановили свою последовательность запуска сразу перед соответствующими триггерами udev
.
Таким образом, наш диск sda
пока ещё не доступен из dev
.
Это обусловлено тем, что наш триггер udevadm
пока ещё небыл выполнен. наша служба
dracut-pre-trigger.service
исполняет только udevadm control --reload
, что
перезапускает соответствующие правила udev
. Как показано на
Рисунке 7-26,
systemd-udev.service
была запущена, но служба systemd-udev-trigger
пока еще не
стартовала.
Рисунок 7-27 отображает те стадии запуска, которые мы уже прошли.
Как мы уже видели, с помощью pre-udev
каталог
/dev
не наполняется так как служба
systemd-udevd.service
сама по себе не стартовала. С
pre-trigger
то же самое: /dev
не наполняется, но соответствующая служба udevd
была запущена. Эта
служба udevd
создаст некую среду для старта/ запуска разнообразных
инструментов udev
, таких как udevadm
.
Применяя то окружение, которое предоставляется демоном udevd
,
как вы можете видеть на Рисунке 7-28,
внутри pre-trigger
мы будем иметь возможность выполнять
udevadm
, которую мы не способны применять в своей оболочке
pre-udev
.
Как вы можете видеть внутри установленного переключателя pre-trigger
,
устройство sda
ещё пока не было создано. Но поскольку у нас имеется
готовой среда udevadm
мы можем выполнять через неё поиск устройств.
Как показано на Рисунке 7-29,
мы сначала смонтируем необходимую файловую систему конфигурации ядра.
pre-trigger:/ # udevadm trigger --type=subsystems --action=add
Затем для добавления необходимых устройств мы включим udevadm
.
pre-trigger:/ # udevadm trigger --type=devices --action=add
Как мы можем видеть на
Рисунке 7-29,
было создано устройство sda
. Те же самые команды будут выстреливаться
systemd через systemd-udev-trigger.service
, которые будут обнаруживать
и создавать файлы устройств хранения в /dev
.
# cat usr/lib/systemd/system/systemd-udev-trigger.service | grep -v ‘#’
[Unit]
Description=udev Coldplug all Devices
Documentation=man:udev(7) man:systemd-udevd.service(8)
DefaultDependencies=no
Wants=systemd-udevd.service
After=systemd-udevd-kernel.socket systemd-udevd-control.socket
Before=sysinit.target
ConditionPathIsReadWrite=/sys
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/udevadm trigger –type=subsystems –action=add
ExecStart=/usr/bin/udevadm trigger –type=devices –action=add
Однако, как вы можете видеть на Рисунке 7-30,
та же самая команда udevadm
не достигает успеха в специальной точке входа
pre-udev
, поскольку отсутствует среда udev
.
Именно это важно и для dracut-pre-trigger.service
или специальной
точки входа pre-trigger
.
Представленная на Рисунке 7-31 блок- схема поможет вам понять те шаги, которые на данный момент были предприняты systemd внутри initramfs. Эта блок-схема станет ещё более понятной после прочтения Главы 8. Я настоятельно рекомендую вернуться к этой главе после завершения Главы 8.
Как вы можете увидеть на
Рисунке 7-32,
мы достигли этапа local-fs-target
запуска.
Итак, systemd достиг local-fs.target
. До сих пор systemd выполнял
службы одну за другой только по той причине, что устройства хранения не были готовы. Поскольку триггер
udevadm
был успешным и устройства хранения были наполнены, самое время
подготовить необходимые точки монтирования, которые будут достигнуты local-fs.target
.
Прежде входить в local-fs.target
, надлежит убедиться что
local-fs.pre.target
запущена.
# cat usr/lib/systemd/system/local-fs-pre.target
[Unit]
Description=Local File Systems (Pre)
Documentation=man:systemd.special(7)
RefuseManualStart=yes
#cat usr/lib/systemd/system/local-fs.target
[Unit]
Description=Local File Systems
Documentation=man:systemd.special(7)
DefaultDependencies=no
Conflicts=shutdown.target
After=local-fs-pre.target
OnFailure=emergency.target
OnFailureJobMode=replace-irreversibly
systemd-fstab-generator
будет перемещаться по
local-fs.target
.
- man page - systemd.special
systemd-fstab-generator(3) автоматически добавляет зависимости с типом Before= для всех монтируемых элементов, которые ссылаются на локальные точки монтирования для такого целевого элемента. Кроме того, добавляются зависимости типа Wants= для этого целевого элемента, для которого такие монтирования перечислены в /etc/fstab, которые обладают установленным параметром автоматического монтирования.
Из initramfs будет вызван systemd-fstab-generator
.
# file usr/lib/systemd/system-generators/systemd-fstab-generator
usr/lib/systemd/system-generators/systemd-fstab-generator: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e16e9d4188e2cab491f551b5f703a5caa645764b, for GNU/Linux 3.2.0, stripped
На самом деле, systemd все необходимые генераторы на неком раннем этапе этой последовательности запуска.
# ls -l usr/lib/systemd/system-generators
total 92
-rwxr-xr-x. 1 root root 3750 Dec 21 12:19 dracut-rootfs-generator
-rwxr-xr-x. 1 root root 45640 Dec 21 12:19 systemd-fstab-generator
-rwxr-xr-x. 1 root root 37032 Dec 21 12:19 systemd-gpt-auto-generator
Одним из них выступает systemd-fstab-generator
. Самая основная задача
systemd-fstab-generator
состоит в считывании значения команжной строки
ядра и создаёт файлы элементов systemd в каталоге /tmp
или в
/run/systemd/generator/
(продолжайте чтение и это всё будет иметь
значение). Как вы можете наблюдать, имеется исполняемый файл, что означает что нам требуется проверить
исходный код C systemd чтобы понять что он делает. Наш systemd-fstab-generator
не получает никаких входных данных, либо три параметра на входе.
# usr/lib/systemd/system-generators/systemd-fstab-generator /dev/sda5
This program takes zero or three arguments.
Конечно, этими тремя входными параметрами выступают название корневой файловой системы, тип файловой системы и
флаг корневой файловой системы. При написании этой книги, самой последней версией systemd является версия 244, а
потому мы применяли именно её для пояснений здесь. Показанное ранее сообщение поступило из
src/shared/generator.h
.
# vim systemd-244/src/shared/generator.h
57 /* Similar to DEFINE_MAIN_FUNCTION, but initializes logging and assigns positional arguments. */
58 #define DEFINE_MAIN_GENERATOR_FUNCTION(impl) \
59 _DEFINE_MAIN_FUNCTION( \
60 ({ \
61 log_setup_generator(); \
62 if (argc > 1 && argc != 4) \
63 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), \
64 "This program takes zero or three arguments."); \
65 }), \
66 impl(argc > 1 ? argv[1] : "/tmp", \
67 argc > 1 ? argv[2] : "/tmp",
Исполняемый файл systemd-fstab-generator
был собран из
src/fstab-generator/fstabgenerator.c
.
# vim systemd-244/src/fstab-generator/fstab-generator.c
868 static int run(const char *dest, const char *dest_early, const char *dest_late) {
869 int r, r2 = 0, r3 = 0;
870
871 assert_se(arg_dest = dest);
872 assert_se(arg_dest_late = dest_late);
873
874 r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0);
875 if (r < 0)
876 log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
877
878 (void) determine_root();
879
880 /* Always honour root= and usr= in the kernel command line if we are in an initrd */
881 if (in_initrd()) {
882 r = add_sysroot_mount();
883
884 r2 = add_sysroot_usr_mount();
885
886 r3 = add_volatile_root();
887 } else
888 r = add_volatile_var();
889
890 /* Honour /etc/fstab only when that's enabled */
891 if (arg_fstab_enabled) {
892 /* Parse the local /etc/fstab, possibly from the initrd */
893 r2 = parse_fstab(false);
894
895 /* If running in the initrd also parse the /etc/fstab from the host */
896 if (in_initrd())
897 r3 = parse_fstab(true);
898 else
899 r3 = generator_enable_remount_fs_service(arg_dest);
900 }
901
902 return r < 0 ? r : r2 < 0 ? r2 : r3;
903 }
904
905 DEFINE_MAIN_GENERATOR_FUNCTION(run);
Как вы можете видеть, вначале она передаёт значения параметров командной строки через функцию
proc_cmdline_parse
.
root = root filesystem name
rootfstype = root filesystem type
rootflags = ro, rw or auto etc.
systemd-fstab-generator
выполняется дважды:когда он пребывает внутри
initramfs и когда он вне iniytramfs. Когда systemd покидает initramfs (после монтирования корневой файловой
системы в sysroot
), systemd-fstab-generator
будет собирать необходимые параметры командной строки для файловой системы usr
(когда он пребывает в неком обособленном разделе и если его записи доступны в
etc/fstab
).
'usr' filesystem name
'usr' filesystem type
'usr' filesystem flags
Чтобы было проще понять, мы рассмотрим следующее:
Inside of initramfs: Before mounting the user's root filesystem in /sysroot
Outside of initramfs: After mounting the user's root filesystem in /sysroot
Итак, исполняемый файл systemd-fstab-generator
будет собирать параметры
командной строки, связанные с корневой файловой системой пользователя, когда systemd запускается вне initramfs,
Запускается sytemd внутри или вне initramfs будет проверяться через соответствующую функцию
in_initrd
. Эта функция записана в src/basic/util.c
.
Интересно проверить как она проверяет внутри или вне среды initramfs.
# vim systemd-244/src/basic/util.c
54 bool in_initrd(void) {
55 struct statfs s;
56 int r;
57
58 if (saved_in_initrd >= 0)
59 return saved_in_initrd;
60
61 /* We make two checks here:
62 *
63 * 1. the flag file /etc/initrd-release must exist
64 * 2. the root file system must be a memory file system
65 *
66 * The second check is extra paranoia, since misdetecting an
67 * initrd can have bad consequences due the initrd
68 * emptying when transititioning to the main systemd.
69 */
70
71 r = getenv_bool_secure("SYSTEMD_IN_INITRD");
72 if (r < 0 && r != -ENXIO)
73 log_debug_errno(r, "Failed to parse $SYSTEMD_IN_INITRD, ignoring: %m");
74
75 if (r >= 0)
76 saved_in_initrd = r > 0;
77 else
78 saved_in_initrd = access("/etc/initrd-release", F_OK) >= 0 &&
79 statfs("/", &s) >= 0 &&
80 is_temporary_fs(&s);
81
82 return saved_in_initrd;
83 }
Здесь проверяется доступен ли файл /etc/initrd-release
. Если этот файл не
представлен, это означает что мы пребываем вне initramfs.Данная функция далее вызывает функцию
statfs
, которая предоставляет подробности данной файловой системы, как
это показано здесь:
struct statfs {
__fsword_t f_type; /* Type of filesystem (see below) */
__fsword_t f_bsize; /* Optimal transfer block size */
fsblkcnt_t f_blocks; /* Total data blocks in filesystem */
fsblkcnt_t f_bfree; /* Free blocks in filesystem */
fsblkcnt_t f_bavail; /* Free blocks available to
unprivileged user */
fsfilcnt_t f_files; /* Total file nodes in filesystem */
fsfilcnt_t f_ffree; /* Free file nodes in filesystem */
fsid_t f_fsid; /* Filesystem ID */
__fsword_t f_namelen; /* Maximum length of filenames */
__fsword_t f_frsize; /* Fragment size (since Linux 2.6) */
__fsword_t f_flags; /* Mount flags of filesystem
(since Linux 2.6.36) */
__fsword_t f_spare[xxx];
/* Padding bytes reserved for future use */
};
Затем вызывается функция is_temporary_fs()
, которая записана внутри
/src/basic/stat-util.c
.
190 bool is_temporary_fs(const struct statfs *s) {
191 return is_fs_type(s, TMPFS_MAGIC) ||
192 is_fs_type(s, RAMFS_MAGIC);
193 }
Как вы можете видеть, проверяется обладает ли эта корневая файловая система назначенным ей магическим числом
ramfs. Если это так, тогда мы внутри initramfs. В нашем случае вы пребываем внутри своей среды initramfs,
поэтому данная функция вернёт true
и продолжит далее из
src/fstab-generator/fstab-generator.c
создание тодько корневых файловых
систем файлов элементов -.mount
(sysroot.mount
). Если бы мы были вне initramfs (после монтирования в
sysroot
корневой файловой системы пользователя), имелся бы созданным
для корневой файловой системы usr
файл элемента
-.mount
. Короче говоря, вначале он проверяет находится ли он внутри
initramfs. Если это так, тогда создаётся соответствующий файл эелмента монтирования для такой корневой файловой
системы, а если бы мы пребывали вовне, тогда он создаёт его для файловой системы usr
(если это обособленная файловая система). Для того чтобы увидеть это в действии, мы выбросим себя в
соответствующий этап switch_root
(особая точка входа) с тем, чтобы мы имели
возможность запустить вручную исполняемый файл systemd-fstab-generator
.
-
Прежде всего я удалил содержимое каталога
/tmp
. Это обусловлено тем, что генераторfstab
создаёт свои файлы монтирования элементов внутри/tmp
. -
Запускаю исполняемый файл
systemd-fstab-generator
и, как вы можете увидеть на Рисунке 7-33, создаётся пара файлов в/tmp
.
-
Это создаёт файл элемента
sysroot.mount
. Как и предполагает его название, он был создан для монтирования соответствующей корневой файловой системы пользователя. Этот файл элемента был создан считыванием/proc/cmdline
. Чтобы просмотреть содержимоеsysroot.mount
обратитесь к Рисунку 7-34.
Эта корневая файловая система монтируется из
sda5
(с применением UUID) в соответствующем каталогеsysroot
. -
Проверьте раздел
requires
файла элементаsysroot.mount
. Он предписывает, что вначале должен быть исполненsystemd-fsck-root.service
, причём до монтирования надлежащей корневой файловой системы. Рисунок 7-35 отображаетsystemd-fsck-root.service
.
Итак при запуске, когда вы пребываете внутри initramfs, тогда systemd-fstab-generator
выработает файлы монтирования элементов для необходимой корневой файловой системы пользователя и также будет
создан соответствующий файл службы fsck
.
В самом конце последовательности запуска initramfs systemd будет ссылаться на эти файлы из своего каталога
/tmp
, исполнит fsck
вначале в
неком корневом устройстве и смонтирует эту корневую файловую систему в sysroot
(внутри initramfs); в конечном счёте будет выполнен switch_root
.
Теперь вы должны понимать, что хотя именем исполняемого файла и является
systemd-fstab-generator
, на самом деле он не создаёт файл
/etc/fstab
. Вместо этого его задание состоитв том, чтобы создавать элементы
монтирования самого systemd для root
(когда он внутри initramfs) и
usr
(в случае пребывания вне initramfs) в каталогах
/tmp
или внутри run/systemd/generator/
.
Затем система обладает лишь точуой монтирования root
, а потому она создаёт
файлы элемента systemd только для корневой файловой системы. Внутри initramfs она вызывает
add_sysroot_mount
для монтирования соответствующей корневой файловой системы
пользователя. Когда та смонтирована, systemd этой корневой файловой системы вызывает функцию
add_sysroot_usr_mount
. Эти функции вызывают функцию с названием
add_mount
, которая в свою очередь делает файлы элемента монтирования этого
systemd. Ниже приводится фрагмент кода такой функции add_mount
из
src/fstab-generator/fstab-generator.c
:
# vim systemd-244/src/fstab-generator/fstab-generator.c
341 r = unit_name_from_path(where, ".mount", &name);
342 if (r < 0)
343 return log_error_errno(r, "Failed to generate unit name: %m");
344
345 r = generator_open_unit_file(dest, fstab_path(), name, &f);
346 if (r < 0)
347 return r;
348
349 fprintf(f,
350 "[Unit]\n"
351 "SourcePath=%s\n"
352 "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n",
353 source);
354
355 /* All mounts under /sysroot need to happen later, at initrd-fs.target time. IOW, it's not
356 * technically part of the basic initrd filesystem itself, and so shouldn't inherit the default
357 * Before=local-fs.target dependency. */
358 if (in_initrd() && path_startswith(where, "/sysroot"))
359 fprintf(f, "DefaultDependencies=no\n");
Имеющаяся в данный момент система обладает только корневым разделом. Чтобы помочь нам осознать это ещё лучше,
здесь я подготовил некую тестовую систему, обладающую root
,
boot
, usr
,
var
и opt
как обособленными
файловыми системами.
UUID = f7ed74b5-9085-4f42-a1c4-a569f790fdad / ext4 defaults 1 1
UUID = 06609f65-5818-4aee-a9c5-710b76b36c68 /boot ext4 defaults 1 2
UUID = 68fa7990-edf9-4a03-9011-21903a676322 /opt ext4 defaults 1 2
UUID = 6fa78ab3-6c05-4a2f-9907-31be6d2a1071 /usr ext4 defaults 1 2
UUID = 9c721a59-b62d-4d60-9988-adc8ed9e8770 /var ext4 defaults 1 2
Мы выкинем себя соответствующей оболочке pre-pivot
(которую мы пока
ещё не обсуждали).
Рисунок 7-36
отображает, что мы передали параметр командной строки rd.break=pre-pivot
в своё ядро.
Как мы можем видеть на
Рисунке 7-37,
в специальной точке входа pre-pivot
наша корневая файловая система будет
монтироваться совместно с файловой системой usr
, поскольку наша особая
точка входа pre-pivot
останавливает нашу последовательность запуска после
монтирования своей корневой файловой системы пользователя в sysroot
. Однако
opt
, var
и
boot
не будут смонтированы.
Даже если вы запустите systemd-fstab-generator
, вы обнаружите что будут
созданы только файлы элементов монтирования usr
и
root
. На
Рисунке 7-38
вы можете наблюдать вывод systemd-fstab-generator
.
Это подтверждает, что в окружении initramfs могут быть смонтированы только root
и usr
. Остальные необходимые точки монтирования будут смонтированы после
initramfs или после переключения в корень. Так как необходимая нам файловая система var
пока ещё не смонтирована, требуемый журнал journalctl
будет смонтирован из
имеющейся файловой системы /run
и, как мы знаем, это некая временная файловая
система. Это ясно говорит что внутри имеющейся среды initramfs вы не можете выполнять доступ к постоянным
журналам journald, которые пребывают в /var/log
. Чтобы лучше разобраться с этим,
обратитесь к Рисунку 7-39,
Рисунку 7-40 и
Рисунку 7-41.
Вы заметили один момент? Служба dracut-cmdline
считывает параметры
командной строки ядра, а затем относящиеся к usr
параметры командной строки
не доступны в /proc/cmdline
. Итак, каким образом systemd удаётся смонтировать
эту файловую систему usr
? Кроме того, во время выработки initramfs dracut не
копирует в неё файл etc/fstab
.
lsinitrd | grep -i fstab
-rw-r--r-- 1 root root 0 Jul 25 03:54 etc/fstab.empty
-rwxr-xr-x 1 root root 45640 Jul 25 03:54 usr/lib/systemd/system-generators/systemd-fstab-generator
# lsinitrd -f etc/fstab.empty
<no_output>
Тогда как же systemd управляет монтированием файловой этой системы usr
внутри initramfs, когда она не обладает в ней записью?
Когда systemd-fstab-generator
запускается в процессе
local-fs.target
, он создаёт файлы монтирования элементов только для
root
; далее он продолжает свою последовательность запуска и монтирует
соответствующую корневую файловую систему в sysroot
. Когда эта корневая
файловая система смонтирована, она считывает запись usr
из
/etc/sysroot/etc/fstab
и делает файл элемента
usr.mount
и завершает её монтирование. Давайте перепроверим что мы разобрались
с этим:
-
Сваливаемся в особую точку входа
pre-pivot
. -
Удаляем имеющийся
/etc/fstab
из своего смонтированного/sysroot
. -
Запускаем
systemd-fstab-generator
. -
Взгляните на Рисунок 7-42.
Так как название нашей корневой файловой системы dracut-cmdline
получит из
proc/cmdline
, systemd-fstab-generator
создаст файл sysroot.mount
. Но поскольку внутри
sysroot
отсутствует файл fstab
,
этот usr
будет рассматриваться как отдельный раздел, который не доступен и
файл элемента usr.mount
будет пропущен, даже когда
usr
выступает отдельной точкой монтирования.
Что если вы желаете иметь подобные opt
и var
точки монтирования внутри /sysroot
или вы их пожелаете внутри некой среды
initramfs? Страница руководства systemd имеет ответ на это, аот он:
x-initrd.mount/em>
Некая дополнительная файловая система должна быть смонтирована в имеющейся initramfs. Обратитесь к описанию initrd-fs.target в systemd.special(7).
initrd-fs.target
systemd-fstab-generator(3) автоматически добавляет зависимости с типом Before= to sysroot-usr.mount и все обнаруженные в /etc/fstab точки монтирования, которые обладают x-initrd.mount и не имеют установленными параметрами noauto mount.
Итак, нам требуется воспользоваться параметром x-initrd.mount [systemd.mount]
.
в /etc/fstab
. Например, здесь я включид точку монтирования
var
внутри initramfs через то же самое окружение
pre-pivot
:
pre-pivot:/# vi /sysroot/etc/fstab
UUID=f7ed74b5-9085-4f42-a1c4-a569f790fdad / ext4 defaults 1 1
UUID=06609f65-5818-4aee-a9c5-710b76b36c68 /boot ext4 defaults 1 2
UUID=68fa7990-edf9-4a03-9011-21903a676322 /opt ext4 defaults 1 2
UUID=6fa78ab3-6c05-4a2f-9907-31be6d2a1071 /usr ext4 defaults 1 2
UUID=9c721a59-b62d-4d60-9988-adc8ed9e8770 /var ext4 defaults,x-initrd.mount 1 2
Как вы можете видеть на
Рисунке 7-43,
, однако был создан файл монтирования элемента var
fsck
доступен только для нашей файловой системы
root
. Чтобы лучше разобраться с этим, обратитесь, пожалуйста, к
Рисунку 7-44.
Как вы видите из
Рисунка 7-45,
мы достигли этапа swap.target
своего запуска.
Это выполняется параллельно с local-fs.target. local-fs-.target
,
создавая необходимые точки монтирования для root
и
usr
, в то время как swap.target
создаёт файлы монтирования элементов для устройства подкачки страниц. Когда необходимая корневая файловая система
готова, соответственным образом в неё монтируется sysroot
.
systemd-fstab-generator
считает fstab
и когда имеется запись устройства подкачки, будет создан надлежащий файл элемента
swap.mount
. Это означает, что файл swap.mount
будет создан только после переключения в окончательную корневую файловую систему пользователя
(switch_root
в sysroot
).
На этом этапе не будет создан соответствующий swap.mount
.
Данная служба создаёт все реальные устройства root
,
swap
и usr
. Давайте разберёмся с этим
на неком примере.
При помощи специальной точки обработки pre-udev
мы обнаружили что подобные
sda
устройства не доступны. Никакя команда udevadm
не будет работать, поскольку её служба udevd
сама по себе пока ещё не
стартовала. Обратитесь к
Рисунку 7-46.
При помощи специальной точки входа pre-trigger
устройство
sda
не создаётся, однако запускается служба
udevd
; однако, как вы можете видеть на
Рисунке 7-47 и
Рисунке 7-48
вы можете применять подобные udevadm
инструменты, которые и создадут
устройство sda
в /dev
, однако
мы не создаём в нём устройства, подобные lvm
или
raid
. Такие устройства имеют название устройств
dm
(device mapper, устройство построения соответсвия). Итак, данная служба
pre-trigger
не будет иметь возможности создания необходимых файлов устройств
для своего корня когда он пребывает в lvm
, а следовательно подобные
/dev/fedora_localhost-live/
устройства не будут созданы.
Служба dm
пока ещё не стартовала. Давайте вначале посмотрим что в
точности сообщает соответствующий файл элемента.
# cat usr/lib/systemd/system/dracut-initqueue.service | grep -v '#'
[Unit]
Description=dracut initqueue hook
Documentation=man:dracut-initqueue.service(8)
DefaultDependencies=no
Before=remote-fs-pre.target
Wants=remote-fs-pre.target
After=systemd-udev-trigger.service
Wants=systemd-udev-trigger.service
ConditionPathExists=/usr/lib/initrd-release
ConditionPathExists=|/lib/dracut/need-initqueue
ConditionKernelCommandLine=|rd.break=initqueue
Conflicts=shutdown.target emergency.target
[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-initqueue
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes
KillSignal=SIGHUP
Как вы можете наблюдать, эта служба просто запускает свой сценарий /bin/dracut-initqueue
и, если мы откроем этот сценарий, вы обнаружите что на самом деле он выполняет команду
udevadm settle
со значением timeout
равным 0
.
# vim bin/dracut-initqueue
22 while :; do
23
24 check_finished && break
25
26 udevadm settle --exit-if-exists=$hookdir/initqueue/work
27
28 check_finished && break
29
30 if [ -f $hookdir/initqueue/work ]; then
31 rm -f -- "$hookdir/initqueue/work"
32 fi
33
34 for job in $hookdir/initqueue/*.sh; do
35 [ -e "$job" ] || break
36 job=$job . $job
37 check_finished && break 2
38 done
39
40 udevadm settle --timeout=0 >/dev/null 2>&1 || continue
41
42 for job in $hookdir/initqueue/settled/*.sh; do
43 [ -e "$job" ] || break
44 job=$job . $job
45 check_finished && break 2
46 done
47
48 udevadm settle --timeout=0 >/dev/null 2>&1 || continue
49
50 # no more udev jobs and queues empty.
51 sleep 0.5
Это в конечном счёте запускает команду lvm_scan
из
lib/dracut/hooks/initqueue/timeout/
. Обратите внимание на передаваемые
параметры командной строки ядра root
и
rd.break
на
Рисунке 7-49.
Как вы видите на
Рисунке 7-50,
команда lvm_scan
записана в одном из имеющихся файлов.
Итак, здесь у нас имеются два варианта: либо мы можем только выполнить
/bin/dracut-initqueue
, или, как это отображено на
Рисунке 7-51,
мы можем выполнить команду lvm_scan
либо из специальной точки входа
pre-trigger
, либо из специальной точки входа
initqueue
.
Поскольку мы обсудили часть LVM initramfs, это именно тот момент, чтобы рассмотреть одну из наиболее распространённых и критически важных проблем "Can’t Boot".
Проблема 7 "Can’t Boot" (systemd + Root LVM)
Проблема: Мы изменяем значение названия стандартного
корневого устройства с /dev/mapper/fedora_localhost--live-root
на /dev/mapper/root_vg-root
. Мы делаем соответствующую запись в
/etc/fstab
, однако после перезагрузки наша система не способна запуститься.
Рисунок 7-52
показывает что видно на нашем экране.
Поскольку у нс теперь лучшее понимание dracut-initqueue
, мы можем понять,
что это сообщение об ошибке ясно означает что systemd не способен собрать устройство
root lvm
.
-
Давайте изолируем эту проблему вначале повторно вызвав свои шаги выполнения. Первоначально название
root lv
таково:#cat /etc/fstab /dev/mapper/fedora_localhost--live-root / ext4 defaults 1 1 UUID=eea3d947-0618-4d8c-b083-87daf15b2679 /boot ext4 defaults 1 2 /dev/mapper/fedora_localhost--live-swap none ext4 defaults 0 0
-
Значение названия
root volume group
было изменено.# vgrename fedora_localhost-live root_vg
Наша
volume group
Fedora_localhost-live
была успешно переименована вoot_vg
. -
Запись
/etc/fstab
root lvm
была <была изменена соответствующим образом./dev/mapper/root_vg-root / ext4 defaults 1 1 UUID=eea3d947-0618-4d8c-b083-87daf15b2679 /boot ext4 defaults 1 2 /dev/mapper/root_vg-swap none swap defaults 0 0
Однако после перезапуска systemd начинает выдавать сообшения об ошибке
dracut-initqueue timeout
.
Эти шаги выглядят так, как будто они выполнены верно, однако нам необходимо продолжить своё исследование
чтобы разобраться почему dracut-initqueue
не способен собрать LVM.
Если мы подождём какое- то время в своём окне ошибок, как показано на Рисунке 7-53, systemd автоматически вывалится в аварийную оболочку. Подробности того как systemd скидывает нас в аварийное окно мы увидим в Главе 8.
Как показано на
Рисунке 7-54,
мы можем просканировать доступные в настоящий момент LV и смонтируем root_vg
(корневую виртуальную группу) для проверки её содержимого.
Как вы можете видеть, root_vg
(переименованная
vg
) доступна и у нас также имеется и возможность ей активировать. Это с
очевидностью означает что установленные метаданные LVM не разрушены и что это устройство LVM не обладает проблемами
с целостностью. Как показано на
Рисунке 7-55,
мы смотнируем root_vg
во временном каталоге и повторно проверим её записи
fstab
из самой текущей аварийной оболочки.
vg
не тронута, установленные записи fstab
верны и мы имеем возможность смонтировать необходимую root_vg
. Чего же
тогда не хватает?
Пропущенной частью является то, что параметры командной строки были отрегулированы в GRUB. Смотрите Рисунок 7-56.
Чтобы запуститься нам требуется прервать экран- заставку GRUB и изменить параметры командной строки свого ядра по сравнению с отображаемыми на Рисунке 7-57.
Смотрите на новые на Рисунке 7-58.
После того как данная система запустилась, измените следующий /etc/default/grub
:
# cat /etc/default/grub
GRUB_TIMEOUT=10
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="resume=/dev/mapper/fedora_localhost--live-swap rd.lvm.lv=fedora_localhost-live/root rd.lvm.lv=fedora_localhost-live/swap console=ttyS0,115200 console=tty0"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
на такое:
# cat /etc/default/grub
GRUB_TIMEOUT=10
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="resume=/dev/mapper/root_vg-swap rd.lvm.lv=root_vg/root rd.lvm.lv=root_vg/swap console=ttyS0,115200 console=tty0"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
Необходимо изменить файл /etc/default/grub
, так как Fedora применяет
имеющиеся записи BLS из /boot/loader/entries
.
Замените /boot/grub2/grubenv
с:
# cat /boot/grub2/grubenv
saved_entry=2058a9f13f9e489dba29c477a8ae2493-5.3.7-301.fc31.x86_64
menu_auto_hide=1
boot_success=0
kernelopts=root=/dev/mapper/fedora_localhost--live-root ro resume=/dev/mapper/fedora_localhost--live-swap rd.lvm.lv=fedora_localhost-live/root rd.lvm.lv=fedora_localhost-live/swap console=ttyS0,115200 console=tty0
boot_indeterminate=9
на следующее:
# cat /boot/grub2/grubenv
saved_entry=2058a9f13f9e489dba29c477a8ae2493-5.3.7-301.fc31.x86_64
menu_auto_hide=1
boot_success=0
kernelopts=root=/dev/root_vg/root ro resume=/dev/mapper/root_vg-swap rd.lvm.lv=root_vg/root rd.lvm.lv=root_vg/swap console=ttyS0,115200 console=tty0
boot_indeterminate=9
Это исправляет нашу проблему "Can’t Boot".
Теперь настало время поговорить б одной занимательной службе с названием plymouth
.
Ранний Linux отображал сообщения запуска напрямую в своей консоли что было надоедливым для пользователей рабочего стола.
Таким образом и появился plymouth
, что отображено здесь:
# cat usr/lib/systemd/system/plymouth-start.service
[Unit]
Description=Show Plymouth Boot Screen
DefaultDependencies=no
Wants=systemd-ask-password-plymouth.path systemd-vconsole-setup.service
After=systemd-vconsole-setup.service systemd-udev-trigger.service systemd-udevd.service
Before=systemd-ask-password-plymouth.service
ConditionKernelCommandLine=!plymouth.enable=0
ConditionVirtualization=!container
[Service]
ExecStart=/usr/sbin/plymouthd --mode=boot --pid-file=/var/run/plymouth/pid --attach-to-session
ExecStartPost=-/usr/bin/plymouth show-splash
Type=forking
KillMode=none
SendSIGKILL=no
Как вы можете наблюдать, из файла элемента
/usr/lib/systemd/system/plymouth-start.service
plymouth
запускается сразу после
systemd-udev-trigger.service
и перед
dracut-initqueue.service
, что отображено на
Рисунке 7-59.
Как показано на
Рисунке 7-60,
plymouth
будет активным на протяжении данной последовательности запуска..
plymouth
является тем инструментом, который отображает вам анимацию во
время выполняемого запуска. Например, в Fedora он не показывает сообщения консоли, отображённые на
Рисунке 7-61.
plymouth
показывает вам анимацию, отражаемую на
Рисунке 7-62.
Установка plymouth
Если вы желаете устанавливать различные темы plymouth
, тогда вот
что вам требуется предпринять:
-
Выгрузите
plymouth-theme
из gnome-look.org или вы можете воспользоваться следующим:# dnf install plymouth-theme*
-
Распакуйте выгруженную тему в следующее местоположение:
/usr/share/plymouth/themes/
# ls -l /usr/share/plymouth/themes/ total 52 drwxr-xr-x. 2 root root 4096 Apr 26 2019 bgrt drwxr-xr-x 3 root root 4096 Mar 30 09:15 breeze drwxr-xr-x 2 root root 4096 Mar 30 09:15 breeze-text drwxr-xr-x. 2 root root 4096 Mar 30 09:15 charge drwxr-xr-x. 2 root root 4096 Apr 26 2019 details drwxr-xr-x 2 root root 4096 Mar 30 09:15 fade-in drwxr-xr-x 2 root root 4096 Mar 30 09:15 hot-dog drwxr-xr-x 2 root root 4096 Mar 30 09:15 script drwxr-xr-x 2 root root 4096 Mar 30 09:15 solar drwxr-xr-x 2 root root 4096 Mar 30 09:15 spinfinity drwxr-xr-x. 2 root root 4096 Apr 26 2019 spinner drwxr-xr-x. 2 root root 4096 Apr 26 2019 text drwxr-xr-x. 2 root root 4096 Apr 26 2019 tribar
-
Вам потребуется пересобрать initramfs, ибо
plymouth
запускается из среды initramfs. Например, его файл настроек должен быть обновлён для нашей новой темыplymouth
.# cat /etc/plymouth/plymouthd.conf # Administrator customizations go in this file #[Daemon] #Theme=fade-in [Daemon] Theme=hot-dog
После перезапуска, как это отображено на
Рисунке 7-63,
вы сможете обнаружить новую тему plymouth
с названием
hot-dog
.
Управление plymouth
Так как plymouth
запускается на некой ранней стадии, dracut предоставляет
некоторые параметры командной строки для управления поведением plymouth
.
- plymouth.enable=0
полностью отключает заставку запуска
plymouth
- rd.plymouth=0
отключает заставку запуска
plymouth
только для самого initramfs
Показываемое ранее изображение hot-dog носило название экрана заставки. Просмотреть установленные/ выбранные экраны заставки вы можете следующим образом:
#plymouth --show-splash
Другим основным мотивом plymouth
выступает сопровождение всех сообщений
времени запуска в неком простом текстовом файле, который пользователи смогут просмотреть после самого запуска.
Этот журнал будет сохранён в /var/log/boot.log
, однако помните, что вы
сможете обнаружить сообщения запуска только после запуска plymouth
.
Но в то же самое время необходимо иметь помнить, что plymouth
запускается на неком раннем этапе initramfs (сразу после старта udevd
).
# less /varlog/boot.log
<snip>
------------ Sat Jul 06 01:43:12 IST 2019 ------------
[ESC[0;32m OK ESC[0m] Started ESC[0;1;39mShow Plymouth Boot ScreenESC[0m.
[ESC[0;32m OK ESC[0m] Reached target ESC[0;1;39mPathsESC[0m.
[ESC[0;32m OK ESC[0m] Started ESC[0;1;39mForward Password R...s to Plymouth Directory WatchESC[0m.
[ESC[0;32m OK ESC[0m] Found device ESC[0;1;39m/dev/mapper/fedora_localhost--live-rootESC[0m.
[ESC[0;32m OK ESC[0m] Reached target ESC[0;1;39mInitrd Root DeviceESC[0m.
[ESC[0;32m OK ESC[0m] Found device ESC[0;1;39m/dev/mapper/fedora_localhost--live-swapESC[0m.
Starting ESC[0;1;39mResume from hiber...fedora_localhost--live-swapESC[0m...
[ESC[0;32m OK ESC[0m] Started ESC[0;1;39mResume from hibern...r/fedora_localhost--live-swapESC[0m.
[ESC[0;32m OK ESC[0m] Reached target ESC[0;1;39mLocal File Systems (Pre)ESC[0m.
[ESC[0;32m OK ESC[0m] Reached target ESC[0;1;39mLocal File SystemsESC[0m.
Starting ESC[0;1;39mCreate Volatile Files and DirectoriesESC[0m...
[ESC[0;32m OK ESC[0m] Started ESC[0;1;39mCreate Volatile Files and DirectoriesESC[0m.
[ESC[0;32m OK ESC[0m] Reached target ESC[0;1;39mSystem InitializationESC[0m.
[ESC[0;32m OK ESC[0m] Reached target ESC[0;1;39mBasic SystemESC[0m.
[ESC[0;32m OK ESC[0m] Started ESC[0;1;39mdracut initqueue hookESC[0m.
[ESC[0;32m OK ESC[0m] Reached target ESC[0;1;39mRemote File Systems (Pre)ESC[0m.
[ESC[0;32m OK ESC[0m] Reached target ESC[0;1;39mRemote File SystemsESC[0m.
Starting ESC[0;1;39mFile System Check...fedora_localhost--live-rootESC[0m...
[ESC[0;32m OK ESC[0m] Started ESC[0;1;39mFile System Check ...r/fedora_localhost--live-rootESC[0m.
Mounting ESC[0;1;39m/sysrootESC[0m...
[ESC[0;32m OK ESC[0m] Mounted ESC[0;1;39m/sysrootESC[0m.
[ESC[0;32m OK ESC[0m] Reached target ESC[0;1;39mInitrd Root File SystemESC[0m.
Starting ESC[0;1;39mReload Configuration from the Real RootESC[0m...
[ESC[0;32m OK ESC[0m] Started ESC[0;1;39mReload Configuration from the Real RootESC[0m.
[ESC[0;32m OK ESC[0m] Reached target ESC[0;1;39mInitrd File SystemsESC[0m.
[ESC[0;32m OK ESC[0m] Reached target ESC[0;1;39mInitrd Default TargetESC[0m.
Starting ESC[0;1;39mdracut pre-pivot and cleanup hookESC[0m...
[ESC[0;32m OK ESC[0m] Started ESC[0;1;39mdracut pre-pivot and cleanup hookESC[0m.
Starting ESC[0;1;39mCleaning Up and Shutting Down DaemonsESC[0m...
[ESC[0;32m OK ESC[0m] Stopped target ESC[0;1;39mTimersESC[0m.
[ESC[0;32m OK ESC[0m] Stopped ESC[0;1;39mdracut pre-pivot and cleanup hookESC[0m.
[ESC[0;32m OK ESC[0m] Stopped target ESC[0;1;39mInitrd Default TargetESC[0m.
[ESC[0;32m OK ESC[0m] Stopped target ESC[0;1;39mRemote File SystemsESC[0m.
[ESC[0;32m OK ESC[0m] Stopped target ESC[0;1;39mRemote File Systems (Pre)ESC[0m.
[ESC[0;32m OK ESC[0m] Stopped ESC[0;1;39mdracut initqueue hookESC[0m.
Starting ESC[0;1;39mPlymouth switch root serviceESC[0m...
[ESC[0;32m OK ESC[0m] Stopped target ESC[0;1;39mInitrd Root DeviceESC[0m.
[ESC[0;32m OK ESC[0m] Stopped target ESC[0;1;39mBasic SystemESC[0m.
[ESC[0;32m OK ESC[0m] Stopped target ESC[0;1;39mSystem InitializationESC[0m.
.
.
</snip>
Структура
plymouth
получает входные данные из initframs/ systemd чтобы понять какой
этап данной процедуры запуска был выполнен (в виде процентов от всей процедуры запуска) и в соответствии с этим
отображает на своём экране надлежащую анимацию или индикатор выполнения. Имеются два исполняемых файла, которые
заботятся о работе самого plymouth
.
/bin/plymouth (Interface to plymouthd)
/usr/sbin/plymouthd (main binary which shows splash and logs boot messages in boot.log file)
Внутри initramfs имеются доступными различные службы plymouthd
,
на которые полагается systemd.
# ls -l usr/lib/systemd/system/ -l | grep -i plymouth
-rw-r--r--. 1 root root 384 Dec 21 12:19 plymouth-halt.service
-rw-r--r--. 1 root root 398 Dec 21 12:19 plymouth-kexec.service
-rw-r--r--. 1 root root 393 Dec 21 12:19 plymouth-poweroff.service
-rw-r--r--. 1 root root 198 Dec 21 12:19 plymouth-quit.service
-rw-r--r--. 1 root root 204 Dec 21 12:19 plymouth-quit-wait.service
-rw-r--r--. 1 root root 386 Dec 21 12:19 plymouth-reboot.service
-rw-r--r--. 1 root root 547 Dec 21 12:19 plymouth-start.service
-rw-r--r--. 1 root root 295 Dec 21 12:19 plymouth-switch-root.service
-rw-r--r--. 1 root root 454 Dec 21 12:19 systemd-ask-password-plymouth.path
-rw-r--r--. 1 root root 435 Dec 21 12:19 systemd-ask-password-plymouth.service
drwxr-xr-x. 2 root root 4096 Dec 21 12:19 systemd-ask-password-plymouth.service.wants
При запуске из initramfs systemd время от времени вызывает эти службы на протяжении всего этапа запуска. Как
вы можете видеть, каждая служба вызывается исполняемым файлом plymouthd
и
передаёт переключатели соответственно текущей стадии запуска. Например,
plymouth-start.service
просто запускает plymouthd
в режиме boot
. Существуют лишь два режима; одним из них выступает
boot
, а другим является shutdown
.
# cat usr/lib/systemd/system/plymouth* | grep -i execstart
ExecStart=/usr/sbin/plymouthd --mode=shutdown --attach-to-session
ExecStartPost=-/usr/bin/plymouth show-splash
ExecStart=/usr/sbin/plymouthd --mode=shutdown --attach-to-session
ExecStartPost=-/usr/bin/plymouth show-splash
ExecStart=/usr/sbin/plymouthd --mode=shutdown --attach-to-session
ExecStartPost=-/usr/bin/plymouth show-splash
ExecStart=-/usr/bin/plymouth quit <<---
ExecStart=-/usr/bin/plymouth --wait
ExecStart=/usr/sbin/plymouthd --mode=reboot --attach-to-session
ExecStartPost=-/usr/bin/plymouth show-splash
ExecStart=/usr/sbin/plymouthd --mode=boot --pid-file=/var/run/plymouth/pid --attach-to-session
ExecStartPost=-/usr/bin/plymouth show-splash
ExecStart=-/usr/bin/plymouth update-root-fs --new-root-dir=/sysroot <<---
Другим образцом, который мы можем рассмотреть, является момент соответствующий
switch_root
, когда systemd просто вызывает
plymouth-switch-root.service
, которая в свою очередь запускает исполняемый
файл plymouthd
с некой обновлённой корневой файловой системой в качестве
sysroot
. Иными словами, вы можете сказать, что помимо
switch_root
plymouth
изменил
свой корневой каталог с initramfs на ральную корневую файловую систему. Следуя далее вы можете обнаружить, что
systemd стартовал саму службу plymouth
точно тем же способом, которым systemd
отправил сообщение quit
в plymouthd
в
самом конце текущей последовательности запуска. В то же самое время вы, вероятно, отметили, что systemd вызывает
plymouth
также и в момент перезапуска и останова. Это не весть что, это всего
лишь вызов того же самого plymouthd
в надлежащем режиме.
Итак, мы достигли стадии sysinit.target
. Рисунок
Рисунок 7-64
отображает пройденную нами к данному моменту последовательность запуска.
Поскольку данный элемент является target
(целевым), его задание состоит в
удержании или запуске пакета прочих элементов (служб, сокетов и т.п.). Перечень элементов будет доступен в каталоге
target
. Как вы можете видеть, являются ничем иным как символическими ссылками
к первоначальным файлам элементов служб.
#ls -l usr/lib/systemd/system/sysinit.target.wants/
total 0
kmod-static-nodes.service -> ../kmod-static-nodes.service
plymouth-start.service -> ../plymouth-start.service
systemd-ask-password-console.path -> ../systemd-ask-password-console.path
systemd-journald.service -> ../systemd-journald.service
systemd-modules-load.service -> ../systemd-modules-load.service
systemd-sysctl.service -> ../systemd-sysctl.service
systemd-tmpfiles-setup-dev.service -> ../systemd-tmpfiles-setup-dev.service
systemd-tmpfiles-setup.service -> ../systemd-tmpfiles-setup.service
systemd-udevd.service -> ../systemd-udevd.service
systemd-udev-trigger.service -> ../systemd-udev-trigger.service
Большинство этих служб уже были запущены прежде чем мы достигли sysinit.target
.
Например, systemd-udevd.service
и
systemd-udev-trigger.service
(после службы pre-trigger
)
уже были запущены и мы уже видели, что systemd-udevd.service
запустит
исполняемый файл /usr/lib/systemd/systemd-udevd
, в то время как служба
systemd-udev-trigger
выполняет исполняемый файл
udevadm
. Тогда зачем нам нужно запускать эти службы снова при помощи
sysinit.target
? мы и не делаем этого.
sysinit.target
запустит лишь те службы, которые пока не запущены и проигнорирует
любые действия для уже запущенных служб Давайте рассмотрим конкретные цели каждой из таких файлов элементов служб.
Файл элемента systemd kmod-static-nodes systemd
запускает исполняемый файл
kmod
с установленным переключателем static-nodes
.
Мы уже наблюдали в Главе 5, что
lsmod
, insmod
,
modinfo
, modprobe
,
depmod
и т.п. являются символическими ссылками к этому исполняемому
файлу kmod
.
#lsinitrd | grep -i kmod
lrwxrwxrwx 1 root root 11 Jul 25 03:54 usr/sbin/depmod -> ../bin/kmod
lrwxrwxrwx 1 root root 11 Jul 25 03:54 usr/sbin/insmod -> ../bin/kmod
lrwxrwxrwx 1 root root 11 Jul 25 03:54 usr/sbin/lsmod -> ../bin/kmod
lrwxrwxrwx 1 root root 11 Jul 25 03:54 usr/sbin/modinfo -> ../bin/kmod
lrwxrwxrwx 1 root root 11 Jul 25 03:54 usr/sbin/modprobe -> ../bin/kmod
lrwxrwxrwx 1 root root 11 Jul 25 03:54 usr/sbin/rmmod -> ../bin/kmod
# cat usr/lib/systemd/system/kmod-static-nodes.service | grep -v '#'
[Unit]
Description=Create list of static device nodes for the current kernel
DefaultDependencies=no
Before=sysinit.target systemd-tmpfiles-setup-dev.service
ConditionCapability=CAP_SYS_MODULE
ConditionFileNotEmpty=/lib/modules/%v/modules.devname
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/kmod static-nodes --format=tmpfiles --output=/run/tmpfiles.d/static-nodes.conf
Обладая переключателем static-nodes
, systemd просто собирает все
присутствующие статические узлы (устройства), представленные в данной системе. Зачем нам требуются статические
узлы в нашу эпоху обработки динамических узлов (udev
)? Существуют
некоторые модули, такие как fuse
или
ALSA
, которым требуются некоторые файлы устройств, присутствующие в
/dev
, или же они могут созавать их. Однако это может быть опасным, ибо
файлы такого устройства создаются kernel
или
/dev/fuse
. Таким образом, во избежание создания модулей из файлов
устройств, systemd создаст статические узлы, подобные /dev/fuse
или
/dev/snd/seq
, посредством
kmod-static-nodes.service
. Ниже приводятся соответствующие статические узлы,
создаваемые kmod-static-nodes.service
в некой системе Fedora.
# kmod static-nodes
Module: fuse
Device node: /dev/fuse
Type: character device
Major: 10
Minor: 229
Module: btrfs
Device node: /dev/btrfs-control
Type: character device
Major: 10
Minor: 234
Module: loop
Device node: /dev/loop-control
Type: character device
Major: 10
Minor: 237
Module: tun
Device node: /dev/net/tun
Type: character device
Major: 10
Minor: 200
Module: ppp_generic
Device node: /dev/ppp
Type: character device
Major: 108
Minor: 0
Module: uinput
Device node: /dev/uinput
Type: character device
Major: 10
Minor: 223
Module: uhid
Device node: /dev/uhid
Type: character device
Major: 10
Minor: 239
Module: vfio
Device node: /dev/vfio/vfio
Type: character device
Major: 10
Minor: 196
Module: hci_vhci
Device node: /dev/vhci
Type: character device
Major: 10
Minor: 137
Module: vhost_net
Device node: /dev/vhost-net
Type: character device
Major: 10
Minor: 238
Module: vhost_vsock
Device node: /dev/vhost-vsock
Type: character device
Major: 10
Minor: 241
Module: snd_timer
Device node: /dev/snd/timer
Type: character device
Major: 116
Minor: 33
Module: snd_seq
Device node: /dev/snd/seq
Type: character device
Major: 116
Minor: 1
Module: cuse
Device node: /dev/cuse
Type: character device
Major: 10
Minor: 203
Далее у нас имеется служба plymouth
, которая уже обладает
systemd-ask-password-console.path
, которым выступает некий файл элемента
.path
.
# cat usr/lib/systemd/system/systemd-ask-password-console.path | grep -v '#'
[Unit]
Description=Dispatch Password Requests to Console Directory Watch
Documentation=man:systemd-ask-password-console.service(8)
DefaultDependencies=no
Conflicts=shutdown.target emergency.service
After=plymouth-start.service
Before=paths.target shutdown.target cryptsetup.target
ConditionPathExists=!/run/plymouth/pid
[Path]
DirectoryNotEmpty=/run/systemd/ask-password
MakeDirectory=yes
Этот файл элемента .path
служит для активации на основе пути, но так как
мы не зашифровали свой корневой диск при помощи LUKS, у нас нет реального файла службы, который будет принимать
надлежащий пароль от соответствующего пользователя. Если бы мы настроили LUKS, мы бы обладали надлежащим файлом
элемента службы /usr/lib/systemd/system/systemd-ask-password-plymouth.service
,
как это показывается далее:
# cat usr/lib/systemd/system/systemd-ask-password-plymouth.service
[Unit]
Description=Forward Password Requests to Plymouth
Documentation=http://www.freedesktop.org/wiki/Software/systemd/PasswordAgents
DefaultDependencies=no
Conflicts=shutdown.target
After=plymouth-start.service
Before=shutdown.target
ConditionKernelCommandLine=!plymouth.enable=0
ConditionVirtualization=!container
ConditionPathExists=/run/plymouth/pid
[Service]
ExecStart=/usr/bin/systemd-tty-ask-password-agent --watch --plymouth
Как вы можете видеть, это запускает исполняемый файл systemd-tty-ask-password-agent
,
который запросит пароль при помощи plymouth
вместо
TTY
. Затем соответствующим файлом элемента службы выступает
systemd-journald.service
, который запустит для нас демон
journald
. До этого момента времени все сообщения регистрируются в установленном
сокете journald
, который systemd запускает в качестве самой первой службы
нашей последовательности запуска. Такой сокет journald
имеет размер 8 МБ.
Если этот сокет исчерпывает буфер, тогда все службы будут блокироваться пока данный сокет не станет доступным. Для
современных промышленных систем пространства буфера в 8 МБ более чем достаточно.
#vim usr/lib/systemd/system/sysinit.target.wants/systemd-journald.service
[Unit]
Description=Journal Service
Documentation=man:systemd-journald.service(8) man:journald.conf(5)
DefaultDependencies=no
Requires=systemd-journald.socket
After=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket syslog.socket
Before=sysinit.target
[Service]
OOMScoreAdjust=-250
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE
DeviceAllow=char-* rw
ExecStart=/usr/lib/systemd/systemd-journald
FileDescriptorStoreMax=4224
IPAddressDeny=any
LockPersonality=yes
MemoryDenyWriteExecute=yes
Restart=always
RestartSec=0
RestrictAddressFamilies=AF_UNIX AF_NETLINK
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
Sockets=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket
StandardOutput=null
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
SystemCallFilter=@system-service
Type=notify
WatchdogSec=3min
LimitNOFILE=524288
После этого, если вы желаете чтобы systemd загрузил некие особые модули статичным образом, вы можете получить
какую- то поддержку от нашей следующей службы, которой выступает
systemd-modules-load.service
.
# cat usr/lib/systemd/system/systemd-modules-load.service | grep -v '#'
[Unit]
Description=Load Kernel Modules
Documentation=man:systemd-modules-load.service(8) man:modules-load.d(5)
DefaultDependencies=no
Conflicts=shutdown.target
Before=sysinit.target shutdown.target
ConditionCapability=CAP_SYS_MODULE
ConditionDirectoryNotEmpty=|/lib/modules-load.d
ConditionDirectoryNotEmpty=|/usr/lib/modules-load.d
ConditionDirectoryNotEmpty=|/usr/local/lib/modules-load.d
ConditionDirectoryNotEmpty=|/etc/modules-load.d
ConditionDirectoryNotEmpty=|/run/modules-load.d
ConditionKernelCommandLine=|modules-load
ConditionKernelCommandLine=|rd.modules-load
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/lib/systemd/systemd-modules-load
TimeoutSec=90s
Данная службы выполняет /usr/lib/systemd/systemd-modules-load
. Этот
исполняемый файл распознаёт два параметра командной строки.
-
module_load
: Служит параметром командной строки ядра. -
rd.module_load
: Служит параметром командной строки dracut.
Когда вы передаёте параметр командной строки dracut, systemd-modules-load
статически загрузит такой модуль в память, но для этого данный модуль должен присутствовать в initramfs. Если он
отсутствует в initramfs, тогда вначале его требуется вытащить в initramfs. При генерации initramfs dracut считывает
файлы <module-name>.conf
отсюда:
/etc/modules-load.d/*.conf
/run/modules-load.d/*.conf
/usr/lib/modules-load.d/*.conf
Вам требуется создать надлежащий файл *.conf
и упомянуть в нём
то необходимое название модуля, которое вы хотите добавить в initramfs.
Допустим, здесь мы создали какой- то новый образ initramfs, который не обладает необходимым модулем
vfio
:
# dracut new.img
# lsinitrd | grep -i vfio
<no_output>
Для вытаскивания этого модуля статическим образом вовнутрь initramfs мы здесь создали соответствующий файл
vfio.conf
:
# cat /usr/lib/modules-load.d/vfio.conf
vfio
Вот мы перестраиваем initramfs:
# dracut new.img -f
# lsinitrd new.img | grep -i vfio
Jul 25 03:54 usr/lib/modules/5.3.16-300.fc31.x86_64/kernel/drivers/vfio
Jul 25 03:54 usr/lib/modules/5.3.16-300.fc31.x86_64/kernel/drivers/vfio/vfio.ko.xz
Jul 25 03:54 usr/lib/modules-load.d/vfio.conf
Как вы можете видеть, нужный нам модуль был вытащен вовнутрь initramfs и он был загружен в память как только
стартовала служба systemd-modules-load.service
.
На практике загрузка модулей статическим образом не лучшая мысль.В наши дни модули загружаются в память динамически когда они требуются или по запросу,в то время как статические модули будут всегда загружаться в память безотносительно от потребности или запроса.
Не путайтесь с каталогом /etc/modprobe.d
. Его применение состоит в
передаче надлежащих параметров модулям. Вот пример:
#cat /etc/modprobe.d/lockd.conf
options lockd nlm_timeout=10
nlm_timeour=10
является неким передаваемым в lockd
параметром. Помните, соответствующий файл .conf
внутри
/etc/modprobe.d
обязан обладать неким названием модуля. Через тот же самый файл
настройки вы можете установить некий псевдоим для значения названия модуля. Вот пример:
"alias my-mod really_long_modulename"
Затем systemd установит соответствующие параметры ядра sysctl
при помощи
systemd-sysctl.service
.
# cat usr/lib/systemd/system/systemd-sysctl.service | grep -v '#'
[Unit]
Description=Apply Kernel Variables
Documentation=man:systemd-sysctl.service(8) man:sysctl.d(5)
DefaultDependencies=no
Conflicts=shutdown.target
After=systemd-modules-load.service
Before=sysinit.target shutdown.target
ConditionPathIsReadWrite=/proc/sys/net/
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/lib/systemd/systemd-sysctl
TimeoutSec=90s
systemd-sysctl.service
запустит исполняемый файл
/usr/lib/systemd/systemd-sysctl
, который установит параметры настройки самого
ядра через считывание файлов *.conf
из трёж различных мест.
/etc/sysctl.d/*.conf
/run/sysctl.d/*.conf
/usr/lib/sysctl.d/*.conf
Вот некий пример:
# sysctl -a | grep -i swappiness
vm.swappiness = 60
Установленным по умолчанию значением параметра swappiness
выступает
60
. Если вы желаете изменит его на 10
и это надлежит оставить на постоянной основе между перезапусками, тогда добавьте его в
/etc/sysctl.d/99-sysctl.conf
.
#cat /etc/sysctl.d/99-sysctl.conf
vm.swappiness = 10
Вы имеете возможность перезагрузки и установки параметров sysctl
таким образом:
# sysctl -p
vm.swappiness = 10
Для осуществления таких изменений в initramfs вам следует повторно сгенерировать initramfs. В момент следующего
запуска systemd-sysctl.service
считает значение
swappiness
из файла
99-sysctl.conf
и установит его в своей среде initramfs.
systemd создаёт большое число временных файлов для своего гладкого выполнения. После установки необходимых
параметров sysctl
, он выполняет следующую службу, имеющую название
systemd-tmpfiles-setup-dev.service
, которая запускает исполняемый файл
/usr/bin/systemd-tmpfiles --prefix=/dev --create --boot
. Это создаст
относящиеся к dev
временные файлы в соответствии с такими правилами:
/etc/tmpfiles.d/*.conf
/run/tmpfiles.d/*.conf
/usr/lib/tmpfiles.d/*.conf
После sysinit.target
sytemd проверит созданы ли все необходимые сокеты или
нет посредством sockets.target
.
# ls usr/lib/systemd/system/sockets.target.wants/ -l
total 0
32 Jan 3 18:05 systemd-journald-audit.socket -> ../systemd-journald-audit.socket
34 Jan 3 18:05 systemd-journald-dev-log.socket -> ../systemd-journald-dev-log.socket
26 Jan 3 18:05 systemd-journald.socket -> ../systemd-journald.socket
31 Jan 3 18:05 systemd-udevd-control.socket -> ../systemd-udevd-control.socket
30 Jan 3 18:05 systemd-udevd-kernel.socket -> ../systemd-udevd-kernel.socket
Итак, наш процесс запуска завершил свою последовательность до sysinit.target
.
Обратите внимание на
Рисунок 7-65 .
Проблема 8 "Can’t Boot" (sysctl.conf)
Проблема: После перезапуска возникает ситуация паники ядра и система не способна запускаться. Вот что наблюдается в нашей консоли:
[ 4.596220] Mem-Info:
[ 4.597455] active_anon:566 inactive_anon:1 isolated_anon:0
[ 4.597455] active_file:0 inactive_file:0 isolated_file:0
[ 4.597455] unevictable:19700 dirty:0 writeback:0 unstable:0
[ 4.597455] slab_reclaimable:2978 slab_unreclaimable:3180
[ 4.597455] mapped:2270 shmem:22 pagetables:42 bounce:0
[ 4.597455] free:23562 free_pcp:1982 free_cma:0
[ 4.611930] Node 0 active_anon:2264kB inactive_anon:4kB active_file:0kB inactive_file:0kB unevictable:78800kB isolated(anon):0kB isolated(file):0kB mapped:9080kB dirty:0kB writeback:0kB shmem:88kB shmem_thp: 0kB shmem_pmdmapped: 0kB anon_thp: 0kB writeback_tmp:0kB unstable:0kB all_unreclaimable? yes
[ 4.621748] Node 0 DMA free:15900kB min:216kB low:268kB high:320kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB writepending:0kB present:15992kB managed:15908kB mlocked:0kB kernel_stack:0kB pagetables:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB
[ 4.632561] lowmem_reserve[]: 0 1938 4764 4764 4764
[ 4.634609] Node 0 DMA32 free:38516kB min:27404kB low:34252kB high:41100kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB writepending:0kB present:2080628kB managed:2015092kB mlocked:0kB kernel_stack:0kB pagetables:0kB bounce:0kB free_pcp:2304kB local_pcp:0kB free_cma:0kB
[ 4.645636] lowmem_reserve[]: 0 0 2826 2826 2826
[ 4.647886] Node 0 Normal free:39832kB min:39956kB low:49944kB high:59932kB active_anon:2264kB inactive_anon:4kB active_file:0kB inactive_file:0kB unevictable:78800kB writepending:0kB present:3022848kB managed:2901924kB mlocked:0kB kernel_stack:1776kB pagetables:168kB bounce:0kB free_pcp:5624kB local_pcp:1444kB free_cma:0kB
[ 4.659458] lowmem_reserve[]: 0 0 0 0 0
[ 4.661319] Node 0 DMA: 1*4kB (U) 1*8kB (U) 1*16kB (U) 0*32kB 2*64kB (U) 1*128kB (U) 1*256kB (U) 0*512kB 1*1024kB (U) 1*2048kB (M) 3*4096kB (M) = 15900kB
[ 4.666730] Node 0 DMA32: 1*4kB (M) 0*8kB 1*16kB (M) 1*32kB (M) 1*64kB (M) 0*128kB 0*256kB 1*512kB (M) 3*1024kB (M) 1*2048kB (M) 8*4096kB (M) = 38516kB
[ 4.673247] Node 0 Normal: 69*4kB (UME) 16*8kB (M) 10*16kB (UME) 7*32kB (ME) 5*64kB (E) 1*128kB (E) 1*256kB (U) 9*512kB (ME) 9*1024kB (UME) 2*2048kB (ME) 5*4096kB (M) = 39892kB
[ 4.680399] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=1048576kB
[ 4.683930] Node 0 hugepages_total=2303 hugepages_free=2303 hugepages_surp=0 hugepages_size=2048kB
[ 4.687749] 19722 total pagecache pages
[ 4.689841] 0 pages in swap cache
[ 4.691580] Swap cache stats: add 0, delete 0, find 0/0
[ 4.694275] Free swap = 0kB
[ 4.696039] Total swap = 0kB
[ 4.697617] 1279867 pages RAM
[ 4.699229] 0 pages HighMem/MovableOnly
[ 4.700862] 46636 pages reserved
[ 4.703868] 0 pages cma reserved
[ 4.705589] 0 pages hwpoisoned
[ 4.707435] Tasks state (memory values in pages):
[ 4.709532] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name
[ 4.713849] [ 341] 0 341 5118 1178 77824 0 -1000 (md-udevd)
[ 4.717805] Out of memory and no killable processes...
[ 4.719861] Kernel panic - not syncing: System is deadlocked on memory
[ 4.721926] CPU: 3 PID: 1 Comm: systemd Not tainted 5.3.7-301.fc31.x86_64 #1
[ 4.724343] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.12.0-2.fc30 04/01/2014
[ 4.727959] Call Trace:
[ 4.729204] dump_stack+0x5c/0x80
[ 4.730707] panic+0x101/0x2d7
[ 4.747357] out_of_memory.cold+0x2f/0x88
[ 4.749172] __alloc_pages_slowpath+0xb09/0xe00
[ 4.750890] __alloc_pages_nodemask+0x2ee/0x340
[ 4.752452] alloc_slab_page+0x19f/0x320
[ 4.753982] new_slab+0x44f/0x4d0
[ 4.755317] ? alloc_slab_page+0x194/0x320
[ 4.757016] ___slab_alloc+0x507/0x6a0
[ 4.758768] ? copy_verifier_state+0x1f7/0x270
[ 4.760591] ? ___slab_alloc+0x507/0x6a0
[ 4.763266] __slab_alloc+0x1c/0x30
[ 4.764846] kmem_cache_alloc_trace+0x1ee/0x220
[ 4.766418] ? copy_verifier_state+0x1f7/0x270
[ 4.768120] copy_verifier_state+0x1f7/0x270
[ 4.769604] ? kmem_cache_alloc_trace+0x162/0x220
[ 4.771098] ? push_stack+0x35/0xe0
[ 4.772367] push_stack+0x66/0xe0
[ 4.774010] check_cond_jmp_op+0x1fe/0xe60
[ 4.775644] ? _cond_resched+0x15/0x30
[ 4.777524] ? _cond_resched+0x15/0x30
[ 4.779315] ? kmem_cache_alloc_trace+0x162/0x220
[ 4.780916] ? copy_verifier_state+0x1f7/0x270
[ 4.782357] ? copy_verifier_state+0x16f/0x270
[ 4.783785] do_check+0x1c06/0x24e0
[ 4.785218] bpf_check+0x1aec/0x24d4
[ 4.786613] ? _cond_resched+0x15/0x30
[ 4.788073] ? kmem_cache_alloc_trace+0x162/0x220
[ 4.789672] ? selinux_bpf_prog_alloc+0x1f/0x60
[ 4.791564] bpf_prog_load+0x3a3/0x670
[ 4.794915] ? seq_vprintf+0x30/0x50
[ 4.797085] ? seq_printf+0x53/0x70
[ 4.799013] __do_sys_bpf+0x7e5/0x17d0
[ 4.800909] ? __fput+0x168/0x250
[ 4.802352] do_syscall_64+0x5f/0x1a0
[ 4.803826] entry_SYSCALL_64_after_hwframe+0x44/0xa9
[ 4.805587] RIP: 0033:0x7f471557915d
[ 4.807638] Code: 00 c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d fb 5c 0c 00 f7 d8 64 89 01 48
[ 4.814732] RSP: 002b:00007fffd36da028 EFLAGS: 00000246 ORIG_RAX: 0000000000000141
[ 4.818390] RAX: ffffffffffffffda RBX: 000055fb6ad3add0 RCX: 00007f471557915d
[ 4.820448] RDX: 0000000000000070 RSI: 00007fffd36da030 RDI: 0000000000000005
[ 4.822536] RBP: 0000000000000002 R08: 0070756f7267632f R09: 000001130000000f
[ 4.826605] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
[ 4.829312] R13: 0000000000000006 R14: 000055fb6ad3add0 R15: 00007fffd36da1e0
[ 4.831792] Kernel Offset: 0x26000000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff)
[ 4.835316] ---[ end Kernel panic - not syncing: System is deadlocked on memory ]---
Итак, это проблема "kernel panic". Нам необходимо вначале вычленить эту проблему, ибо проблема паники ядра может возникать в тысячах обстоятельств. Если вы обратите внимание на выделенные сообщения паники чдра, становится очевидным, что был вызван некий "OOM-killer", поскольку наша система исчерпала память. Наше ядро пытается освободить память от кэширования и даже пробует воспользоваться пространством подкачки страниц, однако в конечном счёте она бросает это и наше ядро паникует.
Итак, мы выделили свою проблему. Нам требуется сосредоточиться на том как мы потребляем имеющуюся память Механизм OOM ( OS out-of-memory) будет вызываться когда наша система пребывет под безмерным давлением на память.
Существуют три обстоятельства когда мехонизм OOM-killer вызывается на протяжении последовательности запуска:
-
Наша система обладает действительно низким значением физически установленной памяти.
-
Были установлены неверные параметры настройки нашего ядра.
-
Какие- то модули обладают утечкой памяти.
Данная система обладает 4.9 ГБ физической памяти, что немного, но очевидно, больше чем достаточно для того чтобы ядро Linux завершило свою последовательность запуска.
Какие- то модули способны обладать утечками памяти, однако выявление этого будет сложной задачей. Поэтому, мы для начала убедимся что все относящиеся к памяти параметры настройки ядра были установлены правильно:
-
Для этого мы выбросим себя вовнутрь initramfs. На Рисунке 7-66 мы передаём
rd.break
в качестве параметра командной строки ядра.
-
Мы повторно смонтируем
sysroot
в режиме чтения- записи и проверим установленные параметрыsysctl
.switch_root:/# cat /proc/sys/vm/nr_hugepages 2400
-
Наша проблема состоит в неверно зарезервированном числе hugepages. Мы отключим эту настройку в соответствии с Рисунком 7-67.
После перезапуска наша система получает возможность успешно запускаться. Давайте попробуем разобраться что пошло не так. Данная система обладает 4.9 ГБ памяти и ранее не было зарезервированных hugepages.
# cat /proc/meminfo | grep -e MemTotal -e HugePages_Total
MemTotal: 4932916 kB
HugePages_Total: 0
# cat /proc/sys/vm/nr_hugepages
0
Обычной страницей выступает страница с размером в 4 кБ, в то время как hugepage обладает размером 2 МБ, что в 512 раз больше обычного размера. Hugepage обладают собственными преимуществами, но в то же самое время они обладают и собственными недостатками.
-
Hugepage не обладают возможностью подкачки.
-
Само ядро не применяет hugepage.
-
Применять hugepage способны лишь те приложения, которые осведомлены о hugepage.
Кто- то ошибочно настроил hugepage на значение 2 400 и перестроил initramfs.
# echo "vm.nr_hugepages = 2400" >> /etc/sysctl.conf
# sysctl -p
vm.nr_hugepages = 2400
# dracut /boot/new.img
# reboot
Таким образом, 2 400 hugepage = 4.9 ГБ, что составляет всю установленную основную память и так как вся оперативная
память была зарезервирована под hugepage, наше ядро не имеет возможности её использования. Итак, при запуске,скогда
systemd достигает этапа sysinit.target
и выполняет
systemd-sysctl.service
, эта служба считывает файл
sysctl.conf
из initramfs и резервирует 4.9 ГБ hugepage, которые не может
использовать установленное ядро. Тем самым, наше ядро само по себе вылетает за пределы памяти и система паникует.
Итак, мы достигли basic.target
. Как мы знаем, цели служат для синхронизации
или группирования имеющихся элементов. basic.target
это некая точка
синхронизации для запаздывающих в запуске служб.
# cat usr/lib/systemd/system/basic.target | grep -v '#'
[Unit]
Description=Basic System
Documentation=man:systemd.special(7)
Requires=sysinit.target
Wants=sockets.target timers.target paths.target slices.target
After=sysinit.target sockets.target paths.target slices.target tmp.mount
RequiresMountsFor=/var /var/tmp
Wants=tmp.mount
Следовательно, basic.target
будет успешной когда все имевшиеся ранее
файлы элементов этапов requires
, wants
и after
успешно стартовали. Фактически, почти все имеющиеся службы обладают
добавленными в свои файлы элементов After=basic.target
.
systemd запустит службу dracut-pre-mount.service
непосредственно перед
монтированием своей корневой файловой системы пользователя изнутри initramfs. Поскольку это служба dracut, она
выполнится только когда её пользователь передал необходимый параметр командной строки dracut
rd.break=pre-mount
.
Рисунок 7-68
показывает что мы передали в качестве параметра командной строки ядра
rd.break=pre-mount
.
Как вы видите на
Рисунке 7-69,
это выбрасывает нас в соответствующую аварийную оболочку и необходимая корневая файловая система пользователя
не смонтирована в sysroot
. Да, я сказал, что это выкинуло нас в соответствующую
аварийную оболочку, новы удивитесь обнаружить, что такая аварийная оболочка не что иное как просто оболочка bash,
предоставляемая systemd, однако в тот момент, когда запуск ещё не был закончен. Для того чтобы лучше разобраться с
такой аварийной оболочкой, мы прервёмся на время в своей последовательности запуска и обсудим оболочки отладки initramfs
в Главе 8. Мы вернёмся к своей приостановленной последовательности
запуска в Главе 9.