System V 启动脚本的运用与配置

本节将介绍 System V 的用法以及相关配置。System V 的特点就是配置文件多,所以本节会比 5.4 节(介绍 systemd)在篇幅上多很多,解释说明的地方也会多一点。

5.3.1 System V 的运行级概念

1983 年以来,System V 便是 UNIX 和类 UNIX(例如 Linux)系统中的经典启动过程。它包括小程序 init 用于启动诸如 login(由 getty 启动)这样的基础程序,并运行着名为 rc 的脚本。该脚本控制着一众附加脚本的执行,这些附加脚本是实施系统初始化所需要的任务的脚本。 程序 init 由文件/etc/inittab 控制着,并且组织成用户能够运行的运行级别形式:

  • 0 —停止;
  • 1 —单用户模式;
  • 2 —多用户,无网络;
  • 3 —完整的多用户模式;
  • 4 —用户可定义;
  • 5 —完整的多用户模式,附带显示管理;
  • 6 —重启。 常用的默认运行级为 3 或 5。

5.3.2 安装 LFS-Bootscripts

  • 大致构建用时:少于 0.1 SBU
  • 所需磁盘空间: 244 KB 解压并进入软件包:
tar -xf lfs-bootscripts-20200818.tar.bz2
cd lfs-bootscripts-20200818

安装软件包:

make install

安装完成后,退出当前目录并清理软件包:

cd ..
rm -rf lfs-bootscripts-20200818

5.3.3 配置 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 手册页面。对于 System V 而言,启动的核心命令便是 rc。上面的初始化文件将指示 rc 运行/etc/rc.d/rcS.d 目录中所有以 S 开头的脚本,然后便是 /etc/rc.d/rc?.d 目录中所有以 S 开头的脚本。目录中的问号由指定的 initdefault 值来决定。

为了方便,rc 会从/lib/lsb/init-functions 中读取函数库。该库也会读取一个可选的配置文件/etc/sysconfig/rc.site。任何在后续章节中描述到的系统配置文件中的参数,都可以放在这个文件中(仅限 System V 版),所有的系统参数都可以合并到该文件中。

为了调试方便,函数脚本会将日志输出到/run/var/bootlog 文件中。由于/run 目录是个 tmpfs(临时文件系统),所以该文件在启动之后就不会持续存在了,但在启动过程的最后,这些内容会被添附到更为持久的/var/log/boot.log 文件中。

想要改变运行级,可以使用命令 init ,其中的便是想要切换到的运行级。举个例子,若是想要重启计算机,可以使用命令 init 6,这是 reboot 命令的别名。就像 init 0 是 halt 的别名一样。

/etc/rc.d 下有许多看起来像 rc?.d 的目录(其中的“? ”便是运行级)以及 rcsysinit.d,这些目录下包含了许多符号链接。它们有些以 K 开头,另一些的以 S 开头,首字母的后面都有两位数字。其中的 K 意味着停止(杀死)服务,S 意味着启动服务。而这些数字则决定了脚本的运行顺序,从 00~99,数字越小的将越先被执行。当 init 切换到另一个运行级时,相应的服务将会依据运行级启动或停止。

真正的脚本放在/etc/rc.d/init.d 目录中。那些链接全部指向它们,真正工作的便是它们。K 开头的链接和 S 开头的链接指向的其实是/etc/rc.d/init.d 目录中的同一个脚本。能这样的原因是脚本可以用不同的参数调用,例如 start、stop、restart、reload 和 status。当遇到的链接是 K 开头,便相应执行脚本的 stop 参数。当遇到的链接是 S 开头的话,便相应执行脚本的 start 参数。

还有一种情况刚才没有提及。那便是 rc0.d 和 rc6.d 目录中以 S 开头的链接是不会导致任何服务被启动的。它们只会调用 stop 参数去停止某些服务。这背后的隐含逻辑便是,当用户打算重起或是关闭系统时,无须去启动些什么。系统仅仅是需要被停止而已。

表 5-1 便是脚本参数的描述。

参数作用
stop服务将被停止
restart服务将被停止,然后再一次启动
reload服务的配置文件将被更新。该命令用于修改服务配置文件后,无需重启服务
status告知你服务是否正处于运行中,以及相应的 PID

不必拘谨,启动过程的工作你可以随意修改(这毕竟是你自己的 LFS 系统)。这里的给出的文件只不过是抛砖引玉,示范而已。

5.3.4 udev 启动脚本

启动脚本/etc/rc.d/init.d/udev 启动 udevd,触发已经被内核创建好的「冷插拔(coldplug)」设备,并等待规则完成。该脚本还会解除/sbin/hotplug 中默认的 uevent 处理。这是因为内核不再需要调用外部的二进制文件了。取而代之的是,udevd 会为内核引发的 uevent 去监听 netlink 的套接字。

