Systemd详解与使用

龚正阳龚正阳 -
Systemd详解与使用
背景不太适合进入docker,需要直接部署在宿主机上面,拥有root权限,操作修改系统,读取系统的一些信息等保证开机启动以及异常重启有一套完善的日志系统支持Before/After的启动机制支持基于cgroup的限制以最低的系统开销实现该目的方便打包为deb包进行安装,参考各种服务的安装,比如apt install nginx docker postgresqldeb包背后的逻辑与实现兼容基本全部的linux发行版本

虽然之前使用过的supervisor也可以满足上述大部分需求,但是打工人的世界总是需要去追求一下极致的,于是决定好好学习一下systemd

Systemd基本介绍内核加载到内存之后启动的第一个用户空间程序,pid是1,是所有进程的父进程,会托管所有僵尸进程,如果这个程序挂了,系统也就直接关机了systemd不是内核的一部分,但是却成为了Debian/Ubuntu系和Fedora/Centos/Redhat/RockyLinux系的系统初始化进程Linux启动流程

systemd是系统启动流程中初始化阶段的主要角色,有必要介绍一下linux系统启动流程

硬件引导启动阶段(第一阶段)机器电源接通时,存储在主板芯片上面的固件初始化,POST(Power On Self Test)上电自检

BIOS阶段

初始化硬件,内存,磁盘,显卡等查找启动介质(一般是硬盘),查找MBR或者EFI分区的第一阶段引导程序,并且移交控制权MBR或者EFI程序阶段,查找第二阶段的GRUB(GRand Unified Boot)引导程序并安装到BootLoderBootLoader 启动引导阶段(第二阶段)stage1: 执行BootLoader的主程序(GRUB程序),开始启动stage1.5stage1.5: 引导文件系统中的stage2stage2: 加载GRUB核心映像

grub.conf/grub.cfg阶段

解析grub.conf/grub.cfg配置文件,加载默认内核镜像和 initrd (初始磁盘镜像,对应于/boot/initrd.img文件)镜像到内存中,当所有镜像准备好后,即跳转到内核镜像,移交系统控制权给内核,接下去进入到内核阶段文件后缀名称在Ubuntu22上面看到的是.cfg,可能其他系统是.conf结尾该配置文件路径是/grub/grub.cfg或者/boot/grub/grub.cfg,路径具体位置取决于系统安装时候是否分割了一个/boot分区,之前装系统的时候/boot分区都是必须分配的内核引导阶段(第三阶段)

/boot/kernel and Kernel parameter

内核读取/boot下面的文件,执行加载

查看/boot分区的内容如下,可以看到内核保存了两个版本的镜像(img-5.15.0-53-genericimg-5.15.0-56-generic),其中新镜像是目前系统正常运行时候加载的镜像,每次apt更新系统的时候如果有新版本内核释放,更新时候会保留一份老版本镜像,等下一次新镜像更新到时候才会删除,一份冗余

$ ll /boot 
总用量 165M
-rw-r--r-- 1 root root 256K 十月   18 02:36 config-5.15.0-53-generic
-rw-r--r-- 1 root root 256K 十一月 22 23:08 config-5.15.0-56-generic
drwx------ 3 root root 4.0K 一月    1  1970 efi
drwxr-xr-x 5 root root 4.0K 十二月  3 09:44 grub
lrwxrwxrwx 1 root root   28 十二月  2 09:45 initrd.img -> initrd.img-5.15.0-56-generic
-rw-r--r-- 1 root root  65M 十二月  1 09:01 initrd.img-5.15.0-53-generic
-rw-r--r-- 1 root root  65M 十二月  4 09:04 initrd.img-5.15.0-56-generic
lrwxrwxrwx 1 root root   28 十二月  2 09:45 initrd.img.old -> initrd.img-5.15.0-53-generic
drwx------ 2 root root  16K 五月   26  2021 lost+found
-rw-r--r-- 1 root root 179K 二月    7  2022 memtest86+.bin
-rw-r--r-- 1 root root 181K 二月    7  2022 memtest86+.elf
-rw-r--r-- 1 root root 181K 二月    7  2022 memtest86+_multiboot.bin
-rw------- 1 root root 6.0M 十月   18 02:36 System.map-5.15.0-53-generic
-rw------- 1 root root 6.0M 十一月 22 23:08 System.map-5.15.0-56-generic
lrwxrwxrwx 1 root root   25 十二月  2 09:45 vmlinuz -> vmlinuz-5.15.0-56-generic
-rw------- 1 root root  12M 十月   18 02:41 vmlinuz-5.15.0-53-generic
-rw------- 1 root root  12M 十一月 23 01:07 vmlinuz-5.15.0-56-generic
lrwxrwxrwx 1 root root   25 十二月  2 09:45 vmlinuz.old -> vmlinuz-5.15.0-53-generic

