Глава 7. systemd (Часть I)

Вот что нам известно относительно последовательности запуска к данному моменту:

  1. Начальный загрузчик запускает в памяти необходимые ядро и initramfs.

  2. Запущенное ядро будет загружено в определённом местоположении (неком зависящем от архитектуры месте), в то время как initramfs будет загружаться в любом доступном месте.

  3. Это ядро распаковывает себя само применяя свой заголовок из файла vmlinuz.

  4. Установленное ядро распаковывает initramfs в оперативной памяти ( init/initramfs.c) и монтирует её в качестве временной корневой файловой системы (/) в основной памяти.

  5. Запущенное ядро запускает (init/main.c) соответствующий systemd в качестве самого первого процесса с PID-1 из временной корневой файловой системы.

  6. systemd обнаруживает необходимую корневую файловую систему пользователя и выполняет chroot в неё.

Данная глава направлена на то, как systemd, которая ответвляется из initramfs, управляет монтированием корневой файловой системы пользователя, а также мы увидим подробную последовательность запуска systemd из initramfs. Однако перед этим нам надлежит разобраться с systemd в качестве процесса.

Я предоставлю здесь слово странице руководства:

"После того как обнаружена и смонтирована корневая файловая система initrd передаёт управление системному диспетчеру хоста (например, systemd(1)), хранимому в смонтированной корневой файловой системе, который далее отвечает за проверку всего оставшегося оборудования, монтируя все необходимые файловые системы и порождая все настраиваемые службы."

Структура

