Systemed守护进程

介绍

systemd是linux系统中的一个初始化系统和服务管理器,它负责在系统启动时启动服务、监听系统事件以及管理系统的运行等。它的主要优点包括并行启动服务、按需启动进程、减少系统资源消耗等。

自定义一个systemd服务

创建服务单元文件

如果我们想自定义一个systemd服务,需要创建一个服务单元文件,该文件定义了服务的属性和行为。

服务单元文件通常存储在两个位置:

  • /etc/systemd/system/:用于系统级别的服务
  • ~/.config/systemd/user/:用于用户级别的服务,这个目录在用户主目录中。

我们也可以在其他目录创建服务单元文件,然后在/etc/systemd/system/目录中创建该服务单元文件的符号链接,比如,/home/abc/Service/a.service

1
ln -s /home/abc/Service/a.service /etc/systemd/system/a.service

用户级别的服务单元通常在以下情况下进行服务:

  1. 用户登录时:当用户登录到系统时,systemd会自动启动该用户的所有已启用的用户级别服务

  2. 手动启动:拥有root权限的用户可以手动启动其他用户级别的服务,使用命令:

    1
    sudo systemctl start user@service_name
  3. 通过其他服务或目标触发:用户级别的服务也可以配置为在特定目标(target)启动后自动运行,或者由其他服务或事件触发

创建的服务单元的名字后缀通常是.service

其他常见的单元文件类型包括:

  • .socket:定义套接字单元,用于监听网络套接字或UNIX域套接字
  • .device:定义设备单元,用于管理设备
  • .mount:定义挂载单元,用于管理文件系统挂载点
  • .automount:定义自动挂载单元,用于自动挂载文件系统
  • .swap:定义交换分区单元
  • .target:定义目标单元,用于组织系统状态
  • .path:定义路径单元,用于监事文件系统路径
  • .timer:定义定时器单元,用于定时执行任务

.service文件的几个部分

Unit

用法

1
2
3
4
5
6
7
[Unit]
Description=Explain The Service
Documention=
Before=a.service
After=b.target
Wants=c.service
Requires=d.service
  • Description:对该服务的描述
  • Documention:说明文档
  • Before:在a.service启动之前,启动本服务
  • After:在b.target启动之后,启动本服务
  • Wants:弱依赖于c.service,即使被依赖服务启动失败或停止,本服务仍然运行
  • Requires:强依赖于d.service,如果被依赖服务启动失败或停止,本服务也会停止

配置项可选可不选

启动顺序以依赖关系无关,被依赖的服务与本服务可以同时启动

WantsRequires区别

  1. Wants
    • Wants表示一种弱依赖关系,意味着如果列出的单元启动了,那么当前单元也会被启动,但如果没有启动,也不会影响当前单元的启动。
    • 当系统启动或某个目标(target)被激活时,systemd会尝试启动所有被该目标Wants的单元。因此,Wants可以确保在特定条件下,某些服务会尝试启动,从而间接影响启动顺序。
  2. Requires
    • Requires表示一种强依赖关系,意味着如果列出的单元没有启动,那么当前单元也会失败。
    • 由于Requires定义了强依赖,systemd在启动当前单元时,会尝试启动所有被Requires的单元。如果这些依赖单元无法启动,当前单元也不会启动。

示例:

假设有两个服务单元:serviceA.service和serviceB.service。

  • 如果serviceA.service在Unit部分有Requires=serviceB.service,那么在启动serviceA.service时,systemd会同时启动serviceB.service。如果serviceB.service启动失败,serviceA.service也不会启动。如果停止serviceB.service,serviceA.service也会停止
  • 如果serviceA.service在Unit部分有Wants=serviceB.service,那么在系统启动或相关目标被激活时,systemd会尝试启动serviceB.service,但serviceA.service的启动不会因为serviceB.service的失败而受阻。如果停止serviceB.service,serviceA.service并不会停止

