9.6. System V 引导脚本使用与配置

9.6.1. System V 引导脚本如何工作?

当前构建的 LFS 版本使用一种称为 SysVinit 的特殊引导架构,它的设计基于一组运行级别 (run level)。不同系统的引导过程可能会区别很大;一般不能假设那些在某个 Linux 发行版上正常工作的方法也能在 LFS 正常工作。LFS 有一些独特的工作方式,但它也遵守被广泛接受的标准。

另一种引导过程称为 systemd,我们不会继续讨论它。https://www.linux.com/training-tutorials/understanding-and-using-systemd/ 对其进行了详细介绍。

SysVinit (之后简称为init) 使用一组运行级别。有七个编号为 0 到 6 的运行级别 (实际上还有更多,但它们用于一些特殊情况,一般并不使用。参阅 init(8) 了解更多细节。) 每个运行级别对应于计算机在启动或关闭时应该进行的一组操作。默认运行级别是 3,下面介绍是 LFS 实现的不同运行级别:

0:停止系统运行
1:单用户模式
2:保留用于自定义,如果没有自定义和 3 相同
3:有网络连接的多用户模式
4:保留用于自定义,如果没有自定义和 3 相同
5:和 4 相同,一般用于 GUI 登录 (如 GNOME 的 gdm) 或 LXDE 的 lxdm
6:重启计算机

[注意]

注意

传统上,运行级别 2 被定义为没有网络连接的多用户模式,但是这只有在多年以前,多位用户可以通过串口同时连接系统时才有意义。在当前的系统环境中,这样的定义没有道理,因此我们将其指定为保留

9.6.2. 配置 SysVinit

在内核初始化过程中,第一个运行的程序是 init (如果没有通过命令行指定另外的程序)。这个程序读取初始化文件 /etc/inittab。执行以下命令创建该文件:

cat > /etc/inittab << "EOF"
# Begin /etc/inittab

id:3:initdefault:

si::sysinit:/etc/rc.d/init.d/rc S

l0:0:wait:/etc/rc.d/init.d/rc 0
l1:S1:wait:/etc/rc.d/init.d/rc 1
l2:2:wait:/etc/rc.d/init.d/rc 2
l3:3:wait:/etc/rc.d/init.d/rc 3
l4:4:wait:/etc/rc.d/init.d/rc 4
l5:5:wait:/etc/rc.d/init.d/rc 5
l6:6:wait:/etc/rc.d/init.d/rc 6

ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now

su:S06:once:/sbin/sulogin
s1:1:respawn:/sbin/sulogin

1:2345:respawn:/sbin/agetty --noclear tty1 9600
2:2345:respawn:/sbin/agetty tty2 9600
3:2345:respawn:/sbin/agetty tty3 9600
4:2345:respawn:/sbin/agetty tty4 9600
5:2345:respawn:/sbin/agetty tty5 9600
6:2345:respawn:/sbin/agetty tty6 9600

# End /etc/inittab
EOF

inittab 的手册页中可以找到对该初始化文件的解释。在 LFS 中,关键的命令是 rc。上面的初始化文件会指示 rc 先运行 /etc/rc.d/rcS.d 目录中所有文件名以 S 开头的脚本,再运行 /etc/rc.d/rc?.d 目录中所有文件名以 S 开头的脚本。这里问号表示 initdefault 值指定的默认运行级别。

为了方便起见,rc 脚本从 /lib/lsb/init-functions 中读取脚本函数库。该函数库又会读取一个可选的配置文件,/etc/sysconfig/rc.site。如果希望将所有系统参数集中到一个文件中,可以将后续各节描述的系统配置文件参数都写入这个文件。

为了方便调试,脚本函数会将所有输出记录到 /run/var/bootlog。由于 /run 是 tmpfs,这个文件在重新启动时不会被保留;然而,在引导过程结束时,该文件的内容会被附加到更持久的日志文件 /var/log/boot.log 的末尾。

9.6.2.1. 切换运行级别