systemd впервые был представлен в Fedora 15. Все мы знаем, что systemd выступил заменой для сценариев init (буквально, /sbin/init это теперь символическая ссылка на /usr/lib/systemd/systemd) и впечатляющим образом снизил время запуска. Однако, на практике sytemd намного больше простой замены для init. Вот что выполняет systemd:

  1. Он сопровождает журналирование в journalctl.

  2. Он интенсивно применяет cgroups версии 1 и 2.

  3. Он снижает время запуска.

  4. Он управляет элементами (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

    Управление для элементов служб такими ресурсами как ЦПУ, память, ввод/ вывод

Файлы структурных элементов будут храниться и загружаться из следующих местоположений:

Таблица 7-2. Пути структурных элементов
Элемент Назначение

/etc/systemd/system

Локальная конфигурация

/run/systemd/system

Элементы времени исполнения

/usr/lib/systemd/system

Устанавливаемые пакетами элементы

/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 снижает общее время запуска в своём блоге. Этот блог является одним из наилучших ресурсов когда вы желаете поглубже вникнуть в мир самого systemd.

Имеются четыре демона: syslog, dbus, avahi и bluetooth.

syslog требуется всем демонам для регисрации своих сообщений. Поэтому syslog выступает в качестве необходимого для всех прочих демонов. avahi нуждается в запущенных syslog dbus. Для bluetooth требуются запущенными dbus и syslog, однако avahi не должен исполняться. При использовании модели Sysv/init происходит следующее:

  1. Первым запускается syslog.

  2. Когда он успешно готов, будет запущена служба dbus.

  3. После dbus будет запущена avahi.

  4. Наконец, будет запускаться служба bluetooth. Смотрите Рисунок 7-1.

     

    Рисунок 7-1


    Модель init

bluetooth и avahi не зависят друг от друга, однако bluetooth приходится дожидаться пока не будет запущена avahi. Подобные Ubuntu дистрибутивы применяют вместо init upstart, который улучшает до некоторой степени значение времени запуска. В upstart те службы, которые не зависят друг от друга будут запускаться параллельно, что означает что avahi и bluetooth запустятся совместно. Для справки обратитесь к Рисунку 7-2.

 

Рисунок 7-2


Модель upstart

В systemd все имеющиеся службы запускаются в одно и то же самое время при помощи sockets. Вот некий пример:

  1. systemd создаст некий сокет для syslog, который был заменён journald.

  2. Сокет /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.

  3. Для dbus в /run/dbus/system_bus_socket создаётся его сокет. Для запуска dbus нуждается в запущенном journald, но так как данная система всё ещё запускается и journald/syslog ещё пока не полностью запущен, dbus будет регистрировать свои сообщения в сокете journald dbus, а когда journald полностью готов, он осуществит выборку всех сообщений из этого сокета.

  4. То же самое касается и службы bluetooth; для её запуска требуется запущенной соответствующая служба dbus. Поэтому systemd, перед тем как будет запущена служба dbus, создаст сокет /run/dbus/system_bus_socket. Служба bluetooth не будет дожидаться запуска dbus. Для лучшего понимания вы можете обратиться к Рисунку 7-3.

     

    Рисунок 7-3


    Модель systemd

  5. Если у созданного systemd сокета заканчивается буфер, тогда служба bluetooth будет заблокирована до тех пор пока не станет доступным её сокет. Такой подход с сокетами существенным образом снижает значение времени запуска.

Подход на основе сокетов первоначально был предпринят в macOS. В то время он носил название launchd Из него и почерпнул вдохновение Леннарт Поеттеринг.

systemd-analyze

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 
		
 

Рисунок 7-4


Выработанный график запуска

Далее приводятся некоторые из прочих инструментариев, предоставляемых systemd-analyse, которые можно применять для выявления времени запуска.

Таблица 7-1. Инструментарий systemd-analyse
systemd-analyze <tool> Описание

time

Выводит на печать время, затрачиваемое ядром

blame

Выводит на печать список запущенных элементов, упорядоченных по времени на init

critical-chain [UNIT...]

Выводит на печать дерево своих критичных ко времени цепочек элементов

plot

Выводит SVG график, отображающий инициализацию служб

dot [UNIT...]

Выводит график зависимостей в формате dot(1)

blame

Выводит на печать список запущенных элементов, упорядоченных по времени на init

log-level [LEVEL]

Получает/ устанавливает пороговые значения регистрации для диспетчера

log-target [TARGET]

Получает/ устанавливает цель регистрации для диспетчера

dump

Выводит состояние сериализации диспетчера служб

cat-config

Отображает файл настроек и вставки

unit-files

Перечисляет файлы и символические ссылки для элементов

units-paths

Перечисляет каталоги загрузки для элементов

exit-status [STATUS...]

Перечисляет определения состояний выхода

syscall-filter [NAME...]

Выводит на печать список syscalls в фильтре seccomp

condition...

Выполняет оценку условий и объявлений

verify FILE...

Проверяет правильность файлов элемента

service-watchdogs [BOOL]

Получает/ устанавливает состояние сторожевой схемы службы

calendar SPEC...

Проверяет повторяемые по календарю события

timestamp...

Проверяет значение временной отметки

timespan SPAN...

Проверяет временной интервал

security [UNIT...]

Анализирует безопасность элемента

Проблема 6 "Can’t Boot" (systemd)

Проблема:: Система успешно запускается, однако служба nagios отказывает при запуске в момент загрузки.

Вот основные этапы для решения данной проблемы:

  1. Для начала нам требуется изолировать данную проблему. при появлении на своём экране GRUB удалите параметры командной строки ядра rhgb quiet.

  2. Детализированный журнал показывает что данная система способна запускаться, однако служба nagios отказывает в старте при запуске. Как вы можете видеть, ответственная за сетевую среду служба systemd NetworkManager успешно запустилась. Это означает что нет проблем с сетевым взаимодействием.

    
    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.
    		
  3. Служба 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 может оказаться не способным взаимодействовать со своими серверами.

  1. Для решения такой проблемы 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 сразу же осуществит возврат, причём вне зависимости от значения текущего состояния сети.)"

  2. После включения NetworkManager-wait-online-service данная проблема была разрешена, но время запуска слегка снизилось. Как вы можете видеть из Рисунка 7-5, большая часть времени запуска была съедена NetworkManager-wait-online-service, который выполняет ожидание.

     

    Рисунок 7-5


    Наш график после включения NetworkManager-wait-online-service

    
    > 
    		