WantsRequiresAfterBefore区别

  • BeforeAfter直接指定了单元之间的启动顺序,即一个单元应该在另一个单元之前或之后启动。这些指令明确告诉systemd如何排序单元的启动顺序。BeforeAfter并不会在该服务启动之前或之后去启动其他服务。而是在几个服务需要同时启动时,会根据启动顺序先后启动这几个服务。比如,a.service中有After=b.service,在启动a.service时,systemd并不会尝试先去启动b.service,而是直接启动a.service。当系统开机时,a.serviceb.service均需要启动,那么systemd则会优先启动b.service,然后再去启动a.service,并且b.service无论是否启动成功都不影响a.service的启动
  • WantsRequires并不规定启动顺序,只是强调依赖关系,并且会在启动该服务时根据依赖关系去启动其他服务。
  • a.service中有Requires=b.service时,当我们启动a.service,systemd会同时启动b.service,如果b.service启动失败,a.service也不会启动。当a.serviceb.service均启动后,如果停止b.service,根据依赖关系,a.service也会自动停止
  • a.service中有Wants=b.service时,当我们启动a.service,systemd会同时启动b.service,如果b.service启动失败,a.service会继续启动。当a.serviceb.service均启动后,如果停止b.service,因为a.serviceb.service是弱依赖关系,a.service并不会被停止

比如:

  1. a.service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [Unit]
    Description=App a
    After=b.service

    [Service]
    User=root
    ExecStart=python /root/a.py
    Restart=on-failure
    RestartSec=60s

    [Install]
    WantedBy=multi-user.target

    b.service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [Unit]
    Description=App b

    [Service]
    User=root
    ExecStart=python /root/b.py
    Restart=on-failure
    RestartSec=60s

    [Install]
    WantedBy=multi-user.target

    其中,

    a.py

    1
    2
    3
    4
    5
    import time
    while True:
    with open("/root/test.txt", "a+") as f:
    f.write("a success\n")
    time.sleep(600)

    b.py

    1
    2
    3
    4
    5
    import time
    while True:
    with open("/root/test.txt", "a+") as f:
    f.write("b success\n")
    time.sleep(600)

    运行a.servicetest.txt中的结果只有a success

  2. a.service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [Unit]
    Description=App a
    Requires=b.service

    [Service]
    User=root
    ExecStart=python /root/a.py
    Restart=on-failure
    RestartSec=60s

    [Install]
    WantedBy=multi-user.target

    b.service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [Unit]
    Description=App b

    [Service]
    User=root
    ExecStart=python /root/b.py
    Restart=on-failure
    RestartSec=60s

    [Install]
    WantedBy=multi-user.target

    运行a.servicetest.txt中的结果:

    1
    2
    a success
    b success
  3. a.service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [Unit]
    Description=App a
    Requires=b.service
    After=b.service

    [Service]
    User=root
    ExecStart=python /root/a.py
    Restart=on-failure
    RestartSec=60s

    [Install]
    WantedBy=multi-user.target

    b.service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [Unit]
    Description=App b

    [Service]
    User=root
    ExecStart=python /root/b.py
    Restart=on-failure
    RestartSec=60s

    [Install]
    WantedBy=multi-user.target

    运行a.servicetest.txt中的结果:

    1
    2
    b success
    a success

Service

