3. Systemd入门:Unit文件概念、Service Unit详解、Target与运行级别

聊到系统启动,就绕不开Systemd。说实话,我刚接触Linux那会儿,用的还是SysV init那一套,后来Systemd横空出世,我一开始还挺抗拒的。但用久了你会发现,它确实把服务管理这件事做得更清晰了。今天我就带你把它掰开揉碎,好好讲讲。

3.1 Unit文件:Systemd的“积木块”

Systemd把系统里的每一个资源都抽象成了一个“Unit”。你可以把它想象成一块块乐高积木。服务是一个Unit,挂载点是一个Unit,甚至一个定时任务也是一个Unit。

每个Unit都有一个对应的配置文件,我们叫它Unit文件。这些文件通常放在两个地方:

  • /usr/lib/systemd/system/:软件包自带的默认Unit文件,别动它。
  • /etc/systemd/system/:系统管理员自定义或覆盖的Unit文件,优先级更高。

Unit文件的命名规则是 名称.类型。比如 nginx.service、dev-sda.device。类型决定了这个Unit是干什么的。常见的类型有:

Unit类型 文件后缀 作用
Service .service 管理系统服务(最常用)
Socket .socket 管理进程间通信的套接字
Target .target 逻辑分组,类似运行级别
Mount .mount 管理文件系统挂载点
Timer .timer 管理定时任务(替代cron)
我的小习惯: 我一般不会直接修改 /usr/lib/systemd/system/ 下的文件。需要自定义时,我会在 /etc/systemd/system/ 下创建一个同名的文件,或者用 systemctl edit 命令生成覆盖片段。这样升级软件包时,我的配置不会被冲掉。

3.2 Service Unit详解:从启动到守护

Service Unit是我们打交道最多的。一个典型的Service文件长这样:

[Unit]
Description=My Custom Service
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/myapp
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
User=myuser
Group=mygroup

[Install]
WantedBy=multi-user.target

我们来拆解一下。文件分三个部分:

[Unit] 段:描述与依赖

  • Description:服务的简单描述,systemctl status 时会显示。
  • After:指定在哪些Unit之后启动。注意,它不强制依赖,只控制顺序。
  • Requires:强依赖。如果依赖的Unit启动失败,当前Unit也会失败。
  • Wants:弱依赖。依赖的Unit启动失败,不影响当前Unit。

[Service] 段:核心行为

这是最关键的。Type字段决定了Systemd如何判断服务是否启动成功。我遇到过不少坑,都是Type没选对。

Type 含义 适用场景
simple 主进程启动后,Systemd就认为服务已启动 大多数后台服务
forking 主进程会fork一个子进程,父进程退出 传统Unix守护进程(如nginx)
oneshot 执行一次就退出,不会常驻内存 初始化脚本、一次性任务
notify 服务启动后会通过sd_notify()通知Systemd 需要精确控制启动状态的服务
我曾经踩过的坑: 有一次我写了一个服务,Type用了simple,但程序启动后立刻fork了子进程,父进程退出了。Systemd以为服务挂了,就不断重启。后来改成forking,并指定了PIDFile,问题才解决。记住:forking类型必须配合PIDFile使用。

其他关键字段:

  • ExecStart:启动命令。必须写绝对路径。
  • ExecStop:停止命令。如果不写,Systemd会发SIGTERM信号。
  • ExecReload:重载配置的命令。
  • Restart:什么情况下自动重启。可选值:no, on-success, on-failure, on-abnormal, always。
  • RestartSec:重启前等待的秒数。
  • User/Group:以哪个用户身份运行。安全考虑,别用root。

[Install] 段:安装与启用

这个段定义了服务如何被“安装”到启动链中。最常用的是 WantedBy。比如 WantedBy=multi-user.target,意思就是当系统进入multi-user.target时,这个服务会被启动。

你执行 systemctl enable myapp 时,Systemd会在 /etc/systemd/system/multi-user.target.wants/ 目录下创建一个符号链接,指向你的Service文件。这就是“启用”的本质。

3.3 Target与运行级别:从数字到名字

在SysV时代,我们用数字0-6表示运行级别。Systemd用Target替代了它。Target说白了就是一组Unit的集合。系统启动时,会激活一个默认的Target,然后这个Target会拉起来所有它“想要”的Unit。

常见的Target对应关系:

SysV运行级别 Systemd Target 说明
0 poweroff.target 关机
1 rescue.target 单用户模式(救援模式)
2, 3, 4 multi-user.target 多用户文本模式(无图形界面)
5 graphical.target 多用户图形模式
6 reboot.target 重启

你想想看,Target的好处是什么?它不再是简单的数字,而是有意义的名称。而且Target之间可以建立依赖关系。比如 graphical.target 会 Wants multi-user.target,所以进入图形模式前,系统会先进入多用户模式。

查看当前默认的Target:

systemctl get-default

设置默认Target:

systemctl set-default multi-user.target

切换到另一个Target(不重启):

systemctl isolate graphical.target
核心逻辑: 整个Systemd的启动过程,就是从一个Target到另一个Target的“跳转”。每个Target会拉起来一堆Service、Mount、Socket等Unit。你写的Service文件,通过 WantedBy 挂到某个Target上,就融入了这个启动链条。

下面这张图,是我画的一个简化版启动流程,帮你理解Unit、Service和Target之间的关系:

Systemd 启动流程与 Unit 关系图 default.target multi-user.target graphical.target Wants / Requires 关系 sshd.service Type=simple nginx.service Type=forking myapp.service Type=oneshot 每个 Service 通过 [Install] 段的 WantedBy 挂载到 Target 上 systemctl enable 的本质就是在 .target.wants/ 目录下创建符号链接

嗯,到这里你应该明白了。Systemd的Unit文件就是服务的“身份证”,Target就是服务的“集合地”。写好自己的Service文件,挂到合适的Target上,系统启动时它就会自动跑起来。说白了,就是这么回事。