Linux 启动实用指南(四)

原文:Hands-on Booting

协议:CC BY-NC-SA 4.0

八、调试 Shell

到目前为止,我们知道 initramfs 内置了 bash,并且我们不时地通过rd.break钩子来使用它。本章的目的是理解 systemd 如何在 initramfs 中为我们提供一个 shell。必须遵循的步骤是什么,如何更有效地使用它?但是在此之前,让我们回顾一下到目前为止我们所学到的关于 initramfs 的调试和紧急 shells 的知识。

贝壳

rd.break
           drop to a shell at the end

rd.break将我们放入 initramfs 中,我们可以通过它探索 initramfs 环境。这个 initramfs 环境也被称为紧急模式。在正常情况下,当 initramfs 无法挂载用户的根文件系统时,我们会在紧急模式下掉线。请记住,在将用户的根文件系统挂载到/sysroot下之后,但在对其执行switch_root之前,不带任何参数地传递rd.break会将我们放到 initramfs。你总能在/run/initramfs/rdsosreport.txt文件中找到详细的日志。图 8-1 显示了来自rdsosreport.txt的日志。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-1

rdsosreport.txt 运行时日志

在日志消息中,您可以清楚地看到它就在执行pivot_root之前被丢弃。pivot_rootswitch_root将在第九章讨论,而chroot将在第十章讨论。一旦退出紧急 shell,systemd 将继续暂停的引导序列,并最终提供登录屏幕。

然后我们讨论了如何使用紧急 shells 来修复一些“无法启动”的问题。例如,initramfs 与用户的根文件系统一样好。因此,它确实有lvmraid和与文件系统相关的二进制文件,我们可以用它们来查找、组装、诊断和修复丢失的用户根文件系统。然后我们讨论了如何将它安装在/sysroot下,并探索它的内容,例如修复grub.cfg的错误条目。

同样,rd.break也为我们提供了不同的选项来打破不同阶段的引导顺序。

  • cmdline:这个钩子获取内核命令行参数。

  • pre-udev:这打破了udev处理程序之前的引导顺序。

  • pre-trigger:可以用udevadm控件设置udev环境变量,也可以用udevadm控件设置--property=KEY=value类参数或控制udev的进一步执行。

  • pre-mount:这在/sysroot挂载用户的根文件系统之前中断了引导序列。

  • mount:这打破了在/sysroot挂载根文件系统后的引导顺序。

  • pre-pivot:这在切换到实际的根文件系统之前中断了引导序列。

现在让我们看看 systemd 是如何在这些不同的阶段为我们提供 shells 的。

systemd 如何让我们进入紧急状态?

让我们考虑一个pre-mount钩子的例子。来自 initramfs 的 systemd 从dracut-cmdline.service收集rd.break=pre-mount命令行参数,并从 initramfs 位置/usr/lib/systemd/system.运行 systemd 服务dracut-pre-mount.service,该服务将在运行initrd-root-fs.targetsysroot.mountsystemd-fsck-root.service之前运行。

# cat usr/lib/systemd/system/dracut-pre-mount.service | grep -v #'

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

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

KillSignal=SIGHUP

如您所见,它只是从 initramfs 执行了/bin/dracut-pre-mount脚本。

# vim bin/dracut-pre-mount
  1 #!/usr/bin/sh
  2
  3 export DRACUT_SYSTEMD=1
  4 if [ -f /dracut-state.sh ]; then
  5     . /dracut-state.sh 2>/dev/null
  6 fi
  7 type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
  8
  9 source_conf /etc/conf.d
 10
 11 make_trace_mem "hook pre-mount" '1:shortmem' '2+:mem' '3+:slab' '4+:komem'
 12 # pre pivot scripts are sourced just before we doing cleanup and switch over
 13 # to the new root.
 14 getarg 'rd.break=pre-mount' 'rdbreak=pre-mount' && emergency_shell -n pre-mount "Break pre-mount"
 15 source_hook pre-mount
 16
 17 export -p > /dracut-state.sh
 18
 19 exit 0

/bin/dracut-pre-mount脚本中,最重要的一行如下:

getarg rd.break=pre-mount' rdbreak=pre-mount
     && emergency_shell -n pre-mount "Break pre-mount"

我们已经讨论过了getarg函数,它用于检查什么参数被传递给了rd.break=。如果已经通过了rd.break=pre-mount,那么只调用emergency-shell()函数。该函数在/usr/lib/dracut-lib.sh中定义,并将pre-mount作为字符串参数传递给它。-n代表以下内容:

  • [ -n STRING ] or [ STRING ]:如果STRING的长度不为零,则为真

emergency_shell函数接受_rdshell_name变量的值作为pre-mount.

if [ "$1" = "-n" ]; then
      _rdshell_name=$2

这里,-n被认为是第一个自变量($1),而pre-mount是第二个自变量($2)。所以,_rdshell_name的值变成了pre-mount

#vim /usr/lib/dracut-lib.sh
1123 emergency_shell()
1124 {
1125     local _ctty
1126     set +e
1127     local _rdshell_name="dracut" action="Boot" hook="emergency"
1128     local _emergency_action
1129
1130     if [ "$1" = "-n" ]; then
1131         _rdshell_name=$2
1132         shift 2
1133     elif [ "$1" = "--shutdown" ]; then
1134         _rdshell_name=$2; action="Shutdown"; hook="shutdown-emergency"
1135         if type plymouth >/dev/null 2>&1; then
1136             plymouth --hide-splash
1137         elif [ -x /oldroot/bin/plymouth ]; then
1138             /oldroot/bin/plymouth --hide-splash
1139         fi
1140         shift 2
1141     fi
1142
1143     echo ; echo
1144     warn "$*"
1145     echo
1146
1147     _emergency_action=$(getarg rd.emergency)
1148     [ -z "$_emergency_action" ] \
1149         && [ -e /run/initramfs/.die ] \
1150         && _emergency_action=halt
1151
1152     if getargbool 1 rd.shell -d -y rdshell || getarg rd.break -d rdbreak; then
1153         _emergency_shell $_rdshell_name
1154     else
1155         source_hook "$hook"
1156         warn "$action has failed. To debug this issue add \"rd.shell rd.debug\" to the kernel command line."
1157         [ -z "$_emergency_action" ] && _emergency_action=halt
1158     fi
1159
1160     case "$_emergency_action" in
1161         reboot)
1162             reboot || exit 1;;
1163         poweroff)
1164             poweroff || exit 1;;
1165         halt)
1166             halt || exit 1;;
1167     esac
1168 }

然后,在最后,它从同一个文件中调用另一个_emergency_shell函数(注意函数名前的下划线)。如您所见,_rdshell_name_emergency_shell函数的参数。

_emergency_shell $_rdshell_name

_emergency_shell()函数内部,我们可以看到_name得到参数,也就是pre-mount

local _name="$1"