服务具体执行的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
[Service]
User=root
Group=
EnvironmentFile=
ExecStart=
ExecStop=
ExecReload=
ExecStartPost=
Type=simple
KillMode=process
Restart=on-failure
RestartSec=60s
WorkingDirectory=
  • User:指定服务运行的用户
  • Group:指定服务运行的用户组
  • EnvironmentFile:指定环境变量文件
  • ExecStart:指定服务的启动命令(服务启动时执行的命令)
  • ExecStop:服务器停止时执行的命令
  • ExecReload:重启当前服务时执行的命令
  • ExecStartPost:启动当前服务之后执行的命令
  • ExecStopPost:停止当前服务之后执行的命令
  • ExecStartPre:启动当前服务之前执行的命令
  • Type:服务启动类型
    • simple:默认类型,表示ExecStart为主进程
    • notify:类似于simple,启动结束后会发出通知信号
    • forking:以fock方式从父进程创建子进程,创建后父进程会立即退出
    • oneshot:一次性进程,Systemd会等当前服务退出,再往下执行
    • dbus:当前服务只通过D-Bus启动
    • idle:若有其他任务,等其他任务执行完毕,当前服务才会运行
  • KillMode:定义服务停止时如何杀死服务进程
    • control-group:默认,停止时杀死所有子进程
    • process:只杀死主进程
    • none:只停止服务,不杀死进程
  • Restart:服务在失败时重启的方式
    • no:默认,不重启
    • on-success:正常退出时重启
    • on-failure:非正常退出时重启
    • always:不论什么原因退出,均重启
  • RestartSec:指定重启服务前等待的时间
  • WorkingDirectory:指定服务的工作目录
  • TimeoutStartSec:指定服务启动超时时间,超过对应时间没有启动成功,则视为失败
  • TimeoutStopSec:指定服务停止超时时间,超过对应时间服务还没有停止,则强制杀死进程
  • SuccessExitStatus:定义哪些退出状态码被视为成功,比如SuccessExitStatus=0 2表示退出状态码0和2都视为成功
  • RestartPreventExitStatus:定义哪些退出状态码会阻止服务重启,比如RestartPreventExitStatus=255表示退出状态码255时不会重启
  • NonBlocking:指定ExecStart执行的非阻塞模式,比如NonBlocking=true
  • Nice:用于调整进程的优先级,比如Nice=10
  • MemoryLimit:设置服务的内存使用限制,比如MemoryLimit=500M
  • LimitNPROC:限制服务可以创建的最大进程数,比如LimitNPROC=10000
  • LimitNOFILE:限制服务进程可以打开的最大文件描述符数,防止服务进程打开过多的文件,比如LimitNOFILE=1000000

Install

用法

1
2
3
4
5
6
[Install]
WantedBy=multi-user.target
RequiredBy=
Alias=
Also=
DefaultInstance=
  • WantedBy:指定服务应该被哪些目标(target)所依赖。当这些目标启动时,该服务也会被启动。比如:WantedBy=multi-user.target表示服务在多用户目标启动时自动启动
  • RequiredBy:与WantedBy类似,但表示强依赖关系。如果该服务失败,依赖该服务的目标(target)也会失败。比如:RequiredBy=graphical.target表示图形界面目标强依赖于该服务
  • Alias:为服务定义别名,可以通过别名来管理服务。比如:Alias=myapp.service表示可以通过myapp.service来管理该服务
  • Also:指定在启用当前服务时,还应该启用其他服务或单元。比如:Also=myotherapp.service表示在启用当前服务时,也会启用myotherapp.service
  • DefaultInstance:用于定义模板服务的默认实例。模板服务可以生成多个实例,DefaultInstance指定默认的实例名称

在systemd中,模板服务是一种特殊的服务,它可以生成多个实例,每个实例都有自己独立的配置和运行环境。DefaultInstance选项用于指定在启用模板服务时,如果没有明确指出实例名称,应该使用哪个默认的实例

模板服务的配置文件通常包含占位符,这些占位符在生成实例时会被具体的值替换。

通常模板服务的文件名会包含一个@符号,如myapp@.service,其中@后面的部分会被实例名替换。

比如创建myapp@.service

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=My App Service

[Service]
User=root
Type=simple
ExecStart=/usr/bin/myapp --instance=%i
Restart=on-failure
RestartSec=60s

[Install]
WantedBy=multi-user.target
DefaultInstance=default

在这个配置中,%i是一个占位符,会被实例名称所替换。DefaultInstance=default表示:如果用户启用myapp@.service,而没有指定实例名称,系统会自动使用default作为实例名称

指定一个特定实例:

1
sudo systemctl enable my@instance1.service

这条命令会创建一个指向my@instance1.service的符号链接,表示系统启动时自动启用名为instance1的实例

Unit的区别

[Unit]中的WantsRequires定义的是当前单元对其他单元的依赖