通过命令 init <runlevel> 可以切换运行级别,这里<runlevel> 是希望切换到的运行级别。例如,为了重新启动计算机,用户可以执行 init 6 命令,它和 reboot 作用相同。同样,init 0halt 作用相同。

/etc/rc.d 中有一些名字如同 rc?.d 的目录 (这里 ? 是运行级别编号),以及一个目录 rcS.d,这些目录都包含一些符号链接。它们的文件名都以 KS 开头,且文件名中这两个字母之后一定有两位数字。K 表示停止 (杀死,kill) 一个服务,而 S 表示启动 (start) 一个服务。两位数字决定脚本运行的顺序,从 00 到 99 —— 数字更小的脚本更早执行。当 init 切换到另一个运行级别时,它会执行这些脚本,从而适当地启动或停止服务,满足选择的运行级别要求。

上述符号链接实际指向的脚本文件位于 /etc/rc/init.d。它们完成实际的工作。一对 K 链接和 S 链接指向 /etc/rc/init.d 中的同一脚本,这是因为脚本接受不同的参数,如startstoprestartreload,以及 status。在发现 K 链接时,会以 stop 参数运行脚本。在发现 S 链接时,会以 start 参数运行脚本。

下面是脚本接受的不同参数及其解释:

start

启动服务。

stop

停止服务。

restart

停止服务,再重新启动它。

reload

更新服务配置。当服务的配置文件被修改后,如果不需要重新启动它,就使用该参数。

status

报告服务是否正在运行中,如果正在运行,报告其 PID。

您可以自由修改引导过程的工作方式 (毕竟这是您自己的 LFS 系统)。我们给出的文件只是示例,展示完成引导过程的一种方式。

9.6.3. Udev 引导脚本

/etc/rc.d/init.d/udev 初始化脚本启动 udevd,触发内核已经创建的“冷插拔”设备,并等待所有 udev 规则执行完毕。该脚本也会取消默认的 uevent 处理程序 /sbin/hotplug。这是因为内核不再需要调用外部二进制程序。相反,udevd 会监听一个 netlink 套接字,以获取内核发出的 uevent 事件。

/etc/rc.d/init.d/udev_retry 脚本处理一些子系统的重新触发事件,这些子系统的规则可能依赖于 mountfs 脚本运行时才会挂载的文件系统 (特别地,独立挂载的 /usr/var 文件系统可能导致这种依赖)。该脚本在 mountfs 脚本之后运行,因此这些规则 (如果被重新触发) 这一次应该能够成功执行。/etc/sysconfig/udev_retry 文件配置该脚本,其中除注释外的每个单词都被认为是一个需要重新触发的子系统名。为了找到某个设备的子系统,可以使用命令 udevadm info --attribute-walk <device>,这里 <device> 是一个 /dev 或 /sys 中的绝对路径,例如 /dev/sr0 或 /sys/class/rtc。

参阅第 9.3.2.3 节 “模块加载”了解更多关于模块加载和 udev 的信息。

9.6.4. 配置系统时钟

setclock 脚本从硬件时钟读取时间,硬件时钟也常被称为 BIOS 时钟或互补金属氧化物半导体 (CMOS) 时钟。如果硬件时钟被设为 UTC 时间,该脚本会根据 /etc/localtime 文件 (它告知 hwclock 程序用户处于哪个时区),将硬件时钟的时间转换成本地时间。不存在确定硬件时钟是否为 UTC 的方法,因此这必须手动设置。

setclock 在引导后,由内核检测硬件功能时通过 udev 执行。可以用 stop 参数手动调用它,以将系统时间写入 CMOS 时钟。

如果您不确定您的硬件时钟是否设置为 UTC,运行 hwclock --localtime --show 命令,它会显示硬件时钟给出的当前时间。如果这个时间和您的手表显示的一致,则说明硬件时钟被设定为本地时间。相反,如果 hwclock 输出的时间不是本地时间,则硬件时钟很可能被设定为 UTC 时间。根据您的时区,在 hwclock 显示的时间上加减对应的小时数,进行进一步的验证。例如,如果您现在处于北美山地时区,即 GMT -0700,则需要在本地时间上加 7 小时,再进行比较。