#vim usr/lib/dracut-lib.sh
1081 _emergency_shell()
1082 {
1083     local _name="$1"
1084     if [ -n "$DRACUT_SYSTEMD" ]; then
1085         > /.console_lock
1086         echo "PS1=\"$_name:\\\${PWD}# \"" >/etc/profile
1087         systemctl start dracut-emergency.service
1088         rm -f -- /etc/profile
1089         rm -f -- /.console_lock
1090     else
1091         debug_off
1092         source_hook "$hook"
1093         echo
1094         /sbin/rdsosreport
1095         echo 'You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot'
1096         echo 'after mounting them and attach it to a bug report.'
1097         if ! RD_DEBUG= getargbool 0 rd.debug -d -y rdinitdebug -d -y rdnetdebug; then
1098             echo
1099             echo 'To get more debug information in the report,'
1100             echo 'reboot with "rd.debug" added to the kernel command line.'
1101         fi
1102         echo
1103         echo 'Dropping to debug shell.'
1104         echo
1105         export PS1="$_name:\${PWD}# "
1106         [ -e /.profile ] || >/.profile
1107
1108         _ctty="$(RD_DEBUG= getarg rd.ctty=)" && _ctty="/dev/${_ctty##*/}"
1109         if [ -z "$_ctty" ]; then
1110             _ctty=console
1111             while [ -f /sys/class/tty/$_ctty/active ]; do
1112                 _ctty=$(cat /sys/class/tty/$_ctty/active)
1113                 _ctty=${_ctty##* } # last one in the list
1114             done
1115             _ctty=/dev/$_ctty
1116         fi
1117         [ -c "$_ctty" ] || _ctty=/dev/tty1
1118         case "$(/usr/bin/setsid --help 2>&1)" in *--ctty*) CTTY="--ctty";; esac
1119         setsid $CTTY /bin/sh -i -l 0<>$_ctty 1<>$_ctty 2<>$_ctty
1120     fi

相同的pre-mount字符串已被传递给PS1。让我们先看看PS1到底是什么。

PS1称为一个变量。当用户成功登录时,bash 会显示出来。这里有一个例子:

[root@fedora home]#
  |  |   |    |
[username]@[host][CWD][# since it is a root user]

bash 接受的理想条目是PS1='\u:\w\$'

  • 这是用户名。

  • 这是工作目录。

  • **KaTeX parse error: Expected 'EOF', got '#' at position 18: … =如果 UID 为 0,则`#̲`;否则`'`。

所以,在我们的例子中,当我们得到一个紧急 shell 时,PS1将被 shell 打印如下:

'pre-mount#'

接下来在源代码中,您可以看到PS1变量的新值也被添加到了/etc/profile.中,原因是 bash 每次在将 shell 呈现给用户之前都会读取这个文件。最后,我们简单地启动了dracut-emergency服务。

systemctl start dracut-emergency.service

以下是 initramfs 的usr/lib/systemd/system/中的dracut-emergency.service文件:

# cat usr/lib/systemd/system/dracut-emergency.service | grep -v #'

[Unit]
Description=Dracut Emergency Shell
DefaultDependencies=no
After=systemd-vconsole-setup.service
Wants=systemd-vconsole-setup.service
Conflicts=shutdown.target emergency.target

[Service]
Environment=HOME=/
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
WorkingDirectory=/
ExecStart=-/bin/dracut-emergency
ExecStopPost=-/bin/rm -f -- /.console_lock
Type=oneshot
StandardInput=tty-force
StandardOutput=inherit
StandardError=inherit
KillMode=process
IgnoreSIGPIPE=no
TasksMax=infinity

KillSignal=SIGHUP

服务只是简单地执行/bin/dracut-emergency。这个脚本首先停止plymouth服务。

type plymouth >/dev/null 2>&1 && plymouth quit

这会将hook变量的值存储为emergency,并使用emergency参数调用source_hook函数。

export _rdshell_name="dracut" action="Boot" hook="emergency"
source_hook "$hook"

# vim bin/dracut-emergency
     1 #!/usr/bin/sh
  2
  3 export DRACUT_SYSTEMD=1
  4 if [ -f /dracut-state.sh ]; then
  5     . /dracut-state.sh 2>/dev/null
  6 fi
  7 type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
  8
  9 source_conf /etc/conf.d
 10
 11 type plymouth >/dev/null 2>&1 && plymouth quit
 12
 13 export _rdshell_name="dracut" action="Boot" hook="emergency"
 14 _emergency_action=$(getarg rd.emergency)
 15
 16 if getargbool 1 rd.shell -d -y rdshell || getarg rd.break -d rdbreak; then
 17     FSTXT="/run/dracut/fsck/fsck_help_$fstype.txt"
 18     source_hook "$hook"
 19     echo
 20     rdsosreport
 21     echo
 22     echo
 23     echo Entering emergency mode. Exit the shell to continue.'
 24     echo Type "journalctl" to view system logs.'
 25     echo You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot'
 26     echo after mounting them and attach it to a bug report.'
 27     echo
 28     echo
 29     [ -f "$FSTXT" ] && cat "$FSTXT"
 30     [ -f /etc/profile ] && . /etc/profile
 31     [ -z "$PS1" ] && export PS1="$_name:\${PWD}# "
 32     exec sh -i -l
 33 else
 34     export hook="shutdown-emergency"
 35     warn "$action has failed. To debug this issue add \"rd.shell rd.debug\" to the kernel command line."
 36     source_hook "$hook"
 37     [ -z "$_emergency_action" ] && _emergency_action=halt
 38 fi
 39
 40 /bin/rm -f -- /.console_lock
 41
 42 case "$_emergency_action" in
 43     reboot)
 44         reboot || exit 1;;
 45     poweroff)
 46         poweroff || exit 1;;
 47     halt)
 48         halt || exit 1;;
 49 esac
 50
 51 exit 0

usr/lib/dracut-lib.sh中再次定义了source_hook功能。

source_hook() {
    local _dir
    _dir=$1; shift
    source_all "/lib/dracut/hooks/$_dir" "$@"
}

_dir变量已经捕获了钩子名称,即emergency。所有的钩子都只是一堆脚本,从 initramfs 的/lib/dracut/hooks/目录中存储和执行。

# tree usr/lib/dracut/hooks/
usr/lib/dracut/hooks/
├── cleanup
├── cmdline
│   ├── 30-parse-lvm.sh
│   ├── 91-dhcp-root.sh
│   └── 99-nm-config.sh
├── emergency
│   └── 50-plymouth-emergency.sh
├── initqueue
│   ├── finished
│   ├── online
│   ├── settled
│   │   └── 99-nm-run.sh
│   └── timeout
│       └── 99-rootfallback.sh
├── mount
├── netroot
├── pre-mount
├── pre-pivot
│   └── 85-write-ifcfg.sh
├── pre-shutdown
├── pre-trigger
├── pre-udev
│   └── 50-ifname-genrules.sh
├── shutdown
│   └── 25-dm-shutdown.sh
└── shutdown-emergency

对于紧急钩子,它正在执行usr/lib/dracut/hooks/emergency/50-plymouth-emergency.sh,这正在停止plymouth服务。

#!/usr/bin/sh
plymouth --hide-splash 2>/dev/null || :