[Install]中的WantedByRequiredBy定义的是其他单元或目标对当前单元的依赖

a.serviceWantedBy=b.service,在systemctl enable a.service后,等价于b.service中有Wants=a.servcie

a.serviceRequiredBy=b.service,在systemctl enable a.service后,等价于b.service中有Requires=a.service

WantedByRequiredBy需要对该服务使用systemctl enable命令后才会起作用,比如:

对于a.service

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=App a

[Service]
User=root
ExecStart=
Restart=on-failure
RestartSec=60s

[Install]
WantedBy=b.service

对于b.service

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=App b

[Service]
User=root
ExecStart=
Restart=on-failure
RestartSec=60s

[Install]
WantedBy=multi-user.target

a.service使用命令:

1
sudo systemctl enable a

系统输出:Created symlink /etc/systemd/system/b.service.wants/a.service → /etc/systemd/system/a.service.

systemd在/etc/systemd/system/b.service.wants/目录下创建了一个指向a.service的符号链接。这样,当b.service启动时,systemd会同时启动a.service,但即使a.service启动失败,也不会影响b.service的启动。当停止b.service时,a.service也会停止

b.service使用命令:

1
sudo systemctl enable b

系统输出:Created symlink /etc/systemd/system/multi-user.target.wants/b.service → /etc/systemd/system/b.service.

同理,如果是RequiredBy=b.service,则会在/etc/systemd/system/b.service.requires/目录下创建一个指向a.service的符号链接

设置开机自启动

如果我们希望该服务能够在系统开机的时候自动启动,一般的做法是设置该服务的[Install]部分:

1
2
[Install]
WantedBy=multi-user.target

multi-user.target是systemd中的一个目标(target),它代表了一个系统运行级别,类似于传统的 UNIX 系统中的运行级别 3。这个目标意味着系统已经启动了所有的基本服务,并且可以支持多用户登录,但没有启动图形界面。

如果我们希望该服务开机自动自动,但是需要在图形界面启动之后再启动,可以设置为:

1
2
[Install]
WantedBy=graphical.target

graphical.target 是 systemd 中的一个目标,它代表了一个系统运行级别,类似于传统的 UNIX 系统中的运行级别 5,即完整的图形用户界面(GUI)模式。

占位符

systemd支持多个占位符:

  • %i:表示实例名称,具体用法参照上面举例
  • %n:表示单元名称
  • %p:表示服务的_prefix_,即单元名称中@之前的部分
  • %t:表示当前的时间戳
  • %u:表示当前用户的用户名
  • %U:表示当前用户的UID
  • %h:表示主机名
  • %H:表示主机的硬件地址
  • %m:表示主机的机器ID
  • %b:表示boot ID
  • %s:表示服务的状态

这些占位符可以在服务文件的不同部分使用,以实现更灵活的配置和管理。例如,我们可以在服务文件的ExecStart路径中使用%i来指定不同的配置文件或日志文件路径,以便每个实例都有自己的配置或日志

举例:

  1. %i:假设有一个模板服务文件myapp@.service,我们可以为不同的用户创建实例,如myapp@alice.servicemyapp@bob.service,在服务文件中,我们可以使用%i来引用实例名称:

    1
    2
    [Service]
    ExecStart=/usr/bin/myapp --user=%i

    当启动myapp@alice.service时,%i会被替换为alice,命令变为/usr/bin/myapp --user=alice

  2. %n:如果服务单元名为myapp.service,在服务文件中可以使用%n来引用单元名称:

    1
    2
    [Service]
    ExecStart=/usr/bin/myapp --service-name=%n

    这将导致命令变为/usr/bin/myapp --service-name=myapp.service

  3. %p:对于模板服务myapp@.service%p表示@符号之前的部分:

    1
    2
    [Service]
    ExecStart=/usr/bin/myapp --prefix=%p

    对于实例myapp@alice.service,命令变为/usr/bin/myapp --prefix=myapp

  4. %t:在服务文件中,可以使用%t来添加时间戳到日志文件名

    1
    2
    [Service]
    ExecStart=/usr/bin/myapp >> /var/log/myapp_%t.log

    这将创建一个包含时间戳的日志文件,如/var/log/myapp_1626456789.log

  5. %u:如果服务以特定用户身份运行,可以使用%u来引用用户名:

    1
    2
    [Service]
    ExecStart=/usr/bin/myapp --username=%u

    如果服务由用户alice启动,命令变为/usr/bin/myapp --username=alice

