7.6. System V 启动脚本使用与配置

7.6.1. System V 启动脚本如何工作?

Linux 使用一种称为 SysVinit 的特殊引导架构,它基于 运行级别 (run-level)的概念而构建。 不同系统的 SysVinit 可能会区别很大, 因此不能假设那些在某个 Linux 发行版上正常工作的方法也能在 LFS 正常工作。LFS 有自己的处事原则,但它也遵守被广泛接受的标准。

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

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

7.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:S016:once:/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 的 man 页面中可以找到对该初始化文件的解释。对于 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 文件末尾。

7.6.2.1. 切换运行级别

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

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

符号链接实际指向的脚本位于 /etc/rc.d/init.d,它们完成实际的工作。 K 链接和 S 链接指向 /etc/rc.d/init.d 中的相同脚本,这是因为脚本接受不同的参数,如 startstoprestartreload 以及 status。当发现 K 链接时,脚本被传递 stop 参数。当发现 S 链接时, 对应的脚本被传递 start 参数。

以上解释有一个例外情况,当 rc0.drc6.d 目录中出现以 S 开头的链接时,它们不会启动任何服务。相反,它们被以参数 stop 调用,并停止服务。 这一行为背后的逻辑是,在重新启动系统或停止系统运行时,不需要启动任何服务, 只需要停止整个系统。

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

start

启动服务。

stop

停止服务。

restart

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

reload

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

status

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

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

7.6.3. Udev 启动脚本

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

某些子系统的 udev 规则依赖于一些直到 mount_fs 脚本被执行才会挂载的文件系统(如独立的 /usr/var 文件系统就会导致这种现象) 初始化脚本 /etc/rc.d/init.d/udev_retry 负责重新触发这些子系统的事件。该脚本在 mountfs 后运行,因此这些规则(如果被重新触发)这一次应该能够成功执行。 配置文件 /etc/sysconfig/udev_retry 中包含的除注释外的所有单词都会被认为是一个需要触发的子系统。 为了找到某个设备的子系统,执行 udevadm info --attribute-walk <device>, 这里 <device> 是一个 /dev 或 /sys 中的绝对路径, 例如 /dev/sr0 或 /sys/class/rtc。

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

7.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 时间, 在下面的配置文件中,应该将 UTC 变量的值设为 0

[注意]

译注

Windows 会将硬件时钟设定为本地时间,因此如果您要同时安装 Windows 和 Linux,就要将 Linux (包括 LFS 和其他发行版) 配置为使用本地时间,除非能够忍受 Windows 和 Linux 显示的不同时间 (必有一个是错误的)。

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

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

UTC=1

# 将该变量设置为您希望传递给 hwclock 命令的选项,
# 例如 Alpha 机器上的硬件时钟类型。
CLOCKPARAMS=

# End /etc/sysconfig/clock
EOF

访问 http://www.linuxfromscratch.org/hints/downloads/files/time.txt , 可以找到一个关于如何在 LFS 系统中处理时间问题的好的提示。 它解释了时区、UTC 和 TZ 环境变量等问题。

[注意]

注意

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

7.6.5. 配置 Linux 控制台

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

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

/etc/sysconfig/console 文件应当包含若干行,它们的格式为:变量名="值"。下列变量名会被识别:

LOGLEVEL

该变量指定被发送到终端的内核消息日志等级, 正如使用 dmesg -n 设置的那样。 有效的等级是 "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

将该变量设置为 1yestrue,可以将终端设置为 UTF-8 模式。 这对于基于 UTF-8 的 locale 来说很有用,但对于其他 locale 有害。

LEGACY_CHARSET

对于许多键盘布局,Kbd 软件包不包含现成的 Unicode 键映射。 如果将该变量设置为可用的非 UTF-8 键映射的编码,则 console 启动脚本会将可用的键映射即时转换成 UTF-8。

一些例子:

  • 对于非 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"
    
    # 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-15 到 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-4.18.5 只有在被组合的不是多字节字符的情况下, 才能正常解析死键和组合规则。这个缺陷不影响欧洲语言的键映射, 因为在欧洲语言中要么是一个音调被附加到不带音调的 ASCII 字符上, 要么是两个 ASCII 字符被组合在一起。然而,在 UTF-8 模式中, 以希腊语为例,当某人要在 alpha 字符上附加一个音调时, 就会出现问题。解决方法是要么不使用 UTF-8,要么安装 X 窗口系统, 它处理输入时没有这个限制。

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

[注意]

注意

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

7.6.6. 在引导时创建文件

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

7.6.7. 配置 sysklogd 脚本

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

SYSKLOGD_PARMS=

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

7.6.8. rc.site 文件

可选的 /etc/sysconfig/rc.site 文件包含为每个 System V 启动脚本自动设定的设置。 /etc/sysconfig/ 目录中 hostnameconsole 以及 clock 文件的变量值也可以在 rc.site 中设置。 如果某个变量存在于这三个文件当中,它们会覆盖 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@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 seet 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=

7.6.8.1. 自定义开关机脚本

LFS 启动脚本能够较为高效地引导和关闭系统, 但您仍然可以进行一些调整,进一步提高速度, 并根据您的喜好修改屏幕显示的消息。为此,需要修改上面给出的 /etc/sysconfig/rc.site 文件。

  • 在启动脚本 udev 的运行过程中,它执行 udev settle 命令, 该命令需要较长时间才能完成。然而这个执行时间或许不是必要的, 如果您只有简单分区,且仅有一块网卡, 那么引导过程可能不需要等待该命令完成。设定变量 OMIT_UDEV_SETTLE=y,可以跳过该命令。

  • 启动脚本 udev_retry 默认情况下也会执行 udev settle。 一般来说,该命令只有 /var 目录是一个单独的挂载点时才必要,这是因为系统时钟需要文件 /var/lib/hwclock/adjtime。 其他的定制也可能导致该脚本需要等待 udev 完成, 但在绝大多数 LFS 系统中没有这个必要。 设定变量 OMIT_UDEV_RETRY_SETTLE=y 跳过这一命令。

  • 默认情况下,文件系统检查是安静(没有输出)的。 因此,在引导过程中,检查过程可能看上去是一个很长的时延。 设置变量 VERBOSE_FSCK=y 可以打开 fsck 的输出。

  • 在重启系统时,您可能希望完全跳过文件系统检查命令 fsck。为此,可以创建文件 /fastboot,或使用命令 /sbin/shutdown -f -r now 重启系统。 另一方面,您可以创建 /forcefsck 文件, 或使用 -F 参数(而不是 -f)运行 shutdown, 在重启系统时强制检查所有文件系统。

    设定变量 FASTBOOT=y 会完全禁止引导过程中 fsck 的执行,直到该变量被删除。不建议永久使用该变量。

  • 正常情况下, /tmp 中的所有文件在引导时都被删除。如果其中的文件和目录较多, 可能在引导过程中造成可观的延迟。设定变量 SKIPTMPCLEAN=y 可以跳过删除这些文件的过程。

  • 在系统关闭的过程中,init 向它启动的所有程序(如 getty)发送一个 TERM 信号,等待一段时间 (默认 3 秒),再向每个进程发送 KILL 信号,之后再次等待。 在 sendsignals 脚本中, 以上过程会对那些没有被自己的引导脚本关闭的进程再次执行。 init 的时延可以通过传递参数设定, 为了完全取消时延,可以在关闭或重启系统时使用 -t0 参数 (如 shutdown -t0 -r now)。 sendsignals 脚本的时延可以通过设定参数 KILLDELAY=0 跳过。