一旦emergency挂钩被执行并且plymouth被停止,它将返回到bin/dracut-emergency并打印以下横幅:

echo Entering emergency mode. Exit the shell to continue.'
echo Type "journalctl" to view system logs.'
echo You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot'
echo after mounting them and attach it to a bug report.'

因此,rd.break=hook_name用户通过了什么并不重要。systemd 将执行emergency钩子,一旦横幅被打印出来,它将获取我们已经添加了PS1=_rdshell_name / PS1=hook_name/etc/profile目录,然后我们就可以简单地运行 bash shell 了。

exec sh -i –l

当 shell 开始运行时,它会读取/etc/profile,并找到PS1=hook_name变量。在这里,hook_name就是pre-mount。这就是为什么pre-mount作为 bash 的提示名被印了出来。请参考图 8-2 所示的流程图,以便更好地理解这一点。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-2

流程图

如果用户向rd.break传递任何其他参数,例如initqueue,那么它将被馈入PS1_rdshell_name和钩子变量。稍后,bash 将通过紧急服务被调用。Bash 将从/etc/profile文件中读取PS1值,并在提示中显示initqueue名称。

结论是,相同的 bash shell 会以不同的提示名称(cmdlinepre-mountswitch_rootpre-udevemergency等)提供给用户。)但是在 initramfs 的不同引导阶段。

cmdline:/# pre-udev:/#
pre-trigger:/# initqueue:/#
pre-mount:/# pre-pivot:/#
switch_root:/#

与此类似,rescue.target将由 systemd 执行。

救援服务和紧急服务

救援服务在 systemd 世界中也被称为单用户模式。因此,如果用户请求以单用户模式引导,那么 systemd 实际上会在rescue.service阶段将用户放在紧急 shell 中。图 8-3 显示了到目前为止的引导顺序。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-3

引导序列的流程图

你可以通过rescue.target或者通过runlevel1.target或者emergency.servicesystemd.unit以单用户模式引导。如图 8-4 所示,这次我们将使用 Ubuntu 来探索引导阶段。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-4

内核命令行参数

这会让我们陷入紧急状态。单用户模式、救援服务和紧急服务都启动dracut-emergency二进制。这是我们在 dracut 的紧急挂钩中发布的相同二进制文件。

# cat usr/lib/systemd/system/emergency.service | grep -v ' #'

[Unit]
Description=Emergency Shell
DefaultDependencies=no
After=systemd-vconsole-setup.service
Wants=systemd-vconsole-setup.service
Conflicts=shutdown.target
Before=shutdown.target

[Service]
Environment=HOME=/
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
WorkingDirectory=/
ExecStart=/bin/dracut-emergency
ExecStopPost=-/usr/bin/systemctl --fail --no-block default
Type=idle
StandardInput=tty-force
StandardOutput=inherit
StandardError=inherit
KillMode=process
IgnoreSIGPIPE=no
TasksMax=infinity

KillSignal=SIGHUP

# cat usr/lib/systemd/system/rescue.service | grep -v ' #'

[Unit]
Description=Emergency Shell
DefaultDependencies=no
After=systemd-vconsole-setup.service
Wants=systemd-vconsole-setup.service
Conflicts=shutdown.target
Before=shutdown.target

[Service]
Environment=HOME=/
Environment=DRACUT_SYSTEMD=1

Environment=NEWROOT=/sysroot
WorkingDirectory=/
ExecStart=/bin/dracut-emergency
ExecStopPost=-/usr/bin/systemctl --fail --no-block default
Type=idle
StandardInput=tty-force
StandardOutput=inherit
StandardError=inherit
KillMode=process
IgnoreSIGPIPE=no
TasksMax=infinity

KillSignal=SIGHUP

众所周知,dracut-emergency脚本执行一个 bash shell。

# vim bin/dracut-emergency
  1 #!/usr/bin/sh
  2
  3 export DRACUT_SYSTEMD=1
  4 if [ -f /dracut-state.sh ]; then
  5     . /dracut-state.sh 2>/dev/null
  6 fi
  7 type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
  8
  9 source_conf /etc/conf.d
 10
 11 type plymouth >/dev/null 2>&1 && plymouth quit
 12
 13 export _rdshell_name="dracut" action="Boot" hook="emergency"
 14 _emergency_action=$(getarg rd.emergency)
 15
 16 if getargbool 1 rd.shell -d -y rdshell || getarg rd.break -d rdbreak; then
 17     FSTXT="/run/dracut/fsck/fsck_help_$fstype.txt"
 18     source_hook "$hook"
 19     echo
 20     rdsosreport
 21     echo
 22     echo
 23     echo 'Entering emergency mode. Exit the shell to continue.'
 24     echo 'Type "journalctl" to view system logs.'
 25     echo 'You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot'
 26     echo 'after mounting them and attach it to a bug report.'
 27     echo
 28     echo
 29     [ -f "$FSTXT" ] && cat "$FSTXT"
 30     [ -f /etc/profile ] && . /etc/profile
 31     [ -z "$PS1" ] && export PS1="$_name:\${PWD}# "
 32     exec sh -i -l
 33 else
 34     export hook="shutdown-emergency"

 35     warn "$action has failed. To debug this issue add \"rd.shell rd.debug\" to the kernel command line."
 36     source_hook "$hook"
 37     [ -z "$_emergency_action" ] && _emergency_action=halt
 38 fi
 39
 40 /bin/rm -f -- /.console_lock
 41
 42 case "$_emergency_action" in
 43     reboot)
 44         reboot || exit 1;;
 45     poweroff)
 46         poweroff || exit 1;;
 47     halt)
 48         halt || exit 1;;
 49 esac
 50
 51 exit 0

如图 8-5 所示,sysroot还没有挂载,因为我们还没有到达启动的挂载阶段。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-5

应急 Shell

我希望您现在理解了 systemd 如何在不同的引导阶段向用户呈现紧急 shell。在下一章中,我们将继续暂停的 systemd 的引导序列。

九、系统:第二部分

到目前为止,我们已经到达了服务dracut.pre-mount.service,其中用户的根文件系统还没有挂载到 initramfs 中。systemd 的下一个引导阶段将在sysroot上挂载根文件系统。

sysroot.mount

systemd 接受mount dracut 命令行参数,这将把我们放到一个mount紧急 shell 中。如图 9-1 所示,我们已经传递了rd.break=mount内核命令行参数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-1

内核命令行参数

正如您在图 9-2 中看到的,sysroot已经以只读模式挂载到用户的根文件系统中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-2

该安装钩

dracut.mount钩子(usr/lib/systemd/system/dracut-mount.service)将从 initramfs 运行/bin/dracut-mount脚本,它将完成挂载部分。

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

如您所见,这是从 initramfs 执行dracut-mount脚本,并导出带有sysroot值的NEWROOT变量。

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

我们在第八章中看到了它是如何让我们陷入紧急状态的,以及它的相关功能。由于我们在 initramfs 中挂载用户的根文件系统后停止了引导序列,正如你在图 9-3 中看到的,已经执行了systemd-fstab-generator,并且已经创建了-mount单元文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-3