初始化脚本/etc/rc.d/init.d/udev_retry 负责重新触发子系统的事件,这些子系统的规则,可能会依赖于直到 mountfs 脚本运行后才挂载的文件系统(特别是/usr 和/var)。该脚本会在 mountfs 脚本后运行,所以这些规则(若是被重新触发)应该会在第二次的时候成功。这些被配置在 /etc/sysconfig/udev_retry 文件中;这个文件中,除了注释以外的任何单词(word),都会被认为是在重试时触发的子系统名称。想要找到设备的子系统,使用udevadm info --attribute-walk <device>,其中的是/dev 或/sys 中的绝对路径,例如/dev/sr0 或/sys/class/rtc。

5.3.5 配置系统时钟

脚本从硬件时钟,或称 BIOS 或 CMOS 时钟中读取时间。如果硬件时钟被设置为 UTC,该脚本会使用/etc/localtime 文件(它会告知程序 hwclock 用户的时区)将硬件时钟的时间转换为本地时间。因为无法检测硬件时钟是否为 UTC,所以需要手动去配置。

在内核的启动并检测硬件功能的时候,setclock 会通过 udev 运行。同样可以通过 stop 参数手动将系统时间存入 CMOS 时钟中。

如果你记不清是否将硬件时钟设置成 UTC,可以通过运行命令 hwclock --localtime --show 查看。这条命令会根据硬件时钟显示当前的时间。如果显示的时间和你的手表一致,那么硬件时间可能被设置成了本地时间。如果 hwclock 的输出不是本地时间,那就有可能是 UTC 时间。在 hwclock 的输出时间上加上或减去时区之间的时差。例如,我们常用的北京时间 UTC+8,在本地时间上减去 8 小时便是 UTC 时间了。 如果硬件时钟未设置成 UTC 时间,将 UTC 变量改成 0(零)。这里的 0 和设置中的 1 代表的不是时区,而是是否使用了 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

更多系统时间设置相关的问题可以参考 http://www. linuxfromscratch.org/hints/downloads/files/time.txt,它非常好地解释了如何在 LFS 中应对时间的问题。其中说明了诸如时区,UTC 和 TZ 环境变量之类的问题。 有兴趣的读者,可以尝试着将 CLOCKPARAMS 和 UTC 参数设置在文件/etc/sysconfig/rc.site 中。

5.3.6 配置 Linux 控制台

本章简要介绍控制台的启动脚本,以及控制台键盘映射、字体和内核的日志级别的配置方法。由于国内键盘使用的大都是 US 布局,所以本身是无须设置键盘映射的,且无法通过配置启动文件使控制台显示中文,所以改配置文件主要面向欧美等语言,不感兴趣的读者可以跳过。如果没有配置文件(且 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) 手册,为这些程序选择正确的参数。

/etc/sysconfig/console 文件中每行的格式应该像 VARIABLE="value"这样。详细的配置举例便不再赘述,下列为 console 文件中几个常见变量。

  • LOGLEVEL:日志级别从 1(没有消息)至 8。默认的级别是 7。
  • KEYMAP:用于指定需要加载的键盘映射名称,例如 it。变体过多的情况需指定父目录(例如, qwerty/es),以保证加载的键盘映射是恰当的。
  • KEYMAP_CORRECTIONS:用于解决现有的键盘映射不能完全满足的需求。例如,想要将欧元符号加入到原来不存在该符号的键盘映射中,将变量设置为 euro2 即可。
  • FONT:通常包括字体名、-m 和需要加载的应用字符映射名。例如,为了让字体 lat1-16 和 8859-1 应用字符映射一起加载(美国常用设置),将变量设置为 lat1-16 -m 8859-1。
  • UNICODE:如果需要使控制台进入 UTF-8 模式,则需将该变量设置为 1,yes 或 true。对于基于 UTF-8 语言环境的地区十分有用,不包含中文字符。
  • LEGACY_CHARSET:很多的键盘布局,在 Kbd 的软件包现有的键盘映射中并未囊括。如果该变量设置的编码是键盘映射中非 UTF-8 的编码,那么 console 的启动脚本就不会将键盘映射转换为 UTF-8。

对于中文,日语、韩语以及一些其他的语言需要的字符,Linu 的控制台还无法通过配置使之正常显示。用户若要使用这些语言,需要安装 X Window 系统,用于扩充所需字符域的字体,以及合适的输入法(例如,支持语言十分广泛的 SCIM)。

需要注意,/etc/sysconfig/console 文件只负责 Linux 文本控制台的本地化。对于 X Window 系统,通过 ssh 的会话,以及串口的控制台的键盘布局和终端字体,以上的设定并不适用。所以这些情况下,不要应用列出的最后两项设置中提到限制。

5.3.7 文件 rc.site

可选的/etc/sysconfig/rc.site 文件中包含着那些为每个 System V 启动脚本自动设置好的设定。这些设定也可以在/etc/sysconfig/目录的 hostname、console 和 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@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=

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 跳过命令。

默认情况下,文件系统检测静默执行。看上去就像是启动过程中的一个延迟。想要打开 fsck 的输出,设置变量。

重起时,你可能想完全跳过文件系统检测 fsck。为此,可以创建/fastboot 文件或以/sbin/shutdown -f -r now 命令重启系统。另一方面,你也可以通过创建/forcefsck,或在运行 shutdon 命令时,用-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 跳过。

