Linux 初始化之 Systemd机制
systemd是Linux下的一种init软件,由Lennart Poettering带头开发,其开发目标是提供更优秀的框架以表示系统服务间的依赖关系,并依此实现系统初始化时服务的并行启动,同时达到降低Shell的系统开销的效果,最终代替现在常用的System V与BSD风格init程序。传统sysvinit使用inittab来决定运行哪些shell脚本,大量使用shell脚本被认为是效率低下无法并行的原因。systemd使用了Linux专属技术,不再顾及POSIX兼容.
设计理念
与多数发行版使用的System V风格init相比,systemd采用了以下新技术:
采用Socket激活式与D-Bus激活式服务,以提高相互依赖的各服务的并行运行性能;
用cgroups代替PID来追踪进程,以此即使是两次fork之后生成的守护进程也不会脱离systemd的控制。
从设计构思上说,由于systemd使用了cgroup与fanotify等组件以实现其特性,所以只适用于Linux。有鉴于此,考虑到kFreeBSD分支的软件源无法纳入systemd,为与其他分支保持一致,Debian开发者尽力避免纳入systemd。
应用范围
systemd已纳入众多Linux发行版的软件源中,以下简表:
默认init程序为systemd的发行版
Fedora 15及后续版本
Mageia 2[10]
Mandriva 2011[11]
openSUSE 12.1及后续版本[12]
Arch Linux在2012年10月13日将systemd-sysvcompat纳入base软件组,自此Arch Linux默认安装完即以systemd为init程序[13],同时也提供了与Arch自带启动脚本兼容用的systemd启动脚本包以方便用户,使用户能“开箱即用”[14]
Chakra GNU/Linux,在2012.10的光盘镜像文件发布后默认使用systemd。[15]
可以使用systemd的发行版
Debian GNU/Linux,于“testing”分支源中提供[16],并在2014年的技术委员会的init系统投票中决定在Debian 8 “Jessie”中将以Linux为核心的版本转换到systemd[17] 。
Gentoo,同Openrc一起被Gentoo官方支持[18][19][20]
除此以外,systemd已由Lennart Poettering提请纳入GNOME 3.2的外部依赖关系列表[21],而这意味着所有使用GNOME的发行版都应该使用systemd,最低限度来说也必须将其作为配置选项之一。
一些其他的发行版也把它包含进来,作为 upstart 和 sysvinit 的替代品。
systemd 特点
systemd 开启和监督整个系统是基于 unit 的概念。unit 是由一个与配置文件对应的名字和类型组成的(例如:avahi.service unit 有一个具有相同名字的配置文件,是守护进程 Avahi 的一个封装单元)。unit 有以下几种类型:
service :守护进程的启动、停止、重启和重载是此类 unit 中最为明显的几个类型。
socket :此类 unit 封装系统和互联网中的一个 socket 。当下,systemd 支持流式、数据报和连续包的 AF_INET、AF_INET6、AF_UNIX socket 。也支持传统的 FIFOs 传输模式。每一个 socket unit 都有一个相应的服务 unit 。相应的服务在第一个“连接”进入 socket 或 FIFO 时就会启动(例如:nscd.socket 在有新连接后便启动 nscd.service)。
device :此类 unit 封装一个存在于 Linux 设备树中的设备。每一个使用 udev 规则标记的设备都将会在 systemd 中作为一个设备 unit 出现。udev 的属性设置可以作为配置设备 unit 依赖关系的配置源。
mount :此类 unit 封装系统结构层次中的一个挂载点。
automount :此类 unit 封装系统结构层次中的一个自挂载点。每一个自挂载 unit 对应一个已挂载的挂载 unit (需要在自挂载目录可以存取的情况下尽早挂载)。
target :此类 unit 为其他 unit 进行逻辑分组。它们本身实际上并不做什么,只是引用其他 unit 而已。这样便可以对 unit 做一个统一的控制。(例如:multi-user.target 相当于在传统使用 SysV 的系统中运行级别5);bluetooth.target 只有在蓝牙适配器可用的情况下才调用与蓝牙相关的服务,如:bluetooth 守护进程、obex 守护进程等)
snapshot :与 target unit 相似,快照本身不做什么,唯一的目的就是引用其他 unit 。
systemd 的工具
- systemctl :用作内省和控制 systemd 系统和服务管理器的状态。
- systemd-cgls:以树形递归显示选中的 Linux 控制组结构层次。
- systemadm:一个 systemd 系统和服务管理器的图形化前端。是 systemd-gtk 软件包的一部分。这还只是前期版本,尚需完善。除非你是一个开发者,否则请不要使用它。
systemd 包含了自己的配置和诊断工具,在使用它处理系统启动问题时用到的技巧不同于 sysvinit。由于它与 upstart 和 sysvinit 的兼容特性,我们在使用这两个初始化工具的发行版里面熟悉的命令与技巧也适用于 systemd。
一 systemctl 命令
检视和控制systemd的主要命令是systemctl。该命令可用于查看系统状态和管理系统及服务。详见man 1 systemctl。该工具在改变配置文件或重新启动后台程序时需要 root 权限,但即使是非 root 用户也能下达一些诊断的命令。如果你在启动该命令时不加任何参数,你会看到一个系统启动时执行任务的“单位(unit)”列表,包括挂载及检测磁盘、启动后台服务及配置硬件。
1)输出激活的单元:
$ systemctl
以下命令等效:
$ systemctl list-units
输出运行失败的单元:
$ systemctl --failed
所有可用的单元文件存放在 /usr/lib/systemd/system/ 和 /etc/systemd/system/ 目录(后者优先级更高)。查看所有已安装服务:
$ systemctl list-unit-files
2)使用单元
一个单元配置文件可以描述如下内容之一:系统服务(.service)、挂载点(.mount)、sockets(.sockets 、系统设备、交换分区/文件、启动目标(target)、文件系统路径、由 systemd 管理的计时器。详情参阅 man 5 systemd.unit.
使用 systemctl 控制单元时,通常需要使用单元文件的全名,包括扩展名(例如 sshd.service)。但是有些单元可以在systemctl中使用简写方式。
- 如果无扩展名,systemctl 默认把扩展名当作 .service。例如 netcfg 和 netcfg.service 是等价的。
- 挂载点会自动转化为相应的 .mount 单元。例如 /home 等价于 home.mount。
- 设备会自动转化为相应的 .device 单元,所以 /dev/sda2 等价于 dev-sda2.device。
立即激活单元:
# systemctl start <单元>
立即停止单元:
# systemctl stop <单元>
重启单元:
# systemctl restart <单元>
命令单元重新读取配置:
# systemctl reload <单元>
输出单元运行状态:
$ systemctl status <单元>
检查单元是否配置为自动启动:
$ systemctl is-enabled <单元>
开机自动激活单元:
# systemctl enable <单元>
注意: 如果服务没有Install段落,一般意味着应该通过其它服务自动调用它们。如果真的需要手动安装,可以直接连接服务,如下(将foo替换为真实的服务名):
# ln -s /usr/lib/systemd/system/foo.service /etc/systemd/system/graphical.target.wants/
取消开机自动激活单元:
# systemctl disable <单元>
显示单元的手册页(必须由单元文件提供):
# systemctl help <单元>
重新载入 systemd,扫描新的或有变动的单元:
# systemctl daemon-reload
3)服务
服务(service)单位是最重要的一类单位之一,因为它们管理着后台服务,而在使用 sysvinit 的发行版里面则一般使用初始化脚本来启动这些服务。挂载(mount)与自动挂载(automount)单位用来挂载文件系统。
套接字(socket)单位用来创建套接字,并在访问套接字后,立即利用依赖关系间接地启动另一单位。你可以使用参数让 systemctl 只列出某个类型的单位,如所有的服务单位:systemctl --type=service
systemd 自动将其输出结果递交给 less 显示;你不仅可以使用箭头键来上下滚动,也可以向右滚动,因为有时更多的信息会偶尔“藏”到那里。
列表中的第一栏是单位的名字,
第二栏则表示该单位的定义是否已由 systemd 正确加载。
第三栏则告诉我们该单位是否正在运行。如果你使用了 -a 参数,那么该程序将仅显示非正在运行的单位,即已安装但并未在启动时使用的单位,同时也包含引导系统未能正常加载的单位文件(原因很可能为该单位文件出现错误)。
第四栏则给出了当前状态:“exited”表示该进程已经无任何错误地完成,这种情况适用于一诸如进程在启动后并不在后台继续运行的情况,例如,在系统启动时由于考虑到兼容性因素执行在 sysvinit 里面常用的 /etc/rc.d/rc.local 文件的服务单位。“Running”表示正在后台运行的服务,如 cron、dbus、sshd 和 udev。
第五栏是对该单位的描述。标有“LSB”或“SYSV”的单位已由 systemd 自动创建以管理传统启动脚本。
不能启动或启动后崩溃的服务在第四栏中用红色标为“failed”(如果终端可以显示彩色)。你可以如下命令来察看该服务是何时崩溃的以及在服务程序结束后提供了什么错误代码:
systemctl status ntpd.service
对于一个新安装的 Linux Deepin 12.06,systemctl 会列出约50个服务型单位,包含文本终端的登陆进程(agetty)。因为 systemd 不同于 sysvinit, 它会像管理普通的后台服务一样以服务单位的形式对这些进程进行管理。
4)单位文件与目标(target)
一单位的处理
创建单位用的系统配置文件位于 /lib/systemd/system/,但 /etc/systemd/system 目录下的同名文件会优先于前者。
单位文件的定义通常比传统的 sysvinit 脚本要短得多。例如,用于通过 NTP 来同步网络时间的服务只有短短几行:
[Unit]
Description=Network Time Service
[Service]
ExecStart=/usr/bin/ntpd -n -u ntp:ntp -g
[Install]
WantedBy=multi-user.target
1) 所有的单位文件都包含由[Unit]开头的一节,其中包含一般设置与简
2) [Service]一节含有针对该服务要进行的任务的指定设置——对于 NTP 来说,仅需要启动该服务的命令行。如果需要用一个指定的命令来终止程序,你可以用 ExecStop= 来进行设置。这一步对于 NTP 守护进程是不需要的,因为根据 Unix 传统,它可以用一个简单的“SIGTERM”信号来结束。如果没有指定其他命令,这个命令会告诉 systemd 结束任务。
3) [Install]一节包含了 systemd 在(反)安装时要解释的说明;这里的 NTP 一例中,其内容意为在“多用户”目标激活时应当同步时间。
二目标
“目标”单位的概念与 sysvinit 的运行级别相似;实际上,为考虑兼容性,systemd 甚至能够识别与目标对应的运行级别名称。所以,你可以在引导装载程序中的 kernel 一行中加入 single 这个参数;systemd 就会激活 rescue.target,提供一个相当于单用户模式的最小化界面。
在 systemd 中,多用户模式(即不使用图形化登陆界面就完全启动系统的模式)由 multi-user.target 表示,可以通过下面这个链接来将其设为默认启动目标:
ln -sf /lib/systemd/system/multi-user.target /etc/systemd/system/default.target
如果此后你确实需要默认启动图形化登陆界面,可用同样的方式来将 graphical.target 设为默认目标。这等同于传统初始化工具的运行级别 5。你也可以在引导装载程序中为 kernel 指定想要启动的目标单位:
systemd.unit=multi-user.target
如果想要在操作过程中激活一个不同的目标单位,你可以使用 systemctl 的isolate 命令(需要 root 权限):
systemctl isolate rescue.target
切换为 rescue 目标对于管理任务来说很有用,systemd 这时会停止所有的用户登陆与后台服务,只有系统服务在运行,如监视逻辑卷的服务(lvm2-monitor)。有时,甚至这些服务也需要停止并重新安装,这时你可以使用 emergency.target 来进入紧急模式(emergency mode),这时只有命令提示符的进程以及内核线程在运行。
三 systemctl命令取代了rc.d命令
开机模块加载
/etc/modules-load.d/.conf,相当于原rc.conf中的MODULES变量
# Load virtio-net.ko at boot virtio-net
virtio-net
模块黑名单仍在/etc/modprobe.d/下,如blacklist.conf:
blacklist badmod.ko
Locale
/etc/locale.conf,相当于原rc.conf中的LOCALE
LANG=en_US.UTF-8 LC_COLLATE=C
LC_COLLATE=C
日志服务
systemd自带日志服务,参考systemd Journal
sudo journalctl
可以删除syslog-ng了
主机名
/etc/hostname,相当于原来rc.conf中的HOSTNAME变量
myhostname
网络
sudo systemctl enable NetworkManager.service
不象rc.conf有专门的配置简单网络的地方,还是用NetworkManager、wicd之类的工具吧
如果你坚持使用简单静态配置,可以参考[SOLVED] static ethernet setup under systemd?
运行级别
四systemd用target替代了runlevel的概念,提供了更大的灵活性,如你可以继承一个已有的target,并添加其它服务,来创建自己的target
sudo systemctl list-units --type=target #查询当前target
sudo systemctl isolate graphical.target #改变当前target,重启无效
sudo systemctl enable multi-user.target #改变启动时默认target
sudo systemctl enable kdm.service #graphical是默认target,指定使用的display manager
优化
systemd有自己的”e4rat”
sudo systemctl enable systemd-readahead-collect.service sudo systemctl enable systemd-readahead-replay.service
/etc/fstab,修改/home分区options,检查/home分区时并行启动其它服务
defaults,noauto,x-systemd.automount
其他
sudo systemctl reboot #systemctl还有系统关机、重启、挂起等功能 sudo systemctl suspend
五自己编写 .service 文件
systemd 的单元文件是受 XDG Desktop Entry .desktop 文件启发而产生,而最初起源是 Windows 下的 .ini 文件。
示例参见:Systemd/Services。
处理依赖关系
使用systemd时,可通过正确编写单元配置文件来解决其依赖关系。典型的情况是,单元A要求单元B在A启动之前运行。在此情况下,向单元A配置文件中的 [Unit] 段添加 Requires=B 和 After=B 即可。若此依赖关系是可选的,可添加 Wants=B 和 After=B。请注意 Wants= 和 Requires= 并不意味着 After=,即如果 After= 选项没有制定,这两个单元将被并行启动。
依赖关系通常被用在服务(service)而不是目标(target)上。例如, network.target 一般会被某个配置网络接口的服务引入,所以,将自定义的单元排在该服务之后即可,因为 network.target 已经启动。
启动方式
编写自定义的 service 文件时,可以选择几种不同的服务启动方式。启动方式可通过配置文件 [Service] 段中的 Type= 参数进行设置。具体的参数说明请参阅 man systemd.service 。
- Type=simple(默认值):systemd认为该服务将立即启动。服务进程不会fork。如果该服务要启动其他服务,不要使用此类型启动,除非该服务是socket激活型。
- Type=forking:systemd认为当该服务进程fork,且父进程退出后服务启动成功。对于常规的守护进程(daemon),除非你确定此启动方式无法满足需求,使用此类型启动即可。使用此启动类型应同时指定 PIDFile=,以便systemd能够跟踪服务的主进程。
- Type=oneshot:这一选项适用于只执行一项任务、随后立即退出的服务。可能需要同时设置 RemainAfterExit=yes 使得 systemd 在服务进程退出之后仍然认为服务处于激活状态。
- Type=notify:与 Type=simple 相同,但约定服务会在就绪后向 systemd 发送一个信号。这一通知的实现由 libsystemd-daemon.so 提供。
- Type=dbus:若以此方式启动,当指定的 BusName 出现在DBus系统总线上时,systemd认为服务就绪。
修改现存单元文件
要更改由软件包提供的单元文件,先创建名为 /etc/systemd/system/<单元名>.d/ 的目录(如 /etc/systemd/system/httpd.service.d/),然后放入 *.conf 文件,其中可以添加或重置参数。这里设置的参数优先级高于原来的单元文件。例如,如果想添加一个额外的依赖,创建这么一个文件即可:
/etc/systemd/system/<unit>.d/customdependency.conf
[Unit]
Requires=<新依赖>
After=<新依赖>
然后运行以下命令使更改生效:
# systemctl daemon-reload
# systemctl restart <单元>
此外,把旧的单元文件从 /usr/lib/systemd/system/ 复制到 /etc/systemd/system/,然后进行修改,也可以达到同样效果。在 /etc/systemd/system/ 目录中的单元文件的优先级总是高于 /usr/lib/systemd/system/ 目录中的同名单元文件。注意,当 /usr/lib/ 中的单元文件因软件包升级变更时,/etc/ 中自定义的单元文件不会同步更新。此外,你还得执行 systemctl reenable <unit>,手动重新启用该单元。因此,建议使用前面一种利用 *.conf 的方法。
小贴士: 可以用 systemd-delta 命令来查看哪些单元文件被覆盖、哪些被修改。
eg:slock
Locks the system with the help of slock. Very handy when closing the laptop lid for example.
/etc/systemd/system/screenlock.service
[Unit]
Description=Lock X session using slock
Before=sleep.target
[Service]
User=<username>
Environment=DISPLAY=:0
ExecStart=/usr/bin/slock
[Install]
WantedBy=sleep.target
如何定制或增加一个自定义 unit 文件?
unit 文件在 /etc/systemd/system 下的优先级要高于 /lib/systemd/system 下的。按照个人的需求从后者移动到前者并进行自定义修改。
如果一行以 .include 开始,后接文件名,那么该文件在此时被解析为特殊文件。请确保包含的文件在指令前有适当的章节头信息。
如果可能的话,你应当使用 .include 声明 unit 文件而不是在 /lib/systemd/system 下复制整个 unit 文件到 /etc/systemd/system 目录下。这样你才可以在将来升级软件包时正确地升级未改变的指令。
在使用 .include 和指令时需要小心,因为它可以有多次定义(像 EnvironmentFile= 一样)。由于我们只能添加新指令而不能删除已定义的指令,此时,我们就必须从 /lib/systemd/system复制整个文件到 /etc/systemd/system 中去。
假设我们有一个 lighttpd 服务,我们现在想降低它的 niceness 值。我们需要做的就只是添加 Nice=-5 到 lighttpd.service 文件中。我们可以通过复制整个文件/lib/systemd/system/lighttpd.service 到 /etc/systemd/system/lighttpd.service 或者在 /etc/systemd/system/lighttpd.service 中创建如下文件做到
.include /lib/systemd/system/lighttpd.service
[Service]
Nice=-5
不要忘记在编辑一个 unit 文件后使用 systemdctl daemon-reload 重载 systemd 守护进程。
检测到 Linux 所使用的虚拟化平台类型。
方法一:dmidecode
要检测 Linux 底层的虚拟化类型首选的就是 dmidecode 命令,它最初设计来显示系统 BIOS 和硬件组件的相关信息。使用如下命令便可以检测相关虚拟化信息:
sudo dmidecode -s system-manufacturer
系统极客网站运行在 Microsoft Azure 平台上,所以检测出来是微软的 Hyper-V。如果你的系统运行在物理服务器上,输入的将是硬件制造商的实际名称(如 Dell Inc.)。如果你的 Linux 是运行在虚拟化平台中,则会显示所使用的虚拟化技术相关名称,如 「Microsoft Corporation」「QEMU」「Xen」「VirtualBox」「VMware, Inc」等等。
注意:该方法不适用于基于容器的虚拟化技术。
方法二:systemd
对于使用 systemd 的 Linux 系统,可以使用 systemd-detect-virt 命令来进行检测,该命令目前可以同时检测到基于 hypervisor 的虚拟化技术(例如 KVM、QEMU、VMware、Xen、Oracle VM、VirtualBox、UML)和基于容器的虚拟化技术(例如 LXC、Docker、OpenVZ)。
systemd-detect-virt
注意:在物理服务器上使用该命令会输出「none」。
方法三:virt-what
我们介绍的最后一种检测 Linux 所使用虚拟化类型的方法是 virt-what 命令,virt-what 实际上是一个 Shell 脚本。它通过各种启发式方法来识别虚拟化环境类型,可以检测出 QEMU/KVM、VMware、Hyper-V、VirtualBox、OpenVZ/Virtuozzo、Xen、LXC、IBM PowerVM 以及 Parallels 等平台类型。
在使用之前,大家需要先通过 apt-get 或 yum 安装 virt-what,再执行如下命令进行检测:
sudo virt-what