systemd-fstab-生成器行为

请记住,在sysroot.mount中添加的用户根文件系统名是从/proc/cmdline文件中提取的。sysroot.mount明确提到必须安装什么以及安装在哪里。

initrd.target

正如我们多次说过的,引导序列的最终目的是向用户提供用户的根文件系统,在这样做的同时,systemd 实现的主要阶段如下:

  1. 找到用户的根文件系统。

  2. 挂载用户的根文件系统(我们已经到了引导阶段)。

  3. 找到其他必要的文件系统并挂载它们(usrvarnfscifs等)。).

  4. 切换到挂载的用户的根文件系统。

  5. 启动用户空间守护进程。

  6. 启动multi-user.targetgraphical.target(这超出了本书的范围)。

如您所见,到目前为止,我们已经到了第 2 步,在 initramfs 中挂载用户的根文件系统。我们都知道 systemd 有.targetstarget不过是一堆单元文件。只有当.target的所有单元文件都成功启动后,它才能成功启动。

systemd 世界里有很多目标,比如basic.targetmulti-user.targetgraphical.targetdefault.targetsysinit.target等等。initramfs的最终目的是实现initrd.target。一旦initrd.target成功启动,那么 systemd 就会switch_root进入其中。所以,首先,让我们看看initrd.target以及它在引导序列中的位置。请参考图 9-4 所示的流程图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-4

引导序列

当你在 initramfs 之外(也就是说在switch_root之后),systemd 的default.target会是multi-user.target或者graphical.target,而在 initramfs 之内(也就是说在switch_root之前)basic.target之后,systemd 的default.target会是initrd.target。所以,在成功完成sysinit.targetbasic.target之后,systemd 的主要任务就是实现initrd.target。为了到达那里,systemd 将使用sysroot.mount阶段来读取由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

基本上,服务是通过一个daemon-reload开关来执行systemctl的。这将重新加载 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已经执行了两次。

因此,当您进入挂载 shell ( 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 的整体执行

开关根/枢轴根

现在我们已经到了 systemd 引导的最后阶段,也就是switch_root。systemd 将根文件系统从 initramfs ( /)切换到用户的根文件系统(/sysroot)。systemd 通过采取以下步骤来实现这一点:

  1. 挂载新的根文件系统(/sysroot)

  2. 将它转换成根文件系统(/)

  3. 删除对旧的(initramfs)根文件系统的所有访问

  4. 卸载 initramfs 文件系统并取消分配 ramfs 文件系统

本章将讨论三个要点。

  • switch_root:我们会用老的init方式来解释。

  • pivot_root:我们将以systemd的方式来解释这一点。

  • 我们将在第十章中解释这一点。

在基于 init 的系统上切换到新的根文件系统

基于init的系统使用switch_root切换到新的根文件系统(sysroot)。switch_root的用途在它的手册页上有很好的解释,如下所示:

#man switch_root
NAME
       switch_root - switch to another filesystem as the root of the mount tree

SYNOPSIS
       switch_root [-hV]

       switch_root newroot init [arg...]

DESCRIPTION
       switch_root moves already mounted /proc, /dev, /sys and /run to newroot and makes newroot the new root filesystem and starts init process.

       WARNING: switch_root removes recursively all files and directories on the current root filesystem.

OPTIONS
       -h, --help
              Display help text and exit.

       -V, --version
              Display version information and exit.

RETURN VALUE
       switch_root returns 0 on success and 1 on failure.

NOTES
       switch_root will fail to function

 if newroot is not the root of a mount. If you want to switch root into a directory that does not meet this requirement then you can first use a bind-mounting trick to turn any directory into a mount point:

              mount --bind $DIR $DIR

因此,它切换到一个新的根文件系统(sysroot),并与根文件系统一起移动旧的根文件系统的虚拟文件系统(procdevsys等)。)到新根。switch_root最好的特性是在挂载新的根文件系统后,它自己启动init进程。切换到新的根文件系统发生在 dracut 的源代码中。在写这本书的时候,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的手册页上描述的那样。NEWROOTINIT的定义变量值如下:

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

仅供参考,这些天的init文件是一个symlinksystemd

# 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. devtmpfs挂载/dev目录。

  4. 手动创建stdinstdoutstderrptsshm设备文件。

  5. 制作包含 tmpfs 的/run挂载点。(/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 代码做的二进制。因此,很明显,切换根将发生在 systemd 的 C 源代码中,如下所示:

src/shared/switch-root.c:

首先,考虑以下情况:

new_root = sysroot
old_root = /

这将移动已经在 initramfs 的根文件系统中填充的虚拟文件系统;然后,path_equal函数检查new_root路径是否可用。

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

稍后,它调用一个pivot_root系统调用(init使用switch_root)并将根从/(initramfs 根文件系统)更改为sysroot(用户的根文件系统)。

pivot_root(new_root, resolved_old_root_after) >= 0)

在我们进一步讨论之前,我们需要了解pivot_root是什么,它做什么。

注意,根据 pivot_root 的实现,调用者的 root 和 cwd 可能会也可能不会改变。下面是调用 pivot_root 的顺序,无论哪种情况都适用,假设 pivot_root 和 chroot 都在当前路径:

CD new _ root

pivot _ root。

exec ch root。命令

注意,chroot 必须在旧根下和新根下可用,因为 pivot_root 可能隐式更改了 shell 的根目录,也可能没有。

注意,exec chroot 改变正在运行的可执行文件,如果以后要卸载旧的根目录,这是必须的。还要注意,标准输入、输出和错误可能仍然指向旧的根文件系统上的设备,使其保持忙碌。当调用 chroot 时,可以很容易地更改它们(见下文;请注意,无论 pivot_root 是否更改了 shell 的根,都没有使用前导斜杠)。

# man pivot_root
NAME
       pivot_root - change the root filesystem

SYNOPSIS
       pivot_root new_root put_old

DESCRIPTION
       pivot_root moves the root file system of the current process to the directory put_old and makes new_root the new root file system. Since pivot_root(8) simply calls pivot_root(2), we refer to the man page of the latter for further details:

pivot_root将当前进程(systemd)的根文件系统(initramfs 根文件系统)更改为新的根文件系统(sysroot),同时将正在运行的可执行文件(systemd from initramfs)更改为新的可执行文件(systemd from user ’ s root file system)。

pivot_root之后,它分离 initramfs ( src/shared/switch-root.c)的旧根设备。

# vim src/shared/switch-root.c

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

在成功的pivot_root之后,这是当前状态:

  • sysroot已经变成了根(/)。

  • 当前工作目录变成了根目录(/)。

  • 这样 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 将启动httpdmysqlpostfixnetwork services等用户空间服务。

  • 最终,目标将是达到default.target。我们之前讨论过,在switch_root之前,systemd 的目标default.target会是initrd.target,在switch_root之后,不是multi-user.target就是graphical.target