/boot/initrd

引导initrd解压载入在内存中加载内核使用的root文件系统,执行initrd文件系统中的init程序,完成加载其他驱动模块

执行/sbin/init进程

发现/sbin是指向/usr/sbin的一个软链接

$ ll / |grep bin
lrwxrwxrwx   1 root root    7 五月   26  2021 bin -> usr/bin
lrwxrwxrwx   1 root root    8 五月   26  2021 sbin -> usr/sbin

查看init发现init是指向systemd的一个软链接

$ ll /usr/sbin |grep init
lrwxrwxrwx 1 root root     20 九月   10 02:47 init -> /lib/systemd/systemd
-rwxr-xr-x 1 root root    13K 三月   15  2022 mkinitramfs
lrwxrwxrwx 1 root root     14 九月   10 02:47 telinit -> /bin/systemctl
-rwxr-xr-x 1 root root   6.8K 二月    9  2022 update-initramfs
Systemd初始化阶段(第四阶段)内核启动第一个用户空间应用程序,系统控制权移交给systemd该阶段会加载执行级别,加载服务,启动shell和图形化界面初始化阶段有3个类型,SysV初始化,UpStart(Ubuntu开发的用于替换SysV的初始化程序)和Systemd(由Redhat工程师开发),很多老系统一般都是SysV作为初始化程序,从centos7(大概是2014年)以后的系统大多都是采用SystemdUbuntu15开始使用systemd为什么各大主流linux系统采用systemd改进启动项的效率,避免频繁的进程创建,内核/用户切换,为系统的启动和管理提供一套完整的解决方案利用 Dbus 进程间通讯与 socket 激活机制,解决任务启动时的依赖问题,实现启动并行化实现任务daemons的精确控制,使用内核的 cgroup 机制,不依赖 pid 来追踪进程,进程多次fork 之后生成的进程也不会脱离 systemd 的控制统一任务定义,用户不需要编写shell脚本,而是使用 systemd 制定的 unit 规则systemd有两大设计理念,启动更少的服务,更多地并行启动服务,最终实现用户可以快速进入系统systemd使用C编写,用于替换传统的脚本shell启动,shell中调用系统命令的操作会导致新进程的生成,比如awk, sed, grep等都会生成新进程,产生了很大的开销,systemd的运行开销比SysVUpStart小很多,速度也更快systemd做了什么解决启动项的依赖性Socket依赖:例如服务A依赖服务Bsocket,但是服务B还未启动,所以systemd创建了一个临时socket用于接收服务A的请求与数据,当B真正起来的时候再把临时socket缓存的数据以及描述符替换为BsocketD-Bus(Desktop Bus)依赖:是一个用来在进程间通信的服务,该服务支持Bus Activation特性,即服务A要通过 D-Bus 服务和B通讯,但是B没有启动,那么 D-Bus 可以把B起来,在B启动的过程中,D-Bus 缓存数据,systemd使用这个特性并行启动AB文件系统依赖: 系统启动过程中,systemd 参考了 autofs 的设计思路,使得依赖文件系统的服务和文件系统本身初始化两者可以并发工作,监测到某个文件系统挂载点真正被访问到的时候才触发挂载操作提供了一个系统和服务管理器利用 Linuxcgroups监视进程支持快照和系统恢复维护挂载点和自动挂载点各服务间基于依赖关系进行精密控制基于journald的服务日志管理控制基础系统配置维护登陆用户列表以及系统账户运行时目录和设置运行容器和虚拟机简单的管理网络配置网络时间同步日志转发...

