并发启动原理之二:解决 D-Bus 依赖
D-Bus 是 desktop-bus 的简称,是一个低延迟、低开销、高可用性的进程间通信机制。它越来越多地用于应用程序之间通信,也用于应用程序和操作系统内核之间的通信。很多现代的服务进程都使用D-Bus取代套接字作为进程间通信机制,对外提供服务。比如简化Linux 网络配置的NetworkManager 服务就使用D-Bus 和其他的应用程序或者服务进行交互:邮件客户端软件evolution 可以通过D-Bus 从NetworkManager 服务获取网络状态的改变,以便做出相应的处理。
D-Bus 支持所谓"busactivation"功能。如果服务A 需要使用服务 B 的 D-Bus 服务,而服务 B 并没有运行,则 D-Bus 可以在服务 A 请求服务 B 的 D-Bus 时自动启动服务 B。而服务 A 发出的请求会被 D-Bus 缓存,服务 A 会等待服务 B 启动就绪。利用这个特性,依赖D-Bus 的服务就可以实现并行启动。
并发启动原理之三:解决文件系统依赖
系统启动过程中,文件系统相关的活动是最耗时的,比如挂载文件系统,对文件系统进行磁盘检查(fsck),磁盘配额检查等都是非常耗时的操作。在等待这些工作完成的同时,系统处于空闲状态。那些想使用文件系统的服务似乎必须等待文件系统初始化完成才可以启动。但是systemd 发现这种依赖也是可以避免的。
Systemd 参考了 autofs 的设计思路,使得依赖文件系统的服务和文件系统本身初始化两者可以并发工作。autofs可以监测到某个文件系统挂载点真正被访问到的时候才触发挂载操作,这是通过内核automounter 模块的支持而实现的。比如一个open()系统调用作用在"/misc/cd/file1"的时候,/misc/cd 尚未执行挂载操作,此时 open()调用被挂起等待,Linux 内核通知 autofs,autofs 执行挂载。这时候,控制权返回给open()系统调用,并正常打开文件。
Systemd 集成了 autofs 的实现,对于系统中的挂载点,比如/home,当系统启动的时候,systemd 为其创建一个临时的自动挂载点。在这个时刻/home真正的挂载设备尚未启动好,真正的挂载操作还没有执行,文件系统检测也还没有完成。可是那些依赖该目录的进程已经可以并发启动,他们的open()操作被内建在systemd 中的autofs 捕获,将该open()调用挂起(可中断睡眠状态)。然后等待真正的挂载操作完成,文件系统检测也完成后,systemd将该自动挂载点替换为真正的挂载点,并让open()调用返回。由此,实现了那些依赖于文件系统的服务和文件系统本身同时并发启动。
当然对于"/"根目录的依赖实际上一定还是要串行执行,因为systemd 自己也存放在/之下,必须等待系统根目录挂载检查好。
不过对于类似/home 等挂载点,这种并发可以提高系统的启动速度,尤其是当/home是远程的 NFS 节点,或者是加密盘等,需要耗费较长的时间才可以准备就绪的情况下,因为并发启动,这段时间内,系统并不是完全无事可做,而是可以利用这段空余时间做更多的启动进程的事情,总的来说就缩短了系统启动时间。
Systemd 的使用
下面针对技术人员的不同角色来简单地介绍一下 systemd 的使用。本文只打算给出简单的描述,让您对 systemd 的使用有一个大概的理解。具体的细节内容太多,即无法在一篇短文内写全。还需要读者自己去进一步查阅 systemd的文档。
Unit 文件的编写
开发人员开发了一个新的服务程序,比如httpd,就需要为其编写一个配置单元文件以便该服务可以被systemd 管理,类似UpStart 的工作配置文件。在该文件中定义服务启动的命令行语法,以及和其他服务的依赖关系等。
此外我们之前已经了解到,systemd 的功能繁多,不仅用来管理服务,还可以管理挂载点,定义定时任务等。这些工作都是由编辑相应的配置单元文件完成的。我在这里给出几个配置单元文件的例子。
下面是 SSH 服务的配置单元文件,服务配置单元文件以.service为文件名后缀。
[root@kalaguiyin system]# cat/usr/lib/systemd/system/sshd.service
[Unit]
Description=OpenSSH server daemon
After=network.target sshd-keygen.service
Wants=sshd-keygen.service
#[unit]部分,描述信息
[Service]
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s
#[service]定义,ExecStartPre定义启动服务之前应该运行的命令;
#ExecStart 定义启动服务的具体命令行语法。
[Install]
WantedBy=multi-user.target
#[install]部分:WangtedBy表明这个服务是在多用户模式下所需要的。
那我们就来看下multi-user.target 吧:
[root@kalaguiyin system]# catmulti-user.target
[Unit]
Description=Multi-User System
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.service rescue.target
After=basic.target rescue.servicerescue.target
AllowIsolate=yes
# Requires 定义表明 multi-user.target 启动的时候 basic.target 也必须被启动;另外 basic.target 停止的时# 候,multi-user.target 也必须停止。如果您接着查看 basic.target 文件,会发现它又指定了 sysinit.target
# 其他的单元必须随之启动。同样 sysinit.target 也会包含其他的单元。采用这样的层层链接的结构,最终所# 有需要支持多用户模式的组件服务都会被初始化启动好。
[Install]
Alias=default.target
# Alias 定义,即定义本单元的别名,这样在运行 systemctl 的时候就可以使用这个别名来引用本单元。
此外在/etc/systemd/system 目录下还可以看到诸如*.wants 的目录,放在该目录下的配置单元文件等同于在[Unit]小节中的 wants 关键字,即本单元启动时,还需要启动这些单元。比如您可以简单地把您自己写的 foo.service 文件放入 multi-user.target.wants 目录下,这样每次都会被默认启动了。
[root@kalaguiyin system]# pwd
/etc/systemd/system
[root@kalaguiyin system]# ls
basic.target.wants display-manager.service
bluetooth.target.wants getty.target.wants
dbus-org.bluez.service graphical.target.wants
printer.target.wants sockets.target.wants
spice-vdagentd.target.wants default.target sysinit.target.wants default.target.wants
再让我们来看看sys-kernel-debug.mout文件,这个文件定义了一个文件挂载点:
[root@kalaguiyin system]# cat
sys-kernel-debug.mount
[Unit]
Description=Debug File System
Documentation=https://wsw.kernel.org/doc/Documentation/filesystems/debugfs.txt
Documentation=http://wsw.freedesktop.org/wiki/Software/systemd/APIFileSystems
DefaultDependencies=no
ConditionPathExists=/sys/kernel/debug
Before=sysinit.target
[Mount]
What=debugfs
Where=/sys/kernel/debug
Type=debugfs
这个配置单元文件定义了一个挂载点。挂载配置单元文件有一个[Mount]配置小节,里面配置了 What,Where 和Type 三个数据项。这都是挂载命令所必须的,例子中的配置等同于下面这个挂载命令:
mount –t debugfs /sys/kernel/debug debugfs
Systemd系统管理:
systemd 的主要命令行工具是 systemctl。
多数管理员应该都已经非常熟悉系统服务和 init 系统的管理,比如 service、chkconfig以及 telinit 命令的使用。systemd 也完成同样的管理任务,只是命令工具systemctl 的语法有所不同而已。