systemd предоставляет ещё один инструмент, bootchart, который в целом является демоном, через который вы можете проводить анализ производительности процесса запуска Linux. Он будет собирать сведения времени запуска и составлять ни их основе некий график. Вы можете рассматривать bootchart в качестве некоторой расширенной версии systemd-analyze plot. Для применения данного инструмента, как это показано на Рисунке 7-6, вам потребуется передать значение полного пути исполняемого файла systemd-bootchart в параметр командной строки ядра init.

 

Рисунок 7-6


Параметры командной строки ядра

После успешного процесса запуска, как это можно видеть на Рисунке 7-7, данный инструмент создаст подробный графический образ в /run/log/bootchart*. После выработки этого образа, systemd-bootchart передаст управление в systemd, а systemd продолжит свою процедуру запуска.

 

Рисунок 7-7


График bootchart

Поскольку мы теперь разобрались с основами systemd, мы можем продолжить своб приостановленную последовательность запуска. На данный момент мы достигли того этапа, на котором наше ядро распаковало initramfs в ОЗУ и запустило из него исполняемый файл systemd. После запуска процесса systemd, он будет следовать обычной последовательности запуска.

Поток systemd внутри initramfs

systemd будет запущен из initramfs и протекает показанной на Рисунке 7-8 последовательностью запуска. Гаральд Хойер (создавший dracut initramfs и являющийся ведущим разработчиком systemd) разработал эту блок- схему, которая также доступна и на страницах руководства systemd.

 

Рисунок 7-8



Блок- схема запуска

Эта блок- схема взята со страницы руководства dracut. Окончательная цель systemd состоит в процедуре запуска и монтирования корневой файловой системы пользователя внутри initramfs (sysroot) с последующим переключением в неё. После того как systemd был switch_rooted в новую корневую файловую систему (пользователя), он покидает свою среду initramfs и продолжает свою процедуру запуска стартуя службы пространства пользователя, такие как httpd, mysql и т.п.. Он также отрисует рабочий стол/ GUI если его пользователь запускает данную систему в графическом режиме. Сфера действий данной книги заключается в охвате последовательности запуска вплоть до монтирования systemd своей корневой файловой системы пользователя и последующего переключения в неё. Существует ряд причин не охватывать эту последовательность запуска после switch_root. Я упоминаю тут те причины, которые очень важны:

  • Окончательная цель запуска состоит в монтировании корневой файловой системы пользователя и присутствия в ней самого пользователя, которые и рассматривает подробно эта книга.

  • Все действия systemd, выполняемые после initramfs просто понять, так как systemd предпринимает подобные действия, просто в новой среде корневой файловой системы.

  • Промышленные системы, как правило, не запускаются в графическом режиме.

  • Linux обладает несколькими рабочими столами, такими как GNOME, KDE, Cinnamon, Unity и т.п.. Всякий пользователь имеет свой собственный предпочтительный ему рабочий стол и практически невозможно документировать все этапы каждого из рабочих столов при запуске.

Итак, осознавая всё это, в данной главе мы рассмотрим всю последовательность запуска вплоть до basic.target. Рассмотрите, пожалуйста, Рисунок 7-9.

 

Рисунок 7-9


Последовательность запуска вплоть до basic.target

systemd-journal.socket

Все процессы обязаны регистрировать свои сообщения. На самом деле, процесс, служба или демон запустятся только если имеется возможность регистрации его сообщений в механизме журналирования самой ОС. В наши дни таким механизмом ведения журналов ОС выступает 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.

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 }
 	   