将下面的配置文件中的 UTC 变量值改为 0 (零),表示硬件时钟没有设为 UTC。

执行以下命令,创建新的 /etc/sysconfig/clock 文件:

cat > /etc/sysconfig/clock << "EOF"
# Begin /etc/sysconfig/clock

UTC=1

# Set this to any options you might need to give to hwclock,
# such as machine hardware clock type for Alphas.
CLOCKPARAMS=

# End /etc/sysconfig/clock
EOF

https://www.linuxfromscratch.org/hints/downloads/files/time.txt 可以找到一个较好地介绍了如何在 LFS 中处理时间问题的 hint。它解释了与时区,UTC,以及 TZ 环境变量相关的问题。

[注意]

注意

CLOCKPARAMS 和 UTC 参数也可以在 /etc/sysconfig/rc.site 文件中设置。

9.6.5. 配置 Linux 控制台

本节讨论如何配置 console 引导脚本,使之正确设定键盘映射,控制台字体,以及控制台内核日志级别。如果不使用非 ASCII 字符 (如版权符号,英镑符号,或者欧元符号),而且使用美式键盘,则可以忽略本节的大多数内容。如果不创建本节的配置文件 (且 rc.site 中没有对应的设置),则 console 脚本什么也不做。

console 脚本读取 /etc/sysconfig/console 中的配置信息。它根据配置决定使用何种键映射和控制台字体。一些与特定语言相关的 HOWTO 文档可以帮助您进行配置;参阅 https://tldp.org/HOWTO/HOWTO-INDEX/other-lang.html。如果仍然有疑问,在/usr/share/keymaps/usr/share/consolefonts 中寻找可用的键映射和控制台字体,并阅读 loadkeys(1)setfont(8) 手册页,以确认应该传递给这两个程序的正确参数。

/etc/sysconfig/console 文件应该包含若干行,每一行的的格式都是:变量名=值。下列变量名会被识别:

LOGLEVEL

该变量指定被发送到控制台的内核消息日志级别,正如使用 dmesg 设置的那样。有效的级别是 1 (不输出内核消息) 到 8 之间的某个数。默认的日志级别是 7,此时的消息输出较为详细。

KEYMAP

该变量指定传递给 loadkeys 程序的参数,它通常是需要加载的键映射名,例如 it。如果这个变量没有设定,引导脚本不会运行 loadkeys,系统将使用内核的默认键映射。注意某些键映射有文件名相同的不同版本 (例如 cz 的和它的变体在 qwerty/ 和 qwertz/ 中,es 在 olpc/ 和 qwerty/ 中,以及 trf 在 fgGIod/ 和 qwerty/ 中)。在这种情况下,父目录名也要指定 (例如 qwerty/es),以保证加载正确的键映射。

KEYMAP_CORRECTIONS

这个 (很少使用的) 变量指定第二次调用 loadkeys 程序时使用的参数。如果现有的键映射不完全符合要求,需要进行微调,这个变量是很有用的。例如,如果需要为通常不包含欧元符号的键映射添加它,将这个变量设为 euro2

FONT

该变量指定传递给 setfont 程序的参数。通常它包含字体名,-m,以及需要加载的应用程序字符映射名。例如,为了加载lat1-16字体和8859-1应用程序字符映射 (适用于美国),可以将该变量设置为 lat1-16 -m 8859-1。在 UTF-8 模式下,内核使用应用程序字符映射将键映射中编组的 8 位键码转化为 UTF-8,因此 “-m” 参数的值应该被设定为键映射中编组的键码编码。

UNICODE

将该变量设为 1yes,或者true,可以将控制台设置于 UTF-8 模式。这对于基于 UTF-8 的 locale 很有用,但对于其他 locale 有害。

LEGACY_CHARSET

