Глава 9. systemd (Часть II)

На данный момент мы достигли своей службы dracut.pre-mount.service, когда наша корневая файловая система пользователя ещё не смонтирована внутри initramfs. Следующая стадия запуска systemd смонтирует эту корневую файловую систему в sysroot.

sysroot.mount

systemd принимает параметр командной строки dracut mount, который сбрасывает нас в аварийную оболочку -mount. Как вы видите на Рисунке 9-1, мы передали параметр командной строки ядра rd.break=mount.

 

Рисунок 9-1


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

Как вы можете видеть на Рисунке 9-2, sysroot была смонтирована с корневой файловой системой пользователя в режиме только для чтения.

 

Рисунок 9-2


Особая точка входа монтирования

Особая точка входа dracut.mount (usr/lib/systemd/system/dracut-mount.service) запустит надлежащий сценарий /bin/dracut-mount из initramfs, который и выполнит необходимую часть монтирования.


#vim usr/lib/systemd/system/dracut-mount.service
		

Как вы можете видеть, этот выполняет сценарий dracut-mount из iniramfs и к тому же экспортирует соответствующую переменную NEWROOT со значением sysroot.


Environment=NEWROOT=/sysroot
ExecStart=-/bin/dracut-mount

[Unit]
Description=dracut mount hook
Documentation=man:dracut-mount.service(8)
After=initrd-root-fs.target initrd-parse-etc.service
After=dracut-initqueue.service dracut-pre-mount.service
ConditionPathExists=/usr/lib/initrd-release
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/mount
ConditionKernelCommandLine=|rd.break=mount
DefaultDependencies=no
Conflicts=shutdown.target emergency.target