Вот последовательность запуска к данному моменту:

  1. Наш начальный загрузчик собирает параметры командной строки от своего пользователя и хранимые им в его собственном файле настроек (grub.cfg).

  2. Он передаст эти параметры командной строки в само ядро заполняя заголовок самого ядра.

  3. Запущенное ядро распаковывает себя само и копирует обнаруженные в заголовке ядра параметры командной строки.

  4. Это ядро распаковывает initramfs в память и применяет его в качестве временной корневой файловой системы.

  5. В той же самой процедуре наше ядро подготавливает виртуальные файловые системы, такие как proc, sys, dev devpts, shm и т.п..

  6. Запущенное ядро сохраняет параметры своей командной строки в файле /proc/cmdline.

  7. 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 показывает данное условие.

 

Рисунок 7-10


Условие

Когда наш пользователь передал rd.break=cmdline, тогда этот сценарий вызывает функцию с названием emergency_shell. Как и предполагает само название, она будет представлять отладочную оболочку и если эта отладочная оболочка успешно запущена, тогда она вызывает другую функцию с названием source_hook и передаёт в неё параметр cmdline. Написавший этот код для предоставления пользователям отладочной оболочки - гениальный програмист!

На данном этапе мы не будем обсуждать функцию оболочки критической ситуации так как сначала нам требуется побольше разобраться с systemd. Следовательно, мы обсудим её более подробно в Главе 8.

Рисунок 7-11 показывает блок- схему работающих элементов dracut-cmdline.service.

 

Рисунок 7-11


Блок- схема dracut-cmdline.service

Двинемся далее, корневой файловой системой может пользователя быть просто /dev/sda5, однако на то же самое устройство sda5 можно ссылаться через uuid, partuuid и label. В конце концов, все прочие ссылки sda5 обязаны достичь /dev/sda5; следовательно наше ядро готовит файлы символических ссылок для всех этих различных названий устройств под /dev/disk/. Обратитесь, пожалуйста, к Рисунку 7-12.

 

Рисунок 7-12


Содержимое каталога /dev/disk

Тот же самый сценарий /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 ядра.

 

Рисунок 7-13


Параметр командной строки ядра

Запущенное ядро распакует initramfs, запустит процесс systemd, systemd проинициализирует сокеты journald, как вы можете видеть на Рисунке 7-14, systemd сбросит нас в оболочку командной строки, поскольку мы запросили systemd прервать (hook) свою последовательность запуска перед выполнением dracut-cmdline.

 

Рисунок 7-14


Специальная точка входа командной строки