systemd的管控范围已经远超作为系统启动项的角色,可以理解为已经大一统管理linux各项功能了,职责和最初的sysvupstart有所脱离,变得极其复杂庞大,这个也是最初redhat/centos系列和debian/ubuntu系列采用systemd引起的巨大波澜

在此推荐阅读一下itwire于2014发表的 Linus Torvalds关于systemd的访谈文章(Linus保持中立意见,如果这个人没有开喷的话,说明是已经取得巨大的成功了)

https://www.itwire.com/business-it-news/open-source/65402-torvalds-says-he-has-no-strong-opinions-on-systemd

也可以阅读一下systemd的作者Lennert发表的关于PID 1的再思考Rethinking PID 1

http://0pointer.de/blog/projects/systemd.html
systemdUnit单元的文件类型

系统初始化需要很多操作,比如启动后台服务,挂载文件系统,配置网络等,过程中的每一步都被 Systemd 抽象为一个配置单元,即 Unit,具体包含的类型如下

typename作用Service unit.service封装一个后台服务进程,比如sshd,dockerdTarget unit.target将多个单元在逻辑上组合在一起。Device unit.device定义内核识别的设备,在 sysfs(5) 里面作为 udev(7) 设备树展示Socket unit.socket用于标识进程间通信用到的 socket 文件Snapshot unit.snapshot管理系统快照Swap unit.swap用于标识 swap 文件或设备Mount unit.mount封装一个文件系统挂载点(也向后兼容传统的 /etc/fstab 文件)Automount unit.automount封装一个文件系统自动挂载点Path unit.path根据文件系统上特定对象的变化来启动其他服务。Time unit.timer封装一个基于时间触发的动作。取代传统的 crond 等任务计划服务Slice unit*.slice控制特定 CGroup 内所有进程的总体资源占用。Systemd Unit文件的位置与执行优先级

不同发行版本的路径是不同的,当三个目录下面存在同名文件的时候,优先级最高目录下的文件会被优先使用

/usr/lib/systemd/system/lib/systemd : 使用包管理器安装的软件的 systemd unit 的实际配置文件的存放位置,在Ubuntu22上面/lib路径是指向/usr/lib的一个软链接,优先级最低/run/systemd/system:在运行时创建的 systemd unit 文件,该目录优先于已安装服务单元文件的目录/etc/systemd/system: 优先级最高,由 systemctl 命令创建的 systemd unit 文件以及为扩展服务而添加的 unit 文件都将启用,系统管理员安装的单元Systemd Unit文件的组成

主要由三块UnitServiceInstall组成

具体版本或者全部字段解释执行如下命令查看

$ man systemd.unit
$ man systemd.service
$ man systemd.exec
....

或者访问地址https://www.freedesktop.org/software/systemd/man/ 或者第三方中文版http://www.jinbuguo.com/systemd/systemd.index.html

常见字段解释如下

Unit块下面的字段Requires: 强依赖,依赖的其它 Unit 列表,列在其中的 Unit 会在这个Unit启动时的同时被启动,如果其中任意一个Unit启动失败,这个Unit也会启动失败Wants: 弱依赖,与 Requires 相似,但只是在当前 Unit 启动时,触发启动列出的 Unit ,而不考虑这些 Unit 启动是否成功After:与 Requires 相似,该字段列出的所有 Unit 全部启动后,才会启动当前的 UnitBefore: 与 After 相反,当前 Unit 应该在列出的 Unit 之前启动OnFailureUnit 启动失败时,自动启动列出的每个 UnitConflicts: 与这个 Unit 有冲突的模块,如果列出的 Unit 已经在运行时,当前 Unit 不能启动,反之亦然Service块下面的字段EnvironmentFile: 指定当前服务的环境变量定义文件,该文件内部的 key=value 键值对,可以用 $key 的形式,在当前.service文件中引用ExecStart: 定义启动进程时执行的命令

ExecStartPre : 启动当前服务之前执行的命令

ExecStartPost: 启动当前服务之后执行的命令ExecStop: 停止服务时执行的命令

ExecStopPost: 停止服务之后执行的命令

ExecReload : 重启服务时执行的命令

Type : 启动类型