[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-mount
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes

KillSignal=SIGHUP
#vim bin/dracut-mount
  1 #!/usr/bin/sh
  2 export DRACUT_SYSTEMD=1
  3 if [ -f /dracut-state.sh ]; then
  4     . /dracut-state.sh 2>/dev/null
  5 fi
  6 type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
  7
  8 source_conf /etc/conf.d
  9
 10 make_trace_mem "hook mount" '1:shortmem' '2+:mem' '3+:slab'
 11
 12 getarg 'rd.break=mount' -d 'rdbreak=mount' && emergency_shell -n mount "Break mount"
 13 # mount scripts actually try to mount the root filesystem, and may
 14 # be sourced any number of times. As soon as one suceeds, no more are sourced.
 15 i=0
 16 while :; do
 17     if ismounted "$NEWROOT"; then
 18         usable_root "$NEWROOT" && break;
 19         umount "$NEWROOT"
 20     fi
 21     for f in $hookdir/mount/*.sh; do
 22         [ -f "$f" ] && . "$f"
 23         if ismounted "$NEWROOT"; then
 24             usable_root "$NEWROOT" && break;
 25             warn "$NEWROOT has no proper rootfs layout, ignoring and removing offending mount hook"
 26             umount "$NEWROOT"
 27             rm -f -- "$f"
 28         fi
 29     done
 30
 31     i=$(($i+1))
 32     [ $i -gt 20 ] && emergency_shell "Can't mount root filesystem"
 33 done
 34
 35 export -p > /dracut-state.sh
 36
 37 exit 0
		

В Главе 8 мы увидели как именно это сбрасывает нас в некую аварийную оболочку и все связанные функции этого. Так как мы остановили свою последовательность запуска после монтирования необходимой корневой файловой системы внутри initramfs, как вы можете обнаружить на Рисунке 9-3, наш systemd-fstab-generator уже был выполнен и уже были созданы файлы элементов -mount.

 

Рисунок 9-3


Поведение нашего systemd-fstab-generator

Помните, что значение названия корневой файловой системы в sysroot.mount было взято из соответствующего файла /proc/cmdline. Сам sysroot.mount чётко указывает что следует монтировать и где это надлежит монтировать.

initrd.target

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

  1. Поиск самой корневой файловой системы пользователя.

  2. Монтирование этой корневой файловой системы пользователя (мы достигли этого этапа запуска).

  3. Поиск прочих необходимых файловых систем и их монтирования (usr, var, nfs, cifs и т.п.).

  4. Переключение на смонтированную корневую файловую систему пользователя.

  5. Запуск необходимых демонов пространства пользователя.

  6. Запуск multi-user.target или graphical.target (что выходит за рамки этой книги).

Как вы можете видеть, на данный момент мы достигли этапа 2, коим выступает монтирование необходимой корневой файловой системы пользователя. Мы знаем, что systemd имеет .targets, а .target это ни что иное, как пакет файлов элементов. Такой .target может быть успешно запущен только когда все эти файлы элементов были успешно запущены.

В установленном мире systemd имеется моножество целей, такие как basic.target, multi-user.target, graphical.target, default.target и sysinit.target, перечисляя лишь часть. Окончательная цель initramfs состоит в достижении initrd.target. После того как успешно запущен initrd.target, тогда в него переключится switch_root systemd. Итак, вначале мы взглянем на initrd.target и где она стоит с точки зрения последовательности запуска. Обращаем ваше внимание на показанную на Рисунке 9-4 блок- схему.

 

Рисунок 9-4


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

Когда вы находитесь вне initramfs (то есть после switch_root), default.target systemd будет либо multi-user.target, либо graphical.target, в то время как внутри initremfs (то есть паеред switch_root) после basic.target default.target systemd будет initrd.target. Итак, после успешного завершения sysinit.target и basic.target, основная задача systemd состоит в достижении initrd.target. Для её достижения systemd применит этап sysroot.target для считывания тех смонтированных файлов элементов, которые созданы systemd-fstab-generator. Сама служба dracut-mount.service смонтирует необходимую корневую файловую систему в /sysroot и затем systemd исполнит соответствующую службу initrd-parse-etc.service. Она выполнит синтаксический разбор файла /sysroot/etc/fstab и создаст необходимые монтируемые файлы элементов для usr или всех прочих точек монтирования, которые обладают установкой параметра x-initrd.mount. Вот как работает initrd-parse-etc.service:


# cat usr/lib/systemd/system/initrd-parse-etc.service | grep -v '#'

[Unit]
Description=Reload Configuration from the Real Root
DefaultDependencies=no
Requires=initrd-root-fs.target
After=initrd-root-fs.target
OnFailure=emergency.target
OnFailureJobMode=replace-irreversibly
ConditionPathExists=/etc/initrd-release
[Service]
Type=oneshot
ExecStartPre=-/usr/bin/systemctl daemon-reload
ExecStart=-/usr/bin/systemctl --no-block start initrd-fs.target
ExecStart=/usr/bin/systemctl --no-block start initrd-cleanup.service
		

Обычно эта служба выполняет systemctl с неким переключателем daemon-reload. Это перезапускает установленную конфигурацию диспетчера systemd. Что перезапустит все генераторы, перезагрузит все файлы элементов и повторно создаст всё дерево зависимостей целиком. В то время как этот демон перезапускается, будут оставаться доступными все сокеты, в которых systemd от имени пользователя выполняет ожидание. Те генераторы, которые повторно исполняет systemd таковы:


# ls usr/lib/systemd/system-generators/ -l
     total 92
     -rwxr-xr-x. 1 root root  3750 Jan 10 19:18 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, который считает записи /sysroot/etc/fstab и создаст необходимые монтируемые файлы элементов для usr и для устройств, обладающих установленным параметром x-initrd.mount. Короче говоря, systemd-fstab-generator исполняется дважды.

Поэтому когда вы сваливаете себя в соответствующую оболочку монтирования (rd.break=mount), вы на самом деле прерываете свою последовательность запуска после соответствующей initrd.target. Эта цель запускает всего лишь такие службы:


# ls usr/lib/systemd/system/initrd.target.wants/

     dracut-cmdline-ask.service  dracut-mount.service      dracut-pre-trigger.service
     dracut-cmdline.service      dracut-pre-mount.service  dracut-pre-udev.service
     dracut-initqueue.service    dracut-pre-pivot.service
		

Для понимания всего этого обращаем ваше внимание на Рисунок 9-5.

 

Рисунок 9-5


Общее исполнение initrd.target

switch_root/pivot_root

Теперь мы достигли окончательного этапа запуска systemd, коим выступает switch_root. Systemd переключает свою корневую файловую систему с initramfs (/) на смонтированную корневую файловую систему пользователя (/sysroot). Systemd достигает этого предпринимая следующие шаги:

  1. Монтируя соответствующую новую корневую файловую систему (/sysroot)

  2. Превращая её в свою корневую файловую систему (/)

  3. Удаляя весь доступ к старой (initramfs) корневой файловой системе

  4. Отключая свою файловую систему initramfs и высвобождая файловую систему ramfs

Имеются три основных момента, которые мы обсудим в данной главе:

  • switch_root: Мы изучим это старым способом init.

  • pivot_root: Мы исследуем это вариантом systemd.

  • chroot: Это мы поясняем в Главе 10.

Переключение к файловой системе Нового корня в системе на основе init

Основанная на init система применяет switch_root для переключения на некую новую корневую файловую систему (sysroot). Основная цель switch_root достаточно хорошо поясняется в её странице руководства, отображаемой здесь:


#man switch_root
NAME
       switch_root - переключиться на другую файловую систему в качестве корня всего монтируемого дерева

SYNOPSIS
       switch_root [-hV]

       switch_root newroot init [arg...]

DESCRIPTION
       switch_root перемещает уже смонтированные /proc, /dev, /sys and /run в newroot и превращает newroot в свою новую корневую файловую систему и запускает процесс init.
       WARNING: switch_root рекурсивно удаляет все файлы и каталоги в своей текущей корневой файловой системе.

OPTIONS
       -h, --help
              Отображает текст подсказки и выполняет выход.

       -V, --version
              Отображает сведения о версии и выполняет выход.

RETURN VALUE
       switch_root возвращает 0 в случае успеха и 1 при неудаче.

NOTES

       switch_root отказывает в работе когда newroot не выступает установленным корнем монтирования. Если вы желаете переключить корень в нек4ий каталог, который не соответствует данному требованию, тогда вы вначале можете применить трюк со связываением- монтироованием для включения любого каталога в некую точку монтирования:

              mount --bind $DIR $DIR
		

Итак, это переключает в некую новую корневую файловую систему (sysroot) и совместно с самим корнем это перемещает все старые виртуальные фаловые системы корневой файловой системы (proc, dev, sys, sys и т.п.) в свой новый корень. Наилучшая функциональная возможность switch_root состоит в том, что после монтирования своей новой корневой файловой системы она запускает сам процесс init в ней самой. Переключение в новую корневую файловую систему имеет место в исходном коде dracut. Самой latest версией dracut на момент написания этих строк была 049. Функция switch_root определяется в файле dracut-049/modules.d/99base/init.sh.


387 unset PS4
388
389 CAPSH=$(command -v capsh)
390 SWITCH_ROOT=$(command -v switch_root)
391 PATH=$OLDPATH
392 export PATH
393
394 if [ -f /etc/capsdrop ]; then
395     . /etc/capsdrop
396     info "Calling $INIT with capabilities $CAPS_INIT_DROP dropped."
397     unset RD_DEBUG
398     exec $CAPSH --drop="$CAPS_INIT_DROP" -- \
399         -c "exec switch_root \"$NEWROOT\" \"$INIT\" $initargs" || \
400     {
401         warn "Command:"
402         warn capsh --drop=$CAPS_INIT_DROP -- -c exec switch_root "$NEWROOT" "$INIT" $initargs
403         warn "failed."
404         emergency_shell
405     }
406 else
407     unset RD_DEBUG
408     exec $SWITCH_ROOT "$NEWROOT" "$INIT" $initargs || {
409         warn "Something went very badly wrong in the initramfs.  Please "
410         warn "file a bug against dracut."
411         emergency_shell
412     }
413 fi
		

В нашем предыдущем коде вы можете обнаружить, что exec switch_root вызывалось в точности так, как это было описано в странице руководства switch_root. Определяемыми значениями переменных для NEWROOT и INIT выступают такие:


NEWROOT = "/sysroot"
INIT = 'init' or 'sbin/init'
		

Просто для вашего сведения, в наши дни этот файл init выступает symlink для systemd.


# ls -l sbin/init
lrwxrwxrwx. 1 root root 22 Dec 21 12:19 sbin/init -> ../lib/systemd/systemd
		

Для успешного switch_root всех виртуальных файловых систем они прежде всего должны быть смонтированы. Это достигается через dracut-049/modules.d/99base/init.sh. Имеются определённые этапы и они таковы:

  1. Смонтировать файловую системуproc.

  2. Смонтировать файловую систему sys.

  3. Смонтировать каталог /dev с devtmpfs.

  4. Вручную создать файлы устройств stdin, stdout, stderr, pts и shm.

  5. Создать точку монтирования /run с tmpfs в ней. (Такая точка монтирования /run не доступна в системах на основе init).


#vim dracut-049/modules.d/99base/init.sh
 11 NEWROOT="/sysroot"
 12 [ -d $NEWROOT ] || mkdir -p -m 0755 $NEWROOT
 13
 14 OLDPATH=$PATH
 15 PATH=/usr/sbin:/usr/bin:/sbin:/bin
 16 export PATH
 17
 18 # mount some important things
 19 [ ! -d /proc/self ] && \
 20     mount -t proc -o nosuid,noexec,nodev proc /proc >/dev/null
 21
 22 if [ "$?" != "0" ]; then
 23     echo "Cannot mount proc on /proc! Compile the kernel with CONFIG_PROC_FS!"
 24     exit 1
 25 fi
 26
 27 [ ! -d /sys/kernel ] && \
 28     mount -t sysfs -o nosuid,noexec,nodev sysfs /sys >/dev/null
 29
 30 if [ "$?" != "0" ]; then
 31     echo "Cannot mount sysfs on /sys! Compile the kernel with CONFIG_SYSFS!"
 32     exit 1
 33 fi
 34
 35 RD_DEBUG=""
 36 . /lib/dracut-lib.sh
 37
 38 setdebug
 39
 40 if ! ismounted /dev; then
 41     mount -t devtmpfs -o mode=0755,noexec,nosuid,strictatime devtmpfs /dev >/dev/null
 42 fi
 43
 44 if ! ismounted /dev; then
 45     echo "Cannot mount devtmpfs on /dev! Compile the kernel with CONFIG_DEVTMPFS!"
 46     exit 1
 47 fi
 48
 49 # prepare the /dev directory
 50 [ ! -h /dev/fd ] && ln -s /proc/self/fd /dev/fd >/dev/null 2>&1
 51 [ ! -h /dev/stdin ] && ln -s /proc/self/fd/0 /dev/stdin >/dev/null 2>&1
 52 [ ! -h /dev/stdout ] && ln -s /proc/self/fd/1 /dev/stdout >/dev/null 2>&1
 53 [ ! -h /dev/stderr ] && ln -s /proc/self/fd/2 /dev/stderr >/dev/null 2>&1
 54
 55 if ! ismounted /dev/pts; then
 56     mkdir -m 0755 /dev/pts
 57     mount -t devpts -o gid=5,mode=620,noexec,nosuid devpts /dev/pts >/dev/null
 58 fi
 59
 60 if ! ismounted /dev/shm; then
 61     mkdir -m 0755 /dev/shm
 62     mount -t tmpfs -o mode=1777,noexec,nosuid,nodev,strictatime tmpfs /dev/shm >/dev/null
 63 fi
 64
 65 if ! ismounted /run; then
 66     mkdir -m 0755 /newrun
 67     if ! str_starts "$(readlink -f /bin/sh)" "/run/"; then
 68         mount -t tmpfs -o mode=0755,noexec,nosuid,nodev,strictatime tmpfs /newrun >/dev/null
 69     else
 70         # the initramfs binaries are located in /run, so don't mount it with noexec
 71         mount -t tmpfs -o mode=0755,nosuid,nodev,strictatime tmpfs /newrun >/dev/null
 72     fi
 73     cp -a /run/* /newrun >/dev/null 2>&1
 74     mount --move /newrun /run
 75     rm -fr -- /newrun
 76 fi
		

Переключение к файловой системе Нового корня в системе на основе systemd

Необходимые шаги почти такие же как и те, что мы обсуждали для систем на основании init. Единственным отличием для systemd является то, что его исполняемый код сделан на C. Поэтому, очевидно, переключение своего корня имеет место в исходном коде C systemd, что отражено тут:


src/shared/switch-root.c:
 	   

Вначале рассмотрим следующее:


new_root = sysroot
old_root = /
		

Это переместит все те виртуальные файловые системы, которыми уже была наполнена корневая файловая система initramfs; затем функция path_equal проверит доступен ли путь new_root.


if (path_equal(new_root, "/"))
      return 0;
 	   

Позднее вызывается syscall pivot_root (init применяет switch_root) и изменяет текущий корень с / (корневой файловой системы самой initramfs) на sysroot (настраиваемая корневая файловая система пользователя).


pivot_root(new_root, resolved_old_root_after) >= 0)
 	   

Прежде чем мы двинемся далее, нам требуется разобраться с тем что из себя представляет pivot_root и что она делает.


# man pivot_root
NAME
       pivot_root - изменяет текущую корневую файловую систему

SYNOPSIS
       pivot_root new_root put_old

DESCRIPTION
       pivot_root перемещает собственную корневую файловую систему текущего процесса в каталог put_old и создаёт новую корневую файловую систему new_root. Поскольку pivot_root(8) просто вызывает pivot_root(2), для дополнительных сведений мы отсылаем к соответствующей странице руководства:
		

Обратите внимание, что в зависимости от реализации pivot_root, root и cwd вызывающей стороны могут измениться, а могут остаться прежними. Ниже приводится последовательность для вызова pivot_root, которая работает в любом случае в предположении что pivot_root и chroot пребывают в текущем PATH:

cd new_root

pivot_root . put_old

exec chroot . command

Заметьте, что chroot обязан быть доступным в своём старом корне, а также и в новом корне, ибо pivot_root может обладать, а может и нет в явном виде изменённым значением корневого каталога своей оболочки.

Обратите внимание, что выполнение chroot изменяет запущенный исполняемый файл, что необходимо когда его старый корневой каталог должен быть размонтирован после всего. Также отметьте, что стандартные input, output и error могут всё ещё указывать на некое устройство своей старой корневой файловой системе, сохраняя их занятыми. Их запросто можно заменить при вызове chroot (см. далее; обратите внимание на отсутствие начальных косых черт, чтобы это работало независимо от того изменил pivot_root корень своей оболочки или нет).

pivot_root изменяет свою корневую файловую систему (корневую файловую систему самого initramfs) текущего процесса (systemd) на новую корневую файловую систему (sysroot), а также он изменяет сам запущенный исполняемый файл (systemd из initramfs) на новый (systemd из корневой файловой системы пользователя).

После pivot_root, выполняется отключение старого корневого устройства initramfs (src/shared/switch-root.c).


# vim src/shared/switch-root.c
 96         /* Вначале мы пробуем pivot_root() с тем, чтобы мы смогли размонтировать свой старый корневой каталог. Во многих случаях (т.е. когда rootfs выступает /),
 97          * это, однако, невозможно и следовательно мы просто повторно монтируем корень */
 98         if (pivot_root(new_root, resolved_old_root_after) >= 0) {
 99
100                 /* Немедленно избавляемся от своего старого корня, когда установлен detach_oldroot.
101                  * Поскольку мы покидаем его, нам надлежит делать это не спеша. */
102                 if (unmount_old_root) {
103                         r = umount_recursive(old_root_after, MNT_DETACH);
104                         if (r < 0)
105                                 log_warning_errno(r, "Failed to unmount old root directory tree, ignoring: %m");
106                 }
107
108         } else if (mount(new_root, "/", NULL, MS_MOVE, NULL) < 0)
109                 return log_error_errno(errno, "Failed to move %s to /: %m", new_root);
110
		

После успешного pivot_root вот наше текущее состояние:

  • sysroot превратился в корень (/).

  • Текущим рабочим каталогом стал корень (/).

  • Будет исполнен chroot с тем, чтобы bash изменил свой корневой каталог со старого корня (initramfs) на новую (пользовательскую) корневую файловую систему. chroot будет обсуждён в нашей следующекй главе.

Наконец, удаляем устройство old_root (rm -rf).


110
111         if (chroot(".") < 0)
112                 return log_error_errno(errno, "Failed to change root: %m");
113
114         if (chdir("/") < 0)
115                 return log_error_errno(errno, "Failed to change directory: %m");
116
117         if (old_root_fd >= 0) {
118                 struct stat rb;
119
120                 if (fstat(old_root_fd, &rb) < 0)
121                         log_warning_errno(errno, "Failed to stat old root directory, leaving: %m");
122                 else
123                         (void) rm_rf_children(TAKE_FD(old_root_fd), 0, &rb); /* takes possession of the dir fd, even on failure */
124         }
		

Для лучшего понимания я настоятельно рекомендую прочесть весь исходный код src/shared/switch-root.c, отображаемый здесь:


1 /* SPDX-License-Identifier: LGPL-2.1+ */
  2
  3 #include <errno.h>
  4 #include <fcntl.h>
  5 #include <limits.h>
  6 #include <stdbool.h>
  7 #include <sys/mount.h>
  8 #include <sys/stat.h>
  9 #include <unistd.h>
 10
 11 #include "base-filesystem.h"
 12 #include "fd-util.h"
 13 #include "fs-util.h"
 14 #include "log.h"
 15 #include "missing_syscall.h"
 16 #include "mkdir.h"
 17 #include "mount-util.h"
 18 #include "mountpoint-util.h"
 19 #include "path-util.h"
 20 #include "rm-rf.h"
 21 #include "stdio-util.h"
 22 #include "string-util.h"
 23 #include "strv.h"
 24 #include "switch-root.h"
 25 #include "user-util.h"
 26 #include "util.h"
 27
 28 int switch_root(const char *new_root,
 29                 const char *old_root_after, /* path below the new root, where to place the old root after the transition */
 30                 bool unmount_old_root,
 31                 unsigned long mount_flags) {  /* MS_MOVE or MS_BIND */
 32
 33         _cleanup_free_ char *resolved_old_root_after = NULL;
 34         _cleanup_close_ int old_root_fd = -1;
 35         bool old_root_remove;
 36         const char *i;
 37         int r;
 38
 39         assert(new_root);
 40         assert(old_root_after);
 41
 42         if (path_equal(new_root, "/"))
 43                 return 0;
 44
 45         /* Check if we shall remove the contents of the old root */
 46         old_root_remove = in_initrd();
 47         if (old_root_remove) {
 48                 old_root_fd = open("/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
 49                 if (old_root_fd < 0)
 50                         return log_error_errno(errno, "Failed to open root directory: %m");
 51         }
 52
 53         /* Determine where we shall place the old root after the transition */
 54         r = chase_symlinks(old_root_after, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved_old_root_after, NULL);
 55         if (r < 0)
 56                 return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, old_root_after);
 57         if (r == 0) /* Doesn't exist yet. Let's create it */
 58                 (void) mkdir_p_label(resolved_old_root_after, 0755);
 59
 60         /* Work-around for kernel design: the kernel refuses MS_MOVE if any file systems are mounted MS_SHARED. Hence
 61          * remount them MS_PRIVATE here as a work-around.
 62          *
 63          * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
 64         if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
 65                 return log_error_errno(errno, "Failed to set \"/\" mount propagation to private: %m");
 66
 67         FOREACH_STRING(i, "/sys", "/dev", "/run", "/proc") {
 68                 _cleanup_free_ char *chased = NULL;
 69
 70                 r = chase_symlinks(i, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &chased, NULL);
 71                 if (r < 0)
 72                         return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, i);
 73                 if (r > 0) {
 74                         /* Already exists. Let's see if it is a mount point already. */
 75                         r = path_is_mount_point(chased, NULL, 0);
 76                         if (r < 0)
 77                                 return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", chased);
 78                         if (r > 0) /* If it is already mounted, then do nothing */
 79                                 continue;
 80                 } else
 81                          /* Doesn't exist yet? */
 82                         (void) mkdir_p_label(chased, 0755);
 83
 84                 if (mount(i, chased, NULL, mount_flags, NULL) < 0)
 85                         return log_error_errno(errno, "Failed to mount %s to %s: %m", i, chased);
 86         }
 87
 88         /* Do not fail if base_filesystem_create() fails. Not all switch roots are like base_filesystem_create() wants
 89          * them to look like. They might even boot, if they are RO and don't have the FS layout. Just ignore the error
 90          * and switch_root() nevertheless. */
 91         (void) base_filesystem_create(new_root, UID_INVALID, GID_INVALID);
 92
 93         if (chdir(new_root) < 0)
 94                 return log_error_errno(errno, "Failed to change directory to %s: %m", new_root);
 95
 96         /* We first try a pivot_root() so that we can umount the old root dir. In many cases (i.e. where rootfs is /),
 97          * that's not possible however, and hence we simply overmount root */
 98         if (pivot_root(new_root, resolved_old_root_after) >= 0) {
 99
100                 /* Immediately get rid of the old root, if detach_oldroot is set.
101                  * Since we are running off it we need to do this lazily. */
102                 if (unmount_old_root) {
103                         r = umount_recursive(old_root_after, MNT_DETACH);
104                         if (r < 0)
105                                 log_warning_errno(r, "Failed to unmount old root directory tree, ignoring: %m");
106                 }
107
108         } else if (mount(new_root, "/", NULL, MS_MOVE, NULL) < 0)
109                 return log_error_errno(errno, "Failed to move %s to /: %m", new_root);
110
111         if (chroot(".") < 0)
112                 return log_error_errno(errno, "Failed to change root: %m");
113
114         if (chdir("/") < 0)
115                 return log_error_errno(errno, "Failed to change directory: %m");
116
117         if (old_root_fd >= 0) {
118                 struct stat rb;
119
120                 if (fstat(old_root_fd, &rb) < 0)
121                         log_warning_errno(errno, "Failed to stat old root directory, leaving: %m");
122                 else
123                         (void) rm_rf_children(TAKE_FD(old_root_fd), 0, &rb); /* takes possession of the dir fd, even on failure */
124         }
125
126         return 0;
127 }
 	   