但是从 initramfs(根文件系统)开始的现有的systemd进程会发生什么呢?是在switch_root还是pivot_root之后被干掉?新的systemd进程是从用户的根文件系统开始的吗?

答案很简单。

  1. initramfs 的 systemd 创建一个管道。

  2. forks 系统。

  3. 原 PID 1 chroot变为/systemd并执行/sysroot/usr/lib/systemd/systemd

  4. 分叉的 systemd 通过管道将其状态序列化为 PID 1 并退出。

  5. PID 1 反序列化来自管道的数据,并继续使用/(以前的/sysroot)中的新配置。

我希望您喜欢 initramfs 中的 systemd 之旅。正如我们前面提到的,systemd 引导序列的其余部分将发生在 initramfs 之外,与我们到目前为止所讨论的差不多。

GUI 如何启动超出了本书的范围。在我们的下一章,我们将讨论现场 ISO 图像和救援模式。

6# 十、救援模式和实时图像

在这最后一章,我们将涵盖救援模式和现场图像。在我们的救援模式讨论中,我们将涵盖救援 initramfs,以及一些“无法启动”的问题。实时映像讨论包括 Squashfs、rootfs.img和实时映像的引导顺序。

救援模式

在救援模式下有两种启动方式。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-2

来自实时图像的救援模式条目

  • Through the built-in GRUB menuentry. Refer to Figure 10-1.

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 10-1

    GRUB 中的救援模式条目

  • 通过实时 ISO 图像。参见图 10-2 。

顾名思义,这种模式旨在拯救陷入“无法启动”问题的系统。想象一下这样一种情况,系统无法挂载根文件系统,您会收到这样一条永无止境的通用消息:

dracut-initqueue: warning dracut-initqueue timeout - starting timeout scripts’。

假设您只安装了一个内核,如下所示:

<snip>
.
.
[  OK  ] Started Show Plymouth Boot Screen.
[  OK  ] Started Forward Password R...s to Plymouth Directory Watch.
[  OK  ] Reached target Paths.
[  OK  ] Reached target Basic System.
[  145.832487] dracut-initqueue[437]: Warning: dracut-initqueue timeout - starting timeout scripts
[  146.541525] dracut-initqueue[437]: Warning: dracut-initqueue timeout - starting timeout scripts
[  147.130873] dracut-initqueue[437]: Warning: dracut-initqueue timeout - starting timeout scripts
[  147.703069] dracut-initqueue[437]: Warning: dracut-initqueue timeout - starting timeout scripts
[  148.267123] dracut-initqueue[437]: Warning: dracut-initqueue timeout - starting timeout scripts
[  148.852865] dracut-initqueue[437]: Warning: dracut-initqueue timeout - starting timeout scripts
[  149.430171] dracut-initqueue[437]: Warning: dracut-initqueue timeout - starting timeout scripts
.
.
</snip>

由于这个系统只有一个内核(不能启动),如果没有环境,你将如何解决“不能启动”的问题?救援模式就是为了这个唯一的目的而创建的。让我们首先选择默认的救援模式,它预装在 Linux 中,可以从 GRUB 菜单中选择。请参见图 10-3 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-3

GRUB 屏幕

救援模式将正常启动,如图 10-4 所示,如果一切正常,它将向用户显示其根文件系统。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-4

在救援模式下挂载的根文件系统

但是我想到了一个问题:当正常的内核无法启动时,为什么同一个系统能够在救援模式下启动呢?

这是因为当您安装 Fedora 或任何 Linux 发行版时,Linux 的安装程序 Anaconda 会在/boot中安装两个内核。

# ls -lh /boot/
total 164M
-rw-r--r--. 1 root root 209K Oct 22 01:03 config-5.3.7-301.fc31.x86_64
drwx------. 4 root root 4.0K Oct 24 04:44 efi
-rw-r--r--. 1 root root 181K Aug  2  2019 elf-memtest86+-5.01
drwxr-xr-x. 2 root root 4.0K Oct 24 04:42 extlinux
drwx------. 5 root root 4.0K Mar 28 13:37 grub2
-rw-------. 1 root root  80M Dec  9 10:18 initramfs-0-rescue-2058a9f13f9e489dba29c477a8ae2493.img
-rw-------. 1 root root  32M Dec  9 10:19 initramfs-5.3.7-301.fc31.x86_64.img
drwxr-xr-x. 3 root root 4.0K Dec  9 10:18 loader
drwx------. 2 root root  16K Dec  9 10:12 lost+found
-rw-r--r--. 1 root root 179K Aug  2  2019 memtest86+-5.01
-rw-------. 1 root root  30M Jan  6 09:37 new.img
-rw-------. 1 root root 4.3M Oct 22 01:03 System.map-5.3.7-301.fc31.x86_64
-rwxr-xr-x. 1 root root 8.9M Dec  9 10:18 vmlinuz-0-rescue-2058a9f13f9e489dba29c477a8ae2493
-rwxr-xr-x. 1 root root 8.9M Oct 22 01:04 vmlinuz-5.3.7-301.fc31.x86_64

如您所见,vmlinuz-5.3.7-301.fc31.x86_64是一个普通内核,而vmlinuz-0-rescue-19a08a3e86c24b459999fbac68e42c05是一个救援内核,它是一个独立的内核,有自己的 initramfs 文件,名为initramfs-0-rescue-19a08a3e86c24b459999fbac68e42c05.img

假设你安装了一个由nvidia提供的新软件包(.rpm.deb),里面有新的图形驱动程序。由于图形驱动程序必须添加到 initramfs 中,nvidia包重新构建了原来的内核 initramfs ( initramfs-5.3.7-301.fc31.x86_64.img)。因此,原来的内核有新添加的图形驱动程序,但是 rescue initramfs 没有添加该驱动程序。当用户尝试引导时,由于安装的图形驱动程序与连接的图形卡不兼容,系统无法使用原始内核(vmlinuz-5.3.7-301.fc31.x86_64)进行引导,但同时,由于不兼容的驱动程序不在 rescue initramfs 中,系统将使用 rescue 模式成功引导。救援模式内核将具有与普通内核相同的命令行参数,因此安装的救援内核知道用户根文件系统的名称。

图 10-5 显示了普通内核的命令行参数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-5

普通内核的命令行参数

图 10-6 显示了救援内核的命令行参数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-6

救援内核的命令行参数

救援模式初始化

救援模式 initramfs ( initramfs-0-rescue-2058a9f13f9e489dba29c477a8ae2493.img)的大小比原始内核的 initramfs ( initramfs-5.3.7-301.fc31.x86_64.img)大得多。