simple: 默认值,执行ExecStart指定的命令,启动主进程forking: 以 fork 方式从父进程创建子进程,创建后父进程会立即退出oneshot: 一次性进程,Systemd 会等当前服务退出,再继续往下执行dbus: 当前服务通过D-Bus启动notify: 当前服务启动完毕,会通知Systemd,再继续往下执行

idle: 若有其他任务执行完毕,当前服务才会运行

WorkingDirectory: 指定服务的工作目录RootDirectory: 指定服务进程的根目录,如果配置了这个参数,服务将无法访问指定目录以外的文件

User: 指定运行服务的用户

Group: 指定运行服务的用户组KillMode: 定义 Systemd 如何停止服务,可选项如下control-group: 默认值,当前控制组里面的所有子进程,都会被杀掉process: 只杀主进程mixed: 主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号none: 没有进程会被杀掉,只是执行服务的 stop 命令install块下面的字段WantedBy 值为一个或多个 Target,当前 Unit 激活 (enable,即开机启动) 时符号链接会放入 /usr/lib/systemd/system 目录下以 <Target>.wants 后缀构成的子目录中,该参数常见的值是multi-user.target,这样,当该target中的任意一个Unit启动的时候,本Unit都会跟着一起启动,这就是配置开机自启动的关键Also 当前 Unit enable/disable 时,同时 enable/disable 的其他 UnitAlias 当前 Unit 可用于启动的别名systemd Unit的配置与使用

最好的样例还是参考操作系统已经有的内容

比如现在要编写一个自启动服务,可以参考docker.servicesshd.service

$ systemctl cat docker
$ systemctl cat sshd

如果要编写一个定时任务,可以参考apt-dayly.timer

$ systemctl cat apt-daily.timer
systemctl使用

管理系统和服务的命令行工具

设备启动,关机等操作,可以看到设备运行操作命令是指向systemctl的软链接

$ ll /usr/sbin |grep system
lrwxrwxrwx 1 root root     14 九月   10 02:47 halt -> /bin/systemctl
lrwxrwxrwx 1 root root     20 九月   10 02:47 init -> /lib/systemd/systemd
lrwxrwxrwx 1 root root     14 九月   10 02:47 poweroff -> /bin/systemctl
lrwxrwxrwx 1 root root     14 九月   10 02:47 reboot -> /bin/systemctl
lrwxrwxrwx 1 root root     14 九月   10 02:47 runlevel -> /bin/systemctl
lrwxrwxrwx 1 root root     14 九月   10 02:47 shutdown -> /bin/systemctl
lrwxrwxrwx 1 root root     14 九月   10 02:47 telinit -> /bin/systemctl

systemd常用操作

动作命令注释 分析系统状态显示系统状态systemctl status列出正在运行的单元systemctl or systemctl list-units列出失败的单元systemctl --failed列出已安装的单元systemctl list-unit-filesShow process status for a PIDsystemctl status {pid}cgroup slice, memory and parent检查单元状态 显示单元的手册页systemctl help {unit}如果单元支持显示单元的状态systemctl status {unit}包括其是否在运行检查单元是否配置为自动启动systemctl is-enabled {unit}启动、重新启动和重新加载单元 立即启动单元systemctl start {unit} 立即停止单元systemctl stop {unit}重新启动单元systemctl restart {unit}重新加载单元及其配置systemctl reload {unit}重新加载 systemd 配置systemctl daemon-reload扫描单元的变动 启用单元 开机自动启用单元systemctl enable {unit} 开机自动启用单元,并立即启动systemctl enable --now {unit}取消开机自动启用单元systemctl disable {unit}重新启用 单元systemctl reenable {unit}先取消启用,再启用 禁用单元禁用单元,使其无法启动systemctl mask {unit}取消禁用单元systemctl unmask {unit}systemd其他常用命令模块journalctl

查询systemd服务的日志信息

$ journalctl
loginctl

systemd的登入管理器,可以查看目前登录到当前计算机的用户信息

$ sudo loginctl         
SESSION  UID USER SEAT  TTY   
      2 1000 gong seat0 tty2
    640 1000 gong       pts/17
    641 1000 gong       pts/19

如果使用ssh {用户名}@localhost再次登录到本地计算机,使用loginctl的时候会看到多了一个session