对于许多键盘布局,Kbd 软件包没有提供 Unicode 键映射。如果该变量被设定为一个可用的非 UTF-8 键映射的编码,console 引导脚本会在需要时将可用的键映射转换成 UTF-8。

一些例子:

  • 第 9.7 节 “配置系统 Locale”中,我们会指定 C.UTF-8 为 Linux 控制台中交互会话的 locale,因此我们需要设定 UNICODE1。另外 Kbd 软件包提供的,包含 C.UTF-8 locale 下程序输出消息中全部字符字形的控制台字体有 /usr/share/consolefonts 中的 LatArCyrHeb*.psfu.gzLatGrkCyr*.psfu.gzLat2-Terminus16.psfu.gz,以及 pancyrillic.f16.psfu.gz (其他提供的字体缺失一些字符的字形,如 Unicode 左右引号和 Unicode 英文破折号)。因此将它们中的某个,例如 Lat2-Terminus16.psfu.gz 设为默认控制台字体:

    cat > /etc/sysconfig/console << "EOF"
    # Begin /etc/sysconfig/console
    
    UNICODE="1"
    FONT="Lat2-Terminus16"
    
    # End /etc/sysconfig/console
    EOF
  • 对于非 Unicode 配置,一般只需要设置 KEYMAP 和 FONT 两个变量。例如,下面是一个波兰语配置:

    cat > /etc/sysconfig/console << "EOF"
    # Begin /etc/sysconfig/console
    
    KEYMAP="pl2"
    FONT="lat2a-16 -m 8859-2"
    
    # End /etc/sysconfig/console
    EOF
  • 正如前文所述,有时需要微调一个现有的键映射。下面的例子为德语键映射添加欧元符号:

    cat > /etc/sysconfig/console << "EOF"
    # Begin /etc/sysconfig/console
    
    KEYMAP="de-latin1"
    KEYMAP_CORRECTIONS="euro2"
    FONT="lat0-16 -m 8859-15"
    UNICODE="1"
    
    # End /etc/sysconfig/console
    EOF
  • 下面是一个使用 Unicode 的白俄罗斯语配置,白俄罗斯语有现有的 UTF-8 键映射:

    cat > /etc/sysconfig/console << "EOF"
    # Begin /etc/sysconfig/console
    
    UNICODE="1"
    KEYMAP="bg_bds-utf8"
    FONT="LatArCyrHeb-16"
    
    # End /etc/sysconfig/console
    EOF
  • 由于在上面的例子中使用了 512 个字形的 LatArCyrHeb-16 字体,在 Linux 控制台中不能使用明亮的颜色,除非使用了帧缓冲。如果希望在没有帧缓冲的情况下继续使用明亮的颜色,且不需要那些不属于自己母语的字符,可以使用专用于一种语言的 256 字形字体,配置文件如下:

    cat > /etc/sysconfig/console << "EOF"
    # Begin /etc/sysconfig/console
    
    UNICODE="1"
    KEYMAP="bg_bds-utf8"
    FONT="cyr-sun16"
    
    # End /etc/sysconfig/console
    EOF
  • 下面的例子展示了从 ISO-8859-1 到 UTF-8 的键映射自动转换,同时在 Unicode 模式下启用了死键:

    cat > /etc/sysconfig/console << "EOF"
    # Begin /etc/sysconfig/console
    
    UNICODE="1"
    KEYMAP="de-latin1"
    KEYMAP_CORRECTIONS="euro2"
    LEGACY_CHARSET="iso-8859-15"
    FONT="LatArCyrHeb-16 -m 8859-15"
    
    # End /etc/sysconfig/console
    EOF
  • 某些键映射有死键 (即,这些键本身不产生字符,而是在下一次按键产生的字符上附加音调) 或定义了组合规则 (例如在默认键映射中,按下 Ctrl+. A E 得到 Æ)。Linux-6.10.5 只有在被组合的不是多字节字符的情况下,才能正常解析死键和组合规则。这个缺陷不影响欧洲语言的键映射,因为在欧洲语言中要么是一个音调被附加到不带音调的 ASCII 字符上,要么是两个 ASCII 字符被组合在一起。然而,在 UTF-8 模式中,以希腊语为例,当某人要在 α字符上附加音调时,就会出现问题。解决方法是要么不使用 UTF-8,要么安装 X 窗口系统,它处理输入时没有这个限制。

  • 对于中文,日文,韩文,以及其他一些语言文字,无论如何配置 Linux 控制台,都不可能正常显示所需要的字符。这些语言的用户应该安装 X 窗口系统,能够覆盖所需要的字符的字体,以及合适的输入法 (如 SCIM 支持许多语言的输入)。