Здесь мы успешно переключились на корневую файловую систему пользователя и покинули свою среду initramfs. Теперь начинает выполнение с PID 1 systemd из корневой файловой системы пользователя и он заботится обо всей остающейся процедуре запуска, которая такова:

  • systemd запускает необходимые службы пространства пользователя, такие как httpd, mysql, postfix, network services и т.п.

  • В конце концов основная цель состоит в достижении default.target. Как мы обсуждали ранее, перед switch_root, той целью, которая вызывается default.target systemd будет initrd.target, а после switch_root ею будет либо multi-user.target, либо graphical.target.

Но что происходит с тем имеющимся процессом systemd, который стартовал из initramfs (своей корневой файловой системы)? Убивается ли он после switch_root или pivot_root? Запускается ли из созданной корневой файловой системы пользователя новый systemd?

Ответ прост.

  1. systemd initramfs создаёт некий конвейер (pipe).

  2. systemd ветвится (forks).

  3. Первоначальный PID 1 выполняет chroot в /systemd и исполняет /systemd/usr/lib/systemd/systemd.

  4. Разветвлённый systemd последовательно упорядочивает своё состояние в конвейер в PID 1 и выполняет выход.

  5. PID 1 выполняет преобразование упорядоченных данных из конвейера и продолжает свою работы с обновлённой конфигурацией в / (который ранее был /sysroot).

Надеюсь, вы удовлетворены знакомством systemd внутри initramfs. Как мы уже упоминали ранее, вся остающаяся последовательность запуска systemd, которая будет происходить вне initramfs, будет более или менее походить на то, что мы обсуждали до сих пор.

Как запускается GUI выходит за рамки данной книги. В своей следующей главе мы обсудим образы live ISO, а также аварийный режим.