systemd对服务的控制命令

  1. 启动服务:

    1
    systemctl start <服务名>
  2. 停止服务:

    1
    systemctl stop <服务名>
  3. 重启服务:

    1
    systemctl restart <服务名>
  4. 检查服务状态:

    1
    systemctl status <服务名>
  5. 重新加载systemd管理器配置:

    1
    systemctl daemon-reload

    当我们修改了systemd单元文件(例如服务、目标、挂载点等)或者创建、删除了单元文件时,需要使用这个命令来让systemd重新识别这些更改。当systemd的单元文件被修改后,daemon-reload会重新加载这些文件,但不影响当前正在运行的单元。daemon-reload不会重启任何服务或改变任何正在运行的服务状态。它只是让systemd知道配置文件发生了变化。

  6. 重新加载服务配置:

    1
    systemctl reload <服务名>

    例如:systemctl reload nginx 将重新加载Nginx服务的配置文件。

  7. 重新加载服务配置并重启服务:

    1
    systemctl reload-or-restart <服务名>

    如果服务支持重新加载配置,则重新加载;否则,重启服务。

  8. 启用服务:

    1
    systemctl enable <服务名>

    systemctl enable <服务名>命令的作用是创建一个符号链接,将服务的单元文件链接到相应的目标目录下。如果服务的单元文件中没有WantedByRequiredBy字段,systemctl enable命令将无法创建这样的链接

  9. 禁用服务:

    1
    systemctl disable <服务名>

    与启用服务相反,删除对应符号链接

  10. 查看服务是否启用:

    1
    systemctl is-enabled <服务名>
  11. 查看服务的依赖关系:

    1
    systemctl list-dependencies <服务名>
  12. 杀死服务:

    1
    systemctl kill <服务名>

    例如:systemctl kill nginx 将发送信号杀死Nginx服务。

  13. 显示服务主配置文件:

    1
    systemctl show <服务名> --property=FragmentPath

    比如:systemctl show a --property=FragmentPath结果:FragmentPath=/etc/systemd/system/a.service

  14. 编辑服务配置文件:

    1
    systemctl edit <服务名>
  15. 重置服务到初始状态:

    1
    systemctl reset-failed <服务名>

    例如:systemctl reset-failed nginx 将重置Nginx服务的状态,清除任何失败标记。

流程

创建一个开机自启动,并能在服务停止后自动重启服务的流程:

  1. /etc/systemd/system/目录下创建服务文件,如a.service

  2. 编辑a.service

  3. 执行命令:

    1
    sudo systemctl daemon-reload
    1
    sudo systemctl enable a
    1
    sudo systemctl start a

注意事项

  • 设置开机自启动时,service的重启时间需要设置,否则会出现开机自启动失败的情况:
1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=PythonCeshi Service
After=multi-user.target network.target nss-lookup.target

[Service]
User=root
ExecStart=/usr/bin/python3 /work/ceshi.py
Restart=on-failure

[Install]
WantedBy=multi-user.target

这种情况没有设置RestartSec,会出现开机自启动失败的情况,用systemctl status ...会显示重启速度过快的消息

  • 正确配置:
1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=PythonCeshi Service
After=multi-user.target network.target nss-lookup.target

[Service]
User=root
ExecStart=/usr/bin/python3 /work/ceshi.py
Restart=on-failure
RestartSec=60

[Install]
WantedBy=multi-user.target

RestartSec=60,代表启动失败后60秒再重启

target