[注意]

注意

/etc/sysconfig/console 文件只控制 Linux 字符控制台的本地化。它和 X 窗口系统,ssh 连接,或者串口终端中的键盘布局设置和终端字体毫无关系。在这些情况下,不存在上述的两项限制。

9.6.6. 在引导时创建文件

有时,我们希望在引导时创建一些文件,例如可能需要 /tmp/.ICE-unix 目录。为此,可以在 /etc/sysconfig/createfiles 配置脚本中创建一项。该文件的格式包含在默认配置文件的注释中。

9.6.7. 配置 Sysklogd 脚本

sysklogd 脚本启动 sysklogd 程序,这是 System V 初始化过程的一部分。-m 0 选项关闭 sysklogd 每 20 分钟写入日志文件的时间戳。如果您希望启用这个周期性时间戳标志,编辑 /etc/sysconfig/rc.site,将 SYSKLOGD_PARMS 定义为您希望的值。例如,如果要删除所有参数,将该变量设定为空:

SYSKLOGD_PARMS=

参阅 man syslogd 了解更多可用选项。

9.6.8. rc.site 文件

可选的 /etc/sysconfig/rc.site 文件包含了为每个 System V 引导脚本自动设定的配置。/etc/sysconfig/ 目录中 hostnameconsole,以及 clock 文件中的变量值也可以在这里设定。如果这些分立的文件和 rc.site 包含相同的变量名,则分立文件中的设定被优先使用。

rc.site 也包含自定义引导过程其他属性的参数。设定 IPROMPT 变量会启用引导脚本的选择性执行。其他选项在文件注释中描述。该文件的默认版本如下:

# rc.site
# Optional parameters for boot scripts.

# Distro Information
# These values, if specified here, override the defaults
#DISTRO="Linux From Scratch" # The distro name
#DISTRO_CONTACT="lfs-dev@lists.linuxfromscratch.org" # Bug report address
#DISTRO_MINI="LFS" # Short name used in filenames for distro config

# Define custom colors used in messages printed to the screen

# Please consult `man console_codes` for more information
# under the "ECMA-48 Set Graphics Rendition" section
#
# Warning: when switching from a 8bit to a 9bit font,
# the linux console will reinterpret the bold (1;) to
# the top 256 glyphs of the 9bit font.  This does
# not affect framebuffer consoles

# These values, if specified here, override the defaults
#BRACKET="\\033[1;34m" # Blue
#FAILURE="\\033[1;31m" # Red
#INFO="\\033[1;36m"    # Cyan
#NORMAL="\\033[0;39m"  # Grey
#SUCCESS="\\033[1;32m" # Green
#WARNING="\\033[1;33m" # Yellow

# Use a colored prefix
# These values, if specified here, override the defaults
#BMPREFIX="      "
#SUCCESS_PREFIX="${SUCCESS}  *  ${NORMAL} "
#FAILURE_PREFIX="${FAILURE}*****${NORMAL} "
#WARNING_PREFIX="${WARNING} *** ${NORMAL} "

# Manually set the right edge of message output (characters)
# Useful when resetting console font during boot to override
# automatic screen width detection
#COLUMNS=120

# Interactive startup
#IPROMPT="yes" # Whether to display the interactive boot prompt
#itime="3"    # The amount of time (in seconds) to display the prompt

# The total length of the distro welcome string, without escape codes
#wlen=$(echo "Welcome to ${DISTRO}" | wc -c )
#welcome_message="Welcome to ${INFO}${DISTRO}${NORMAL}"