В настоящий момент мы находимся внутри initramfs и мы приостановили (dracut hooked последовательность запуска systemd под systemd-journal.socket. Поскольку dracut-cmdline.service ещё пока не стартовала, systemd пока ещё не собрал параметры командной строки ядра, такие как root, rsflags и fstype из /proc/cmdline. Для лучшего понимания обратитесь, пожалуйста, к Рисунку 7-15. Кроме того необходимая символическая ссылка /dev/disk пока ещй не была создана dracut.

 

Рисунок 7-15


Специальная точка входа командной строки

Поскольку systemd пока ещё не собрал название корневой файловой системы пользователя, нет никаких сомнений в том, что вы не обнаружите корневую файловую систему пользователя смонтированной внутри initramfs. sysroot это каталог внутри initramfs, в котором монтируется корневая файловая система пользователя. Отсылаем вас к Рисунку 7-16.

 

Рисунок 7-16


Каталог sysroot

Однако если мы не передадим никаких аргументов в rd.break или просто выйдем из своей текущей оболочки командной строки, мы будем сброшены в оболочку switch_root. Эта оболочка switch_root является окончательным этапом последовательности запуска systemd внутри initramfs. На Рисунке 7-17 вы можете видеть что мы передаём rd.break без каких бы то ни было аргументов.

 

Рисунок 7-17


Параметр командной строки ядра rd.break

Как вы можете видеть на Рисунке 7-18, в своей оболочке switch_root, благодаря тому что dracut-cmdline.service уже был выполнен, вы обнаружите параметры командной строки ядра, собранные systemd. Кроме того, корневая файловая система пользователя была смонтирована внутри initramfs под sysroot.

 

Рисунок 7-18


Специальная точка входа switch_root

Если мы выполним выход на этом этапе, switch_root (pivot_root) будет выполнен systemd, и покинет свою среду initramfs. Рисунок 7-19 в конечном счёте отобразит рабочий стол.

 

Рисунок 7-19


Экран входа в систему Fedora

Возвращаясь к нашей последовательности запуска на данный момент, мы достигли этапа pre-udev. Для этого вы можете обратиться к Рисунку 7-20.

 

Рисунок 7-20


Рассмотренная к данному моменту последовательность запуска

dracut-pre-udev.service

Затем 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 как параметр командной строки ядра.

 

Рисунок 7-21


Передача параметра командной строки ядра pre-udev

Чтобы разобраться со специальной точкой входа pre-udev вы просто можете просмотреть список содержимого /dev и на Рисунке 7-22 вы можете обратить внимание на то что нет устройства с названием sda sda это наш HDD, в котором у нас имеется корневая файловая система.

 

Рисунок 7-22


Специальная точка входа pre-udev

Основная причина отсутствия файлов устройства 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 такова:

  1. Когда пользователь обладает подключённым в момент запуска к своей системе устройство bluetooth, именно само ядро, либо скомпилированные в драйвере ядра или вставляемы позднее модули выявляют это устройство bluetooth и регистрируют его объект в /sys.

  2. Позднее имеющееся ядро создаст некий файл устройства в точке монтирования /dev. После создания такого файла устройства, это ядро отправит uevent в udevd.

  3. udevd будет ссылаться на lib/udev/rules.d/99-systemd.rules из initrafs и вызовет systemd. Что касается этого тега, systemd предполагает обработку всего что в нём остаётся.

  4. systemd исполнит bluetooth.target, который выполнит соответствующий исполняемый файл bluetoothd и имеющееся оборудование bluetooth будет готово к применению.

Конечно, bluetooth не является тем оборудованием, которое требуется в момент запуска. Я взял его просто в качестве примера для более простого понимания.

Итак, мы достигли systemd-udev.service. systemd продолжит нашу последовательность запуска и исполнит dracut-pre-trigger.service. Вы можете видеть общую последовательность запуска на Рисунке 7-23.

 

Рисунок 7-23


Последовательность запуска к данному моменту

dracut-pre-trigger.service

Последовательность запуска systemd из initramfs будет прервана (захвачена) когда ваш пользователь передал параметр командной строки dracut rd.break=pre-trigger. Вы можете видеть на Рисунке 7-24 что мы передали pre-trigger в качестве аргумента для параметра командной строки ядра rd.break.

 

Рисунок 7-24


Параметр командной строки ядра rd.break=pre-trigger

Это сбросит нас в оболочку 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.

 

Рисунок 7-25


Специальная точка входа pre-trigger

Это обусловлено тем, что наш триггер udevadm пока ещё небыл выполнен. наша служба dracut-pre-trigger.service исполняет только udevadm control --reload, что перезапускает соответствующие правила udev. Как показано на Рисунке 7-26, systemd-udev.service была запущена, но служба systemd-udev-trigger пока еще не стартовала.

 

Рисунок 7-26


Специальная точка входа pre-trigger

systemd-udev-trigger.service

Рисунок 7-27 отображает те стадии запуска, которые мы уже прошли.

 

Рисунок 7-27


Последовательность запуска на данный момент

Как мы уже видели, с помощью pre-udev каталог /dev не наполняется так как служба systemd-udevd.service сама по себе не стартовала. С pre-trigger то же самое: /dev не наполняется, но соответствующая служба udevd была запущена. Эта служба udevd создаст некую среду для старта/ запуска разнообразных инструментов udev, таких как udevadm. Применяя то окружение, которое предоставляется демоном udevd, как вы можете видеть на Рисунке 7-28, внутри pre-trigger мы будем иметь возможность выполнять udevadm, которую мы не способны применять в своей оболочке pre-udev.

 

Рисунок 7-28


Специальная точка входа pre-trigger

Как вы можете видеть внутри установленного переключателя 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


Специальная точка входа pre-trigger

Как мы можем видеть на Рисунке 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.

 

Рисунок 7-30


Udevadm в специальной точке входа pre-udev

Именно это важно и для dracut-pre-trigger.service или специальной точки входа pre-trigger.

Представленная на Рисунке 7-31 блок- схема поможет вам понять те шаги, которые на данный момент были предприняты systemd внутри initramfs. Эта блок-схема станет ещё более понятной после прочтения Главы 8. Я настоятельно рекомендую вернуться к этой главе после завершения Главы 8.

 

Рисунок 7-31


Наша блок-схема

local-fs.target

Как вы можете увидеть на Рисунке 7-32, мы достигли этапа local-fs-target запуска.

 

Рисунок 7-32


Охваченная к данному моменту последовательность запуска

Итак, 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.

  1. Прежде всего я удалил содержимое каталога /tmp. Это обусловлено тем, что генератор fstab создаёт свои файлы монтирования элементов внутри /tmp.

  2. Запускаю исполняемый файл systemd-fstab-generator и, как вы можете увидеть на Рисунке 7-33, создаётся пара файлов в /tmp.

     

    Рисунок 7-33


    systemd-fstab-generato0072

  3. Это создаёт файл элемента sysroot.mount. Как и предполагает его название, он был создан для монтирования соответствующей корневой файловой системы пользователя. Этот файл элемента был создан считыванием /proc/cmdline. Чтобы просмотреть содержимое sysroot.mount обратитесь к Рисунку 7-34.

     

    Рисунок 7-34


    Файл sysroot.mount

    Эта корневая файловая система монтируется из sda5 (с применением UUID) в соответствующем каталоге sysroot.

  4. Проверьте раздел requires файла элемента sysroot.mount. Он предписывает, что вначале должен быть исполнен systemd-fsck-root.service, причём до монтирования надлежащей корневой файловой системы. Рисунок 7-35 отображает 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-36


Параметр командной строки ядра

Как мы можем видеть на Рисунке 7-37, в специальной точке входа pre-pivot наша корневая файловая система будет монтироваться совместно с файловой системой usr, поскольку наша особая точка входа pre-pivot останавливает нашу последовательность запуска после монтирования своей корневой файловой системы пользователя в sysroot. Однако opt, var и boot не будут смонтированы.

 

Рисунок 7-37


Специальная точка входа pre-pivot

Даже если вы запустите systemd-fstab-generator, вы обнаружите что будут созданы только файлы элементов монтирования usr и root. На Рисунке 7-38 вы можете наблюдать вывод systemd-fstab-generator.

 

Рисунок 7-38


systemd-fstab-generator в специальнрй точке входа pre-pivot

Это подтверждает, что в окружении initramfs могут быть смонтированы только root и usr. Остальные необходимые точки монтирования будут смонтированы после initramfs или после переключения в корень. Так как необходимая нам файловая система var пока ещё не смонтирована, требуемый журнал journalctl будет смонтирован из имеющейся файловой системы /run и, как мы знаем, это некая временная файловая система. Это ясно говорит что внутри имеющейся среды initramfs вы не можете выполнять доступ к постоянным журналам journald, которые пребывают в /var/log. Чтобы лучше разобраться с этим, обратитесь к Рисунку 7-39, Рисунку 7-40 и Рисунку 7-41.

 

Рисунок 7-39


Команда journalctl в специальной точке входа pre-pivot

 

Рисунок 7-40


Предоставляемый journalctl в /run журнал

 

Рисунок 7-41


Поведение journalctl в специальнрй точке входа pre-pivot

Вы заметили один момент? Служба 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 и завершает её монтирование. Давайте перепроверим что мы разобрались с этим:

  1. Сваливаемся в особую точку входа pre-pivot.

  2. Удаляем имеющийся /etc/fstab из своего смонтированного /sysroot.

  3. Запускаем systemd-fstab-generator.

  4. Взгляните на Рисунок 7-42.

Так как название нашей корневой файловой системы dracut-cmdline получит из proc/cmdline, systemd-fstab-generator создаст файл sysroot.mount. Но поскольку внутри sysroot отсутствует файл fstab, этот usr будет рассматриваться как отдельный раздел, который не доступен и файл элемента usr.mount будет пропущен, даже когда usr выступает отдельной точкой монтирования.

 

Рисунок 7-42


Поведение systemd-fstab-generator

Что если вы желаете иметь подобные 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-43


Работа systemd-fstab-generator

 

Рисунок 7-44


Наша блок-схема

swap.target

Как вы видите из Рисунка 7-45, мы достигли этапа swap.target своего запуска.

 

Рисунок 7-45


Последовательность запуска к данному моменту

Это выполняется параллельно с local-fs.target. local-fs-.target, создавая необходимые точки монтирования для root и usr, в то время как swap.target создаёт файлы монтирования элементов для устройства подкачки страниц. Когда необходимая корневая файловая система готова, соответственным образом в неё монтируется sysroot. systemd-fstab-generator считает fstab и когда имеется запись устройства подкачки, будет создан надлежащий файл элемента swap.mount. Это означает, что файл swap.mount будет создан только после переключения в окончательную корневую файловую систему пользователя (switch_root в sysroot). На этом этапе не будет создан соответствующий swap.mount .

dracut-initqueue.service

Данная служба создаёт все реальные устройства root, swap и usr. Давайте разберёмся с этим на неком примере.

При помощи специальной точки обработки pre-udev мы обнаружили что подобные sda устройства не доступны. Никакя команда udevadm не будет работать, поскольку её служба udevd сама по себе пока ещё не стартовала. Обратитесь к Рисунку 7-46.

 

Рисунок 7-46


Работа специальной точки входа pre-udev

При помощи специальной точки входа pre-trigger устройство sda не создаётся, однако запускается служба udevd; однако, как вы можете видеть на Рисунке 7-47 и Рисунке 7-48 вы можете применять подобные udevadm инструменты, которые и создадут устройство sda в /dev, однако мы не создаём в нём устройства, подобные lvm или raid. Такие устройства имеют название устройств dm (device mapper, устройство построения соответсвия). Итак, данная служба pre-trigger не будет иметь возможности создания необходимых файлов устройств для своего корня когда он пребывает в lvm, а следовательно подобные /dev/fedora_localhost-live/ устройства не будут созданы.

 

Рисунок 7-47


Специальная точка входа pre-trigger

 

Рисунок 7-48


Устройства sda, созданные в специальной точке входа pre-trigger

Служба 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-49


Параметры командной строки ядра

Как вы видите на Рисунке 7-50, команда lvm_scan записана в одном из имеющихся файлов.

 

Рисунок 7-50


Специальная точка входа initqueue

Итак, здесь у нас имеются два варианта: либо мы можем только выполнить /bin/dracut-initqueue, или, как это отображено на Рисунке 7-51, мы можем выполнить команду lvm_scan либо из специальной точки входа pre-trigger, либо из специальной точки входа initqueue.

 

Рисунок 7-51


Команда lvm_scan в специальной точке входа 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 показывает что видно на нашем экране.

 

Рисунок 7-52


Сообщения консоли

Поскольку у нс теперь лучшее понимание dracut-initqueue, мы можем понять, что это сообщение об ошибке ясно означает что systemd не способен собрать устройство root lvm.

  1. Давайте изолируем эту проблему вначале повторно вызвав свои шаги выполнения. Первоначально название 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
    		
  2. Значение названия root volume group было изменено.

    
    # vgrename fedora_localhost-live root_vg
    		

    Наша volume group Fedora_localhost-live была успешно переименована в oot_vg.

  3. Запись /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-53


Аварийная оболочка

Как показано на Рисунке 7-54, мы можем просканировать доступные в настоящий момент LV и смонтируем root_vg (корневую виртуальную группу) для проверки её содержимого.

 

Рисунок 7-54


Активация LV

Как вы можете видеть, root_vg (переименованная vg) доступна и у нас также имеется и возможность ей активировать. Это с очевидностью означает что установленные метаданные LVM не разрушены и что это устройство LVM не обладает проблемами с целостностью. Как показано на Рисунке 7-55, мы смотнируем root_vg во временном каталоге и повторно проверим её записи fstab из самой текущей аварийной оболочки.

 

Рисунок 7-55


Монтирование корневой файловой системы

vg не тронута, установленные записи fstab верны и мы имеем возможность смонтировать необходимую root_vg. Чего же тогда не хватает?

Пропущенной частью является то, что параметры командной строки были отрегулированы в GRUB. Смотрите Рисунок 7-56.

 

Рисунок 7-56


Параметры командной строки ядра

Чтобы запуститься нам требуется прервать экран- заставку GRUB и изменить параметры командной строки свого ядра по сравнению с отображаемыми на Рисунке 7-57.

 

Рисунок 7-57


Старые параметры командной строки ядра

Смотрите на новые на Рисунке 7-58.

 

Рисунок 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

Теперь настало время поговорить б одной занимательной службе с названием 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-59


Последовательность запуска

Как показано на Рисунке 7-60, plymouth будет активным на протяжении данной последовательности запуска..

 

Рисунок 7-60


plymouth

plymouth является тем инструментом, который отображает вам анимацию во время выполняемого запуска. Например, в Fedora он не показывает сообщения консоли, отображённые на Рисунке 7-61.

 

Рисунок 7-61


Когда plymouth не доступен

plymouth показывает вам анимацию, отражаемую на Рисунке 7-62.

 

Рисунок 7-62


Экран plymouth

Установка plymouth

Если вы желаете устанавливать различные темы plymouth, тогда вот что вам требуется предпринять:

  1. Выгрузите plymouth-theme из gnome-look.org или вы можете воспользоваться следующим:

    
    # dnf install plymouth-theme*
    		
  2. Распакуйте выгруженную тему в следующее местоположение: /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
    		
  3. Вам потребуется пересобрать 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.

 

Рисунок 7-63


Тема hot-dog анимации plymouth

Управление 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

Итак, мы достигли стадии sysinit.target. Рисунок Рисунок 7-64 отображает пройденную нами к данному моменту последовательность запуска.

 

Рисунок 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 .

 

Рисунок 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 завершило свою последовательность запуска.

Какие- то модули способны обладать утечками памяти, однако выявление этого будет сложной задачей. Поэтому, мы для начала убедимся что все относящиеся к памяти параметры настройки ядра были установлены правильно:

  1. Для этого мы выбросим себя вовнутрь initramfs. На Рисунке 7-66 мы передаём rd.break в качестве параметра командной строки ядра.

     

    Рисунок 7-66


    Параметр командной строки ядра

  2. Мы повторно смонтируем sysroot в режиме чтения- записи и проверим установленные параметры sysctl.

    
    switch_root:/# cat /proc/sys/vm/nr_hugepages
                   2400
    		
  3. Наша проблема состоит в неверно зарезервированном числе hugepages. Мы отключим эту настройку в соответствии с Рисунком 7-67.

     

    Рисунок 7-67


    Отключение установки hugepage

После перезапуска наша система получает возможность успешно запускаться. Давайте попробуем разобраться что пошло не так. Данная система обладает 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. Как мы знаем, цели служат для синхронизации или группирования имеющихся элементов. 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.

dracut-pre-mount.service

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.

 

Рисунок 7-68


Параметр командной строки ядра

 

Рисунок 7-69


Специальная точка входа pre-mount