常见的target

  1. multi-user.target
    • 类似于传统的运行级别3,表示多用户命令行界面模式。
    • 通常用于服务器或不需要图形界面的环境。
  2. graphical.target
    • 类似于传统的运行级别5,表示完整的图形用户界面模式。
    • 用于桌面环境或需要图形界面的系统。
  3. default.target
    • 系统启动时的默认目标。
    • 通常链接到 graphical.targetmulti-user.target
  4. rescue.target
    • 类似于单用户模式,提供一个救援 shell。运行级别1
    • 用于系统修复或维护。
  5. emergency.target
    • 提供一个非常基本的系统,只有根文件系统和一个 emergency shell。
    • 用于严重的系统问题修复。
  6. poweroff.target
    • 关闭系统。运行级别0
  7. reboot.target
    • 重启系统。运行级别6
  8. halt.target
    • 停止系统,但不关闭电源。
  9. suspend.target
    • 挂起系统。
  10. hibernate.target
    • 将系统休眠到硬盘。
  11. hybrid-sleep.target
    • 结合挂起和休眠的特性。
  12. runlevel0.target
    • 关闭系统,等同于 poweroff.target
  13. runlevel1.target
    • 单用户模式,等同于 rescue.target
  14. runlevel2.target
    • 不完全的多用户模式,没有网络服务。
  15. runlevel3.target
    • 多用户模式,等同于 multi-user.target
  16. runlevel4.target
    • 用户定义的运行级别,通常不使用。
  17. runlevel5.target
    • 图形界面模式,等同于 graphical.target
  18. runlevel6.target
    • 重启系统,等同于 reboot.target
  19. network-online.target
    • 表示网络已经在线,可以用于等待网络连接的服务。
  20. remote-fs.target
    • 远程文件系统挂载点。用于远程文件系统(如NFS)的挂载。这个目标确保所有配置的远程文件系统都已经挂载。
  21. timers.target
    • 用于定时器服务。
  22. emergency.target
    • 急救模式,提供紧急shell用于修复系统问题。
  23. network.target
    • 表示网络服务已启动。这个目标并不等待所有的网络接口都完全配置好,而是确保基本的网络栈已经启动。依赖于这个目标的服务可以在网络基本可用时开始运行。
  24. nss-lookup.target
    • 用于表示名称服务切换(NSS)查找服务已准备就绪的状态。这个目标确保在系统完全启动之前,NSS服务(如DNS解析)已经可用。
  25. sockets.target
    • 用于管理套接字服务。这个目标确保所有定义的套接字都已经创建,相关服务可以在需要时启动。
  26. timers.target
    • 用于定时器服务。这个目标确保所有定义的定时器都已经激活。
  27. paths.target
    • 用于路径监控服务。这个目标确保所有定义的路径监控都已经激活。
  28. swap.target
    • 用于交换分区。这个目标确保所有配置的交换分区都已经激活。
  29. devices.target
    • 用于设备管理。这个目标确保所有设备都已经被发现并配置。

这些目标可以根据我们的需求和服务特性进行选择。例如,如果我们有一个网络服务,我们希望它在网络准备好之后启动,那么可以将服务关联到 network-online.target。如果我们有一个只在系统关闭时运行的服务,可以将其关联到 poweroff.target

使用Systemd Target进行系统管理

  • 切换运行级别:使用systemctl isolate命令,可以立即切换到不同的Target。例如:sudo systemctl isolate graphical.target将立即启动图形用户界面
  • 查看当前激活的Target:使用systemctl list-units --type=target命令,我们可以查看当前激活的Target,以及它们的状态
  • 修改默认Target:使用systemctl set-default命令,我们可以更改系统启动后默认进入的Target。例如:sudo systemctl set-default multi-user.target将设置默认Target为多用户模式

完整的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=PythonCeshi Service
After=multi-user.target network.target nss-lookup.target

[Service]
User=root
ExecStart=/usr/bin/python3 /work/ceshi.py
WorkingDirectory= /work
Restart=on-failure
RestartSec=60

[Install]
WantedBy=multi-user.target

Systemed守护进程
https://blog.shinebook.net/2025/03/13/Ubuntu/systemd/
作者
X
发布于
2025年3月13日
许可协议