# The total length of the interactive string, without escape codes
#ilen=$(echo "Press 'I' to enter interactive startup" | wc -c )
#i_message="Press '${FAILURE}I${NORMAL}' to enter interactive startup"

# Set scripts to skip the file system check on reboot
#FASTBOOT=yes

# Skip reading from the console
#HEADLESS=yes

# Write out fsck progress if yes
#VERBOSE_FSCK=no

# Speed up boot without waiting for settle in udev
#OMIT_UDEV_SETTLE=y

# Speed up boot without waiting for settle in udev_retry
#OMIT_UDEV_RETRY_SETTLE=yes

# Skip cleaning /tmp if yes
#SKIPTMPCLEAN=no

# For setclock
#UTC=1
#CLOCKPARAMS=

# For consolelog (Note that the default, 7=debug, is noisy)
#LOGLEVEL=7

# For network
#HOSTNAME=mylfs

# Delay between TERM and KILL signals at shutdown
#KILLDELAY=3

# Optional sysklogd parameters
#SYSKLOGD_PARMS="-m 0"

# Console parameters
#UNICODE=1
#KEYMAP="de-latin1"
#KEYMAP_CORRECTIONS="euro2"
#FONT="lat0-16 -m 8859-15"
#LEGACY_CHARSET=

9.6.8.1. 自定义引导和关机脚本

LFS 引导脚本能够较为高效地引导和关闭系统,但是您仍然可以微调 rc.site 文件以进一步提高速度,或根据您的个人品味调整引导消息。为此,需要修改上面给出的/etc/sysconfig/rc.site 文件。

  • 在引导脚本 udev 的执行过程中,它会调用 udev settle,该命令需要一段时间才能完成。您的系统可能真的需要这段时间,也可能实际上并不需要,这和系统中的设备有关。如果您只有简单分区,且只有一块网卡,引导过程可能并不需要等待该命令。设定 OMIT_UDEV_SETTLE=y 可以跳过它。

  • 引导脚本 udev_retry 在默认配置下也会执行 udev settle。这只有在 /var 目录是独立挂载的文件系统时才有必要,因为系统时钟需要 /var/lib/hwclock/adjtime 文件。其他的自定义配置也可能需要等待 udev 完成,但在大多数配置中并没有这种必要。设定 OMIT_UDEV_RETRY_SETTLE=y 可以跳过它。

  • 默认配置中,文件系统检查是静默的。这可能看上去像引导过程中的时延。设定变量 VERBOSE_FSCK=y 可以显示 fsck 的输出。

  • 在重新启动系统时,您可能希望完全跳过进行文件系统检查的 fsck 命令。为此,可以创建一个文件 /fastboot,或者使用命令 /sbin/shutdown -f -r now。反之,如果您希望在重新启动系统时强制检查所有文件系统,可以创建文件 /forcefsck,或者在前面的 shutdown 命令中使用 -F 参数代替 -f

    设定变量 FASTBOOT=y 会在引导过程中完全禁止 fsck,直到移除该变量。从长远角度看,不推荐使用它。

  • 通常,/tmp 目录中的所有文件都会在引导过程中被删除。如果其中有较多的文件或目录,可能导致引导过程中出现可观的延时。设定变量 SKIPTMPCLEAN=y 可以跳过删除这些文件的引导步骤。

  • 在关机时,init 程序会向它启动的每个程序 (例如 agetty) 发送 TERM 信号,等待一段固定的时间 (默认是 3 秒),然后向每个进程发送 KILL 信号,再等待一次。对于所有未被自己的引导脚本关闭的进程,sendsignals 脚本会重复以上过程。init 的等待时间可以通过传递参数进行设定。例如,如果要完全取消 init 的等待时间,可以在关机或重新启动时传递 -t0 参数 (如同 /sbin/shutdown -t0 -r now)。sendsignals 中的等待时间可以通过设定参数 KILLDELAY=0 取消。