5.3.8 配置网络

1. 创建网络接口配置文件

网络脚本开启和关闭哪些端口,一般取决于/etc/sysconfig/目录下的文件。该目录中应包含要配置接口的描述文件,例如 ifconfig.eth0。eth0 为接口名,而文件名只能是 ifconfig。文件的内容是此接口的属性信息,例如 IP 地址、子网掩码等。

如果你不确定网卡接口的名称,则可以在系统启动之后执行 ip link 或者 ls /sys/class/net。

可以看到本机网卡的接口名为 enp0s3。相对的创建静态 IP 示例文件如下。

cd /etc/sysconfig/
cat > ifconfig.enp0s3 << "EOF"
ONBOOT=yes
IFACE=enp0s3
SERVICE=ipv4-static
IP=10.0.2.15
GATEWAY=10.0.2.2
PREFIX=24
BROADCAST=10.0.2.255
EOF

请根据实际的网络情况,更改上述文件中的 IP 地址、网关、掩码等。 如果变量 ONBOOT 设置为 yes,则 System V 网络脚本将在系统启动期间激活网卡,如果设置为其他非 yes 值则不会自动激活网卡。在这种情况下,你可以通过命令 ifup 和 ifdown 手动激活或者是禁用网卡。 变量 IFACE 用于定义接口的名称,例如 eth0。所有的网络设备配置文件都依赖于它。文件扩展名必须与该值匹配。 变量 SERVICE 定义了获取 IP 地址的方式。LFS-Bootscripts 包中有模块化的 IP 分配格式,且在文件夹/lib/services/中建立文件以允许其他的 IP 获取方式。在 BLFS 书中,通常它被设置为动态主机配置协议(DHCP)。 变量 GATEWAY 应该包括默认的网关 IP 地址。如果不需要,应该直接将这行注释掉。 变量 PREFIX 表示子网所用的位数。IP 地址中每一个字节是 8 位。如果子网掩码是 255.255.255.0,那么就会使用最前面的 3 个字节(24 位)表达网络号。如果子网掩码是 255.255.255.240,则使用最开始的 28 位。前缀如果大于 24 位,一般都用于 DSL 或者是有线 ISP。在此示例中(PREFIX=24),子网掩码是 255.255.255.0。根据你实际的指望调整此字段,如果省略此字段,则默认为 24。 如欲获得更多信息,请浏览 ifup 的 man 手册。

2. 创建/etc/resolv.conf 文件

如果系统连接到互联网,则需要通过 DNS 服务将域名和 IP 地址进行相互的转换。你可以从 ISP 或者网络管理员处获得可用的 DNS 服务器,并填入/etc/resolv.conf 文件。可以通过以下命令建立此文件:

cat > /etc/resolv.conf << "EOF"
# Begin /etc/resolv.conf

domain LFS-SYSV
nameserver 10.0.2.3
nameserver 8.8.8.8

# End /etc/resolv.conf
EOF

字段 domain 可以忽略或者使用 search 字段替代。你可以参考 man 手册的 resolv.conf 部分获得更多信息。

其中,nameserver 对应的 IP 地址也应该替换为合适的 DNS 服务器的 IP 地址。除了 DNS 服务器还可以是本地网络中的路由。通常来说,一般会有多个 DNS 地址可供填写(需要备选服务器具有相关兼容性)。如果你只想要填写一个 DNS 服务器,那么将文件中的第 2 行 nameserver 移除即可。

上述的 DNS 是直接抄宿主机的,如果宿主机可以上网,大可直接抄宿主机的 DNS 即可。

当然也可以使用 Google 这种公共 IPv4 DNS 的服务器,或者国内的 DNS 服务器。

  • Google 公开的 DNS 解析服务器地址 IPv4 的为:8.8.8.8 和 8.8.4.4。IPv6 的为 2001:4860:4860::8888 和 2001:4860:4860::8844。
  • 114 DNS:114.114.114.114 和 114.114.115.115。
  • 阿里 DNS:223.5.5.5 和 223.6.6.6。
  • 百度 DNS:180.76.76.76。
  • OpenDNS:208.67.220.220。

3. 配置系统主机名称

在系统启动期间,文件/etc/hostname 用于建立系统的主机名称。 通过以下命令建立 /etc/hostname 文件且向其中写入主机名:

echo "<lfs>" > /etc/hostname

替换为你想要设置的名称。请不要输入完整域名(Fully Qualified Domain Name,FQDN),那应该是放在/etc/hosts 文件中的信息。

4. 创建 /etc/hosts

与/etc/resolv.conf 类似,也是处理域名和 IP 地址转换的。优先度高于/etc/resolv.conf,可以理解为一个本地的 DNS 服务。 通过以下命令建立/etc/hosts 文件:

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

127.0.0.1 localhost
::1       localhost ip6-localhost ip6-loopback
ff02::1   ip6-allnodes
ff02::2   ip6-allrouters

# End /etc/hosts
EOF

这部分没需要特别设置的,直接使用上述的命令,配置回环地址即可。