# ls -lh /boot/
total 164M
-rw-r--r--. 1 root root 209K Oct 22 01:03 config-5.3.7-301.fc31.x86_64
drwx------. 4 root root 4.0K Oct 24 04:44 efi
-rw-r--r--. 1 root root 181K Aug  2  2019 elf-memtest86+-5.01
drwxr-xr-x. 2 root root 4.0K Oct 24 04:42 extlinux
drwx------. 5 root root 4.0K Mar 28 13:37 grub2
-rw-------. 1 root root  80M Dec  9 10:18 initramfs-0-rescue-2058a9f13f9e489dba29c477a8ae2493.img
-rw-------. 1 root root  32M Dec  9 10:19 initramfs-5.3.7-301.fc31.x86_64.img
drwxr-xr-x. 3 root root 4.0K Dec  9 10:18 loader
drwx------. 2 root root  16K Dec  9 10:12 lost+found
-rw-r--r--. 1 root root 179K Aug  2  2019 memtest86+-5.01
-rw-------. 1 root root  30M Jan  6 09:37 new.img
-rw-------. 1 root root 4.3M Oct 22 01:03 System.map-5.3.7-301.fc31.x86_64
-rwxr-xr-x. 1 root root 8.9M Dec  9 10:18 vmlinuz-0-rescue-2058a9f13f9e489dba29c477a8ae2493
-rwxr-xr-x. 1 root root 8.9M Oct 22 01:04 vmlinuz-5.3.7-301.fc31.x86_64

这是为什么?这是因为救援 initramfs 不像普通内核的 initramfs 那样是特定于主机的。rescue initramfs 是一个通用的 initramfs,它是通过考虑用户可以在其上创建根文件系统的所有可能的设备而准备的。让我们比较一下这两个 initramfs 系统。

# tree
.
├── normal_kernel
│   └── initramfs-5.3.7-301.fc31.x86_64.img
└── rescue_kernel
    └── initramfs-0-rescue-2058a9f13f9e489dba29c477a8ae2493.img

2 directories, 2 files

我们将把它们提取到各自的目录中。

#/usr/lib/dracut/skipcpio
     initramfs-5.3.7-301.fc31.x86_64.img | gunzip -c | cpio -idv

#/usr/lib/dracut/skipcpio
     initramfs-0-rescue-2058a9f13f9e489dba29c477a8ae2493.img | gunzip -c | cpio -idv

我们将从提取的 initramfs 中创建文件列表。

# tree normal_kernel/ > normal.txt
# tree rescue_kernel/ > rescue.txt

以下是两个 initramfs 系统之间的差异。与正常的 initramfs 相比,rescue initramfs 系统几乎多了 2,189 个文件。此外,几乎 719 额外的模块已被添加到救援 initramfs。

# diff -yt rescue.txt normal.txt  | grep '<' | wc -l
     2186
# diff -yt rescue.txt normal.txt  | grep '<' | grep -i '.ko'  | wc -l
     719

<skip>
.
.
│   │   ├── lspci                                               <
│   │   ├── mdadm                                               <
│   │   ├── mdmon                                               <
│   │   ├── mdraid-cleanup                                      <
│   │   ├── mdraid_start                                        <
│   │   ├── mount.cifs                                          <
│   │   ├── mount.nfs                                           <
│   │   ├── mount.nfs4 -> mount.nfs                             <
│   │   ├── mpathpersist                                        <
│   │   ├── multipath                                           <
│   │   ├── multipathd                                          <
│   │   ├── nfsroot                                             <
│   │   ├── partx                                               <
│   │   ├── pdata_tools                                         <
│   │   ├── ping -> ../bin/ping                                 <
│   │   ├── ping6 -> ../bin/ping                                <
│   │   ├── rpcbind -> ../bin/rpcbind                           <
│   │   ├── rpc.idmapd                                          <
│   │   ├── rpcinfo -> ../bin/rpcinfo                           <
│   │   ├── rpc.statd                                           <
│   │   ├── setpci                                              <
│   │   ├── showmount                                           <
│   │   ├── thin_check -> pdata_tools                           <
│   │   ├── thin_dump -> pdata_tools                            <
│   │   ├── thin_repair -> pdata_tools                          <
│   │   ├── thin_restore -> pdata_tools                         <
│   │   ├── xfs_db                                              <
│   │   ├── xfs_metadump                                        <
│   │   └── xfs_repair                                          <
    ├── lib                                                     <
    │   ├── iscsi                                               <
    │   ├── lldpad                                              <
    │   ├── nfs                                                 <
    │   │   ├── rpc_pipefs                                      <
    │   │   └── statd                                           <
    │   │       └── sm                                          <
</skip>

rescue initramfs 将拥有用户可以在其上创建根文件系统的设备的几乎所有模块和支持的文件,而普通 initramfs 将是特定于主机的。它将只包含用户在其上创建了根文件系统的设备的那些模块和支持的文件。如果您想自己创建一个 rescue initramfs,那么您可以在基于 Fedora 的系统上安装一个dracut-config-generic包。这个包只提供了一个文件,并且它具有关闭特定于主机的 initramfs 生成的配置。

# rpm -ql dracut-config-generic
     /usr/lib/dracut/dracut.conf.d/02-generic-image.conf

# cat /usr/lib/dracut/dracut.conf.d/02-generic-image.conf
     hostonly="no"

如您所见,该文件将限制 dracut 创建特定于主机的 initramfs。

“无法启动”问题 9 (chroot)

**问题:**普通内核和救援内核都无法启动。图 10-7 显示了正常的内核紧急信息。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-7

内核紧急消息

抛出的内核紧急消息抱怨内核无法挂载根文件系统。我们前面看到,每当内核无法挂载用户的根文件系统时,它就会抛出dracut-initqueue超时消息。

'dracut-initqueue: warning dracut-initqueue timeout - starting timeout scripts'

然而,这一次,恐慌信息是不同的。因此,看起来这个问题与用户的根文件系统无关。另一个线索是它提到了 VFS 文件系统;VFS 代表“虚拟文件系统”,因此这表示紧急消息无法从 initramfs 挂载根文件系统。基于这些线索,我想我们已经隔离了这个问题,我们应该把注意力集中在两个内核的 initramfs 上。

如图 10-8 所示,救援模式内核紧急信息也是类似的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-8

救援模式内核紧急消息

**解决方法:**以下是解决问题的步骤:

  1. Since the installed rescue kernel is also panicking, we need to use the live image of Fedora or of any Linux distribution to boot. As shown in Figure 10-9 and Figure 10-10, we are using a live image of Fedora.

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 10-10

    使用实时映像启动

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 10-9

    实时图像欢迎屏幕

  2. 系统已在救援模式下启动。实时映像引导序列将在本章的“实时映像”一节中讨论。先成为sudo用户吧。

$ sudo su
We trust you have received the usual lecture from your local system administrator. It usually boils down to these three things:
#1) Respect the privacy of others.
#2) Think before you type.
#3) With great power comes great responsibility.
[root@localhost-live liveuser] #

  1. 我们在这里看到的根目录来自一个实时图像。因为实时映像内核不知道用户根文件系统的名称,所以它不能像救援内核一样挂载它。

    [root@localhost-live liveuser]# ls /
         bin boot dev etc home lib lib64 lost+found media mnt
         opt proc root run sbin srv sys tmp usr var
    
    
  2. 让我们来看看正常内核和救援内核的 initramfs 有什么问题。为此,我们需要首先挂载用户的根文件系统。

    # vgscan -v
      Found volume group "fedora_localhost-live" using metadata type lvm2
    
    # lvscan -v
      ACTIVE      '/dev/fedora_localhost-live/swap' [2.20 GiB] inherit
      ACTIVE      '/dev/fedora_localhost-live/root' [18.79 GiB] inherit
    
    # pvscan -v
      PV /dev/sda2  VG fedora_localhost-live  lvm2 [<21.00 GiB / 0  free]
      Total: 1 [<21.00 GiB] / in use: 1 [<21.00 GiB] / in no VG: 0 [0 ]
    
    