这个时候可以使用如下命令,指定一个session id 的方式关闭某个用户的访问

$ sudo loginctl kill-session 641
systemd-analyze

分析系统启动过程中的耗时,关闭或者调整某些服务可以优化启动速度

$ sudo systemd-analyze blame
1min 55.462s fstrim.service
     39.372s fwupd-refresh.service
     33.827s apt-daily.service
     31.949s apt-daily-upgrade.service
     16.637s systemd-networkd-wait-online.service
      6.193s plymouth-quit-wait.service
      5.070s docker.service
      1.551s snapd.service
      1.540s netfilter-persistent.service
      1.318s snapd.seeded.service
      1.196s vmware.service
      1.129s freeradius.service
      1.049s gpu-manager.service
      .....
systemd service使用示例

编写一个文件/usr/lib/systemd/system/test-boot.service如下

[Unit]
Description=test boot
# 表示在网络模块启动之后才启动本Unit
After=network.target

[Service]
# 该命令是阻塞的,每隔1秒会打印当前日期
ExecStart=/bin/sh -c "while true; do date; sleep 1; done"
# 配置查询的工作路径,该路径需要存在,否则会报错
WorkingDirectory=/tmp/

[Install]
# 该参数表示此Unit是开机启动时候关联到multi-user.target
# 当multi-user.target下面的任意一个Unit启动都会触发本Unit的启动
# 即enable状态的时候会创建一个链接到/etc/systemd/system/multi-user.target.wants/目录下面
WantedBy=multi-user.target

执行命令如下

$ sudo systemctl daemon-reload

查看当前服务状态

$ sudo systemctl status test-boot
○ test-boot.service - test boot
     Loaded: loaded (/lib/systemd/system/test-boot.service; disabled; vendor preset: enabled)
     Active: inactive (dead)

配置开机启动,注意观察软链接的位置是配置在

$ sudo systemctl enable test-boot
Created symlink /etc/systemd/system/multi-user.target.wants/test-boot.service → /lib/systemd/system/test-boot.service.

接下去启动服务并查看状态

$ sudo systemctl start test-boot
$ sudo systemctl status test-boot
● test-boot.service - test boot
     Loaded: loaded (/lib/systemd/system/test-boot.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2023-03-16 17:32:13 CST; 20s ago
   Main PID: 940646 (sh)
      Tasks: 2 (limit: 38197)
     Memory: 364.0K
     CGroup: /system.slice/test-boot.service
             ├─940646 /bin/sh -c "while true; do date; sleep 1; done"
             └─940825 sleep 1

三月 16 17:32:24 gong sh[940765]: 2023年 03月 16日 星期四 17:32:24 CST
三月 16 17:32:25 gong sh[940769]: 2023年 03月 16日 星期四 17:32:25 CST
三月 16 17:32:26 gong sh[940778]: 2023年 03月 16日 星期四 17:32:26 CST
三月 16 17:32:27 gong sh[940782]: 2023年 03月 16日 星期四 17:32:27 CST
三月 16 17:32:28 gong sh[940786]: 2023年 03月 16日 星期四 17:32:28 CST
三月 16 17:32:29 gong sh[940795]: 2023年 03月 16日 星期四 17:32:29 CST
三月 16 17:32:30 gong sh[940799]: 2023年 03月 16日 星期四 17:32:30 CST
三月 16 17:32:31 gong sh[940803]: 2023年 03月 16日 星期四 17:32:31 CST
三月 16 17:32:32 gong sh[940814]: 2023年 03月 16日 星期四 17:32:32 CST

最后可以重启机器校验一下是否可以开机自启动

参考阅读

Linux 系统启动流程

Linux 的小伙伴 systemd 详解

linuxinit程序发展历史

Linux PID 1Systemd

Docker基础技术:Linux CGroup

archlinuxsystemd wiki文档

itwire于2014发表的 Linus Torvalds关于systemd的访谈

systemd的作者Lennert发表的关于PID 1的再思考

systemdunit配置

特别申明:本文内容来源网络,版权归原作者所有,如有侵权请立即与我们联系(cy198701067573@163.com),我们将及时处理。

Tags 标签

systemdlinux运维

扩展阅读

加个好友,技术交流

1628738909466805.jpg