如您所见,该系统有一个基于 LVM 的用户根文件系统。物理卷在 sda 设备上。接下来,我们将在一个临时目录中挂载用户的根文件系统。

  1. 让我们检查 initramfs 文件的状态。

    # ls temp_root/boot/ -l
         total 0
    
    
# mkdir temp_root
# mount /dev/fedora_localhost-live/root temp_root/
# ls temp_root/
     bin   dev  home  lib64  media  opt   root  sbin  sys
     tmp usr boot  etc  lib   lost+found  mnt    proc  run
     srv   @System.solv user_root_fs.txt  var

用户根文件系统的引导目录为空。这是因为在这个系统上,引导是一个单独的分区。

# mount /dev/sda1 temp_root/boot/
#ls temp_root/boot/
Config-5.3.7-301.fc31.x86_64  efi elf-memtest86+-5.01
extlinux grub2 loader lost+found
Memtest86+-5.01 System.map-5.3.7-301.fc31.x86_64
vmlinuz-0-rescue-19a08a3e86c24b459999fbac68e42c05
vmlinuz-5.3.7-301.fc31.x86_64

令人惊讶的是,正如您所看到的,在用户的根文件系统上没有可用的 initramfs 文件,这就是两个内核都死机的原因。

因此,问题已经确定,我们需要重新生成 initramfs。要制作新的 initramfs,我们需要使用dracut命令,但是有一些问题。

  • 无论我们执行哪个二进制文件或命令,该二进制文件都将来自实时映像根文件系统。例如,dracut命令将从/usr/bin/dracut运行,而用户根文件系统的二进制文件在temp_root/usr/bin/dracut中。

  • 要运行任何二进制文件,它需要像libc.so这样的支持库,这些库将再次从一个活动映像的根文件系统中使用。这意味着我们现在使用的整个环境来自实时图像,这可能会产生严重的问题。例如,我们可以安装任何包,它将被安装在实时映像的根文件系统中,而不是用户的根文件系统中。

简而言之,我们需要将当前的根文件系统(/)从动态映像根文件系统更改为用户的根文件系统(temp_root)。chroot是我们为此需要使用的命令。

  1. 顾名思义,它会将 bash 的根从当前根更改为新根。只有当虚拟文件系统已经装载在新的根上时,chroot才会成功。

    root@localhost-live liveuser]# ls /
         bin  boot  dev  etc  home  lib  lib64  lost+found  media  mnt
         opt  proc  root  run  sbin  srv  sys  tmp  usr  var
    
    

我们当前的根是实时镜像根文件系统。在chroot之前,我们将挂载procdevdevptssysrun虚拟文件系统。

  1. 我们都被设置为进入用户的根文件系统。
# mount -v --bind /dev/ temp_root/dev
mount: /dev bound on /home/liveuser/temp_root/dev.

# mount -vt devpts devpts temp_root/dev/pts -o gid=5,mode=620
mount: devpts mounted on /home/liveuser/temp_root/dev/pts.

# mount -vt proc proc temp_root/proc
mount: proc mounted on /home/liveuser/temp_root/proc.

# mount -vt sysfs sysfs temp_root/sys
mount: sysfs mounted on /home/liveuser/temp_root/sys.

# mount -vt tmpfs tmpfs temp_root/run
mount: tmpfs mounted on /home/liveuser/temp_root/run.

# chroot temp_root/# ls
     bin   dev  home  lib64       media  opt   root  sbin  sys   tmp
     usr boot  etc  lib   lost+found  mnt    proc  run   srv
     @System.solv  user_root_fs.txt  var

所以,temp_root现在成了 bash 的根文件系统。如果退出这个 shell,bash 会将其根目录从用户的根文件系统更改为动态镜像根文件系统。所以,只要我们在同一个 shell 实例中,我们的根目录就是temp_root。现在,无论我们执行什么命令或二进制文件,它都将在用户的根文件系统环境中运行。因此,现在在这个环境中执行进程是完全安全的。

  1. 要解决这个“无法启动”的问题,我们需要重新生成 initramfs。

    root@localhost-live /]# ls /lib/modules
    5.3.7-301.fc31.x86_64
    
    [root@localhost-live /]# cd /boot/
    
    [root@localhost-live boot]# rpm -qa | grep -i 'kernel-5' kernel-5.3.7-301.fc31.x86_64
    
    [root@localhost-live boot]# dracut initramfs-5.3.7-301.fc31.x86_64.img 5.3.7-301.fc31.x86_64
    
    
  2. 如果你想重新生成救援内核 initramfs,那么你需要安装一个dracut-config-generic包。

  3. 重新启动后,系统能够启动,并且“无法启动”问题已得到修复。

企业 Linux 发行版的拯救模式

在一些 Linux 发行版(如 CentOS)中,rescue image 方法有点不同。Linux 的企业版将试图自己找到用户的根文件系统。让我们来看看实际情况。图 10-11 和图 10-12 显示了 CentOS 的救援模式选择程序。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-12

救援模式选择

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-11

CentOS 欢迎屏幕

它将启动,如图 10-13 所示,它将在屏幕上显示一些消息。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-13

信息丰富的消息

如果我们选择选项 1,continue,那么救援模式将搜索磁盘,并将自己找到根文件系统。一旦用户的根文件系统被识别,它将把它挂载到/mnt/sysimage目录下。请参见图 10-14 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-14

根文件系统安装在/mnt/sysimage 下

如您所见,它已经在/mnt/sysimage中挂载了用户的根文件系统;我们只需要chroot进入其中。但是美妙之处在于我们不需要预先挂载虚拟文件系统。这是因为,正如你在图 10-15 中看到的,CentOS 中使用的chroot二进制文件已经被定制,它将自己挂载虚拟文件系统。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-15

根目录

如果我们选择了选项 2,Read-Only Mount,那么救援脚本会以只读模式挂载用户的根文件系统,而不是在/mnt/sysimage中。如果我们选择了第三个选项Skip,救援系统就不会试图自己找到并安装用户的根文件系统;它只会给我们提供一个 Shell。

但是,当 CentOS ISO 的 rescue 内核没有用户的根文件系统名称时,它是如何找到根文件系统的呢?

在这里,Anaconda 没有办法找出用户的根文件系统名。Anaconda 将挂载连接到系统的每一个磁盘,并检查/etc/fstab是否存在。如果找到了/etc/fstab,那么它将从中获取用户的根文件系统名。如果您的系统连接了大量磁盘,那么 Anaconda 很可能需要很长时间来挂载用户的根文件系统。在这种情况下,最好手动挂载用户的根文件系统。查找用户根文件系统的源代码存在于 Anaconda 的源 tarball 中,如下所示:

#vim pyanaconda/storage/root.py

 91 def _find_existing_installations(devicetree):
 92     """Find existing GNU/Linux installations on devices from the device tree.
 93
 94     :param devicetree: a device tree to find existing installations in
 95     :return: roots of all found installations
 96     """
 97     if not os.path.exists(conf.target.physical_root):
 98         blivet_util.makedirs(conf.target.physical_root)
 99
100     sysroot = conf.target.physical_root
101     roots = []
102     direct_devices = (dev for dev in devicetree.devices if dev.direct)
103     for device in direct_devices:
104         if not device.format.linux_native or not device.format.mountable or \
105            not device.controllable or not device.format.exists:
106             continue
107
108         try:
109             device.setup()
110         except Exception:  # pylint: disable=broad-except
111             log_exception_info(log.warning, "setup of %s failed", [device.name])
112             continue
113
114         options = device.format.options + ",ro"
115         try:
116             device.format.mount(options=options, mountpoint=sysroot)
117         except Exception:  # pylint: disable=broad-except
118             log_exception_info(log.warning, "mount of %s as %s failed", [device.name, device.format.type])
119             blivet_util.umount(mountpoint=sysroot)
120             continue
121
122         if not os.access(sysroot + "/etc/fstab", os.R_OK):
123             blivet_util.umount(mountpoint=sysroot)
124             device.teardown()
125             continue
126
127         try:
128             (architecture, product, version) = get_release_string(chroot=sysroot)
129         except ValueError:
130             name = _("Linux on %s") % device.name
131         else:
132             # I'd like to make this finer grained, but it'd be very difficult
133             # to translate.
134             if not product or not version or not architecture:
135                 name = _("Unknown Linux")
136             elif "linux" in product.lower():
137                 name = _("%(product)s %(version)s for %(arch)s") % \
138                     {"product": product, "version": version, "arch": architecture}
139             else:
140                 name = _("%(product)s Linux %(version)s for %(arch)s") % \
141                     {"product": product, "version": version, "arch": architecture}
142
143         (mounts, swaps) = _parse_fstab(devicetree, chroot=sysroot)
144         blivet_util.umount(mountpoint=sysroot)
145         if not mounts and not swaps:
146             # empty /etc/fstab. weird, but I've seen it happen.
147             continue
148         roots.append(Root(mounts=mounts, swaps=swaps, name=name))
149

实时图像

实时图像是 Linux 系统最好的特性之一。如果我们只是坚持正常的硬盘引导部分,这本书就不会完整。让我们来看看一个 Linux 的现场图像是如何启动的。首先让我们挂载 ISO 映像,看看它包含了什么。

# mkdir live_image
# mount /dev/cdrom live_image/
mount: /home/yogesh/live_image: WARNING: device write-protected, mounted read-only.

# tree live_image/
live_image/
├── EFI
│   └── BOOT
│       ├── BOOT.conf
│       ├── BOOTIA32.EFI
│       ├── BOOTX64.EFI
│       ├── fonts
│       │   └── unicode.pf2
│       ├── grub.cfg
│       ├── grubia32.efi
│       ├── grubx64.efi
│       ├── mmia32.efi
│       └── mmx64.efi
├── images
│   ├── efiboot.img
│   ├── macboot.img
│   └── pxeboot
│       ├── initrd.img
│       └── vmlinuz
├── isolinux
│   ├── boot.cat
│   ├── boot.msg
│   ├── grub.conf
│   ├── initrd.img
│   ├── isolinux.bin
│   ├── isolinux.cfg
│   ├── ldlinux.c32
│   ├── libcom32.c32
│   ├── libutil.c32
│   ├── memtest
│   ├── splash.png
│   ├── vesamenu.c32
│   └── vmlinuz
└── LiveOS
    └── squashfs.img

实时图像分为四个目录:EFIimagesisolinuxLiveOS

  • EFI:

    我们在讨论 bootloader 的时候已经讨论过这个目录。UEFI 固件将跳转到该目录并运行grubx64.efi文件。grubx64.efi文件将读取grub.cfg文件,并将从isolinux目录中提取initrd.imgvmlinuz文件。

  • 图像:

    这将主要在我们通过 PXE 引导时使用。网络引导超出了本书的范围。

  • 等 linux:

    如果 UEFI 以 BIOS 方式启动,那么它将从这里读取grub.conf文件。该目录主要用于存储initrdvmlinuz文件。换句话说,这个目录是普通根文件系统的/boot

  • 层:

    这就是奇迹发生的地方。这个目录有一个名为squashfs.img的文件。一旦你安装了它,你会在里面找到rootfs.img

# mkdir live_image_extract_1
# mount live_image/LiveOS/squashfs.img  live_image_extract_1/

# ls live_image_extract_1/
     LiveOS
# ls live_image_extract_1/LiveOS/
     rootfs.img

# mkdir live_image_extract_2
# mount live_image_extract_1/LiveOS/rootfs.img live_image_extract_2/

# ls live_image_extract_2/
     bin  boot  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

壁球比赛

Squashfs 是一个小型的压缩只读文件系统。这个文件系统通常用于嵌入式系统,其中存储的每个字节都很宝贵。Squashfs 为我们提供了比 tarball 归档更多的灵活性和性能。Squashfs 在其中存储了一个动态 Fedora 的根文件系统(rootfs.img),它将以只读方式挂载。

# mount | grep -i rootfs
/home/yogesh/live_image_extract_1/LiveOS/rootfs.img on /home/yogesh/live_image_extract_2 type ext4 (ro,relatime,seclabel)

您可以使用squashfs-tool提供的mksquashfs命令来制作 Squashfs 图像/档案。

rootfs.img

rootfs.img是一个 ext4 文件系统,其中有一个典型的根文件系统。有些发行版为实时图像创建一个访客用户或一个名为live的用户,但是在 Fedora 中是根用户做所有的事情。

# file live_image_extract_1/LiveOS/rootfs.img
live_image_extract_1/LiveOS/rootfs.img: Linux rev 1.0 ext4 filesystem data, UUID=849bdfdc-c8a9-4fed-a727-de52e24d981f, volume name "Anaconda" (extents) (64bit) (large files) (huge files)

实时映像的引导序列

顺序如下:

  1. 固件会调用引导程序(grubx64.efi)。它将读取grub.cfg文件,并从isolinux目录中复制vmlinuzinitrd文件。

  2. 内核将在特定的位置提取自身,并将在任何可用的位置提取 initramfs。

  3. 从 initramfs 启动的 systemd 会在/dev/mapper/live-rwrootfs.img文件提取到设备映射器目标设备,将它挂载到根(/)文件系统,并将switch_root文件放入其中。

  4. 一旦根文件系统可用,您可以将它视为安装在 CD、DVD 或.iso文件中的正常操作。

此外,很明显,与特定于主机的 initramfs 相比,实时映像 initramfs 的大小要大得多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值