# systemctl 针对 service 类型的配置

以前需要建立系统服务,需要在 /etc/init.d/ 创建对应的 bash shell script 来处理,如今在 systemd 环境下,该怎么设置相关的服务启动环境?

# 配置文件相关目录简介

systemd 的配置文件大部分在 /usr/lib/systemd/system/ 目录内,Red Hat 官方文件指出,该目录的文件主要是原本软件所提供的设置,建议不要修改。修改的位置在 /etc/systemd/system/ 目录内

比如:想要额外修改 vsftpd.service ,建议放到的位置如下:

  • /usr/lib/systemd/vsftpd.service :官方的预设配置文件

  • /etc/systemd/system/vsftp.service.d/custom.conf

    建立同名并已 .d 后缀结尾的目录,该目录下的文件会「累加其他设置」进入 /usr/lib/systemd/vsftpd.service

    意思应该是:这里是配置会覆盖掉 /usr/lib/systemd/vsftpd.service 中的同名配置

  • /etc/systemd/system/vsftpd.service.wants/*

    此目录内的文件为链接文件,设置相依服务的链接。作用是启动了 vsftpd.service 后,最好再加上该目录下的建议服务

  • /etc/systemd/system/vsftpd.service.requires/*

    此目录内的文件为链接文件,设置相依服务的链接。作用是,在启动 vsftpd.service 之前,需要事先启动哪些服务

基本上在配置文件中,你可以自由设置相依服务的检查,并且设置加入到哪些 target 里。就是建议不要修改原始的配置文件,在上述建议目录下去操作修改

# 配置文件的设置项目简介

这次通过 sshd.service 的配置文件来讲解配置文件的内容

[root@study ~]# cat /usr/lib/systemd/system/sshd.service
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service
Wants=sshd-keygen.service

[Service]		# 该项目于实际执行的指令参数有关
Type=notify
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]		# 此 unit 要挂载到哪个 target 下
WantedBy=multi-user.target
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

分析上述文件,大概分为三个部分:

  • [Unit]

    unit 本身的说明,以及其他相依 daemon 的设置,包括在什么服务之后才启动此 unit 之类的设置

  • [Service]、[Socket][Timer][Mount][Path]...

    不同的 unit type 需要使用对应的设置项目。对于 service 来说,主要在规范服务启动脚本、环境配置文件名、重新启动的方式等

  • [Install]:将此 unit 安装到哪个 target 里面

配置文件内有些设置规则如下:

  • 设置项目通常是可以重复的

    例如可以设置两个 After 在配置文件中,不过,后面的设置会取代前面的,可以使用 After= 的方式,将该项清空归零

  • 如果设置参数需要有「是/否 boolean 类型值」的项目,可以使用 1、yes、true、on 代表启动,0、no、false、off 代表关闭

  • 空白行、开头为 #; 都表示批注信息

每个部分的说明如下:

# [unit] 部分

  • Description

    使用 systemctl list-units 时,展示出来的简易说明。systemctl status 中输出的服务说明也是该值

  • Documentation

    提供管理员能够进一步的文件查询功能,提供的文件可以是如下的资料

    • =http://www..
    • =man:sshd(8)
    • =file:/etc/ssh/sshd_config
  • After:说明此 unit 是在哪个 daemon 启动之后才能启动。

    基本上仅是说明启动顺序,并无强制要求里面的服务一定要启动后此 unit 才能启动。它与 Requires 的设置含义是有差异的。

  • Before:在什么服务启动前,启动本服务

    同样,并非强制性的

  • Rrquires:相依性配置

    明确定义此 unit 需要在哪个 daemon 启动后才能启动。如果依赖的服务没有启动,则该服务不会被启动

  • Wants:

    此 unit 启动之后,还需要启动哪些服务。不是强制性的,只是希望建立让使用者比较好的操作环境

  • Conflicts:冲突服务

    如果这里申明的服务已经启动,那么本 unit 就不能启动。如果我们的 unit 有启动,那么这里定义的服务不能启动。

# [service] 部分

  • Type:启动方式,会影响到 ExecStart。一般有如下几种

    • simple:默认值,主要由 ExecStart 定义的指令串来启动,启动后常驻于内存中

    • forking:

      由 ExecStart 启动的程序通过 spawns 延伸出其他子程序来作为此 daemon 的主要服务。原生的父程序在启动结束后就会终止运行。传统的 unit 服务大多属于这种项目。

      例如:httpd 这个 www 服务,当 httpd 的程序因为运行过久因此即将终结了,则 systemd 会再重新生出另一个子程序持续运行后,再将父程序删除。据说这样的效率比较好

    • oneshot:与 simple 类似,不过该程序工作完毕后就结束了,不会常驻内存中

    • dbus:与 simple 类似,但是这个 daemon 必须要在取得一个 D-Bus 的名称后,才会继续运行,因此设置该项目时,通常也要设置 BusName 才行

    • idle:与 simple 类似,要执行这个 daemon 必须要所有的工作都顺利执行完成后,才会执行,这里的 daemon 通常是开机到最后才执行的服务

    比较重要的 simple、forking 与 oneshot,很多服务需要子程序 forking,还有很多服务只需要在开机的时候执行一次 oneshot

  • EnvironmentFile:指定启动脚本的环境配置文件

    例如:sshd.service 的配置文件写入到 /etc/sysconfig/sshd 中,也可以直接在该项后面用多个不同的 Shell 变量来设置

  • ExecStart:实际执行此 daemon 的指令或脚本程序

    也可以使用 ExecStartPre(之前) 以及 ExecStartPost(之后)在实际启动服务前后进行额外的指令行为。但是仅支持「指令 参数 参数...」格式,不能接受 < > >> | & 等特殊字符,很多 bash 语法也不支持,如果需要这些特殊字符的时候,最好直接写到指令脚本里面。还有一种方式可以支持完整的语法;将 Type=oneshot 就可以了

  • ExecStop:与 systemctl stop 的执行有关,关闭此服务时所执行的指令

  • ExecReload:与 systemctl reload 有关的指令行为

  • Restart:

    当设置 Restart=1 时,当此 daemon 服务终止后,会再次启动该服务

    除非使用 systemctl 强制将此服务关闭,否则该功能会一直生效

  • RemainAfterExit

    当值设置为 1 时, 当该 daemon 所属的所有程序都终止后,此服务会再尝试启动。这对于 Type=oneshot 的服务有帮助

  • TimeoutSec:若这个服务在启动或是关闭时,因为某些缘故导致无法顺利「正常启动或正常结束」的情况下,则需要等待多久才进入「强制结束」的状态

  • KillMode

    可以是 process、control-group、none 其中的一种:

    • process:终止时,只会终止主要的程序(ExecStart 后面指令串启动的)
    • control-group:则由此 daemon 所产生的其他 control-group 的程序,也会被关闭
    • one:没有程序会被关闭
  • RestartSec:

    与 Restart 有相关性,如果该服务被关闭,需要重新启动时,大概要 sleep 多少时间再重新启动。预设是 100ms

# [Install] 部分

  • WantedBy:该 unit 本身是附挂在哪一个 target unit 下

    该项设置大部分都是 *.target unit 。大多数服务性质的 unit 都是附挂在 multi-user.target 下

  • Also:相依性 enable

    当前 unit 本身被 enable 时,Also 声明的 unit 也 enable

  • Alias:别名

    当 systemctl enable 相关的服务时,此服务会进行连接文件的建立,以 multi-user.target 为例,它是用来预设操作环境 default.target 的规划,因此当设置用 default.target 时, /etc/systed/system/default.target 就会连结到 /usr/lib/systemd/system/multi-user.target

配置讲解之后,下面用这些知识点来练习

# 两个 vsftpd 运行的实例

在上一章将 vsftpd 的 port 修改成了 555。这里再运行一个端口为 21 的 vsftpd 程序,需要两个配置文件以及两个启动脚本来设置了:

  • port 21:
    • /etc/vsftpd/vsftpd.conf 配置文件
    • /usr/lib/systemd/system/vsftpd.service 启动脚本
  • port 555:
    • /etc/vsftpd/vsftpd2.conf 配置文件
    • /usr/lib/systemd/system/vsftpd2.service 启动脚本
# 1. 建立好需要的配置文件
[root@study ~]# cd /etc/vsftpd/
[root@study vsftpd]# ll
total 20
-rw-------. 1 root root  125 Oct 31  2018 ftpusers
-rw-------. 1 root root  361 Mar 17 21:59 user_list
-rw-------. 1 root root 5199 Mar 17 23:02 vsftpd.conf
-rwxr--r--. 1 root root  338 Oct 31  2018 vsftpd_conf_migrate.sh
[root@study vsftpd]# cp vsftpd.conf vsftpd2.conf
[root@study vsftpd]# vim vsftpd.conf
# listen_port=555
# 把端口注释掉,他默认的端口是 21.
[root@study vsftpd]# diff vsftpd.conf vsftpd2.conf
131c131
< # listen_port=555
---
> listen_port=555
# 通过对比,两个文件只有端口号不同

# 2. 开始处理启动脚本设置
[root@study vsftpd]# cd /etc/systemd/system/
[root@study system]# ll | grep vsftp
# 由于我们没有额外修改过启动脚本,所以该目录下是没有 vsftp 相关的脚本的
# 从原始的启动脚本目录复制一份过来
[root@study system]# cp /usr/lib/systemd/system/vsftpd.service vsftpd2.service
[root@study system]# vim vsftpd2.service
[Unit]
Description=Vsftpd2 ftp daemon
After=network.target

[Service]
Type=forking
ExecStart=/usr/sbin/vsftpd /etc/vsftpd/vsftpd2.conf

[Install]
WantedBy=multi-user.target
# 重点只是修改 ExecStart 后面的配置文件

# 3. 重新加载 systemd 的脚本配置文件内容
# systemctl daemon-reload
[root@study system]# systemctl list-unit-files --all | grep vsftpd
vsftpd.service                                enabled
vsftpd2.service                               disabled		# 已经能找到了
vsftpd@.service                               disabled
vsftpd.target                                 disabled
[root@study system]# systemctl status vsftpd2.service
* vsftpd2.service - Vsftpd2 ftp daemon
   Loaded: loaded (/etc/systemd/system/vsftpd2.service; disabled; vendor preset: disabled)
   Active: inactive (dead)

# 由于之前直接修改的 vsftp 的配置文件,所以 vsftp 也需要重新启动
[root@study system]# systemctl restart vsftpd.service vsftpd2.service
[root@study system]# systemctl enable vsftpd.service vsftpd2.service
Created symlink from /etc/systemd/system/multi-user.target.wants/vsftpd2.service to /etc/systemd/system/vsftpd2.service.
[root@study system]# systemctl status vsftpd.service vsftpd2.service
* vsftpd.service - Vsftpd ftp daemon
   Loaded: loaded (/usr/lib/systemd/system/vsftpd.service; enabled; vendor preset: disabled)
   Active: active (running) since Thu 2020-03-19 16:50:22 CST; 27s ago
 Main PID: 5986 (vsftpd)
   CGroup: /system.slice/vsftpd.service
           `-5986 /usr/sbin/vsftpd /etc/vsftpd/vsftpd.conf

Mar 19 16:50:22 study.centos.mrcode systemd[1]: Stopped Vsftpd ftp daemon.
Mar 19 16:50:22 study.centos.mrcode systemd[1]: Starting Vsftpd ftp daemon...
Mar 19 16:50:22 study.centos.mrcode systemd[1]: Started Vsftpd ftp daemon.

* vsftpd2.service - Vsftpd2 ftp daemon
   Loaded: loaded (/etc/systemd/system/vsftpd2.service; enabled; vendor preset: disabled)
   Active: active (running) since Thu 2020-03-19 16:50:22 CST; 27s ago
 Main PID: 5987 (vsftpd)
   CGroup: /system.slice/vsftpd2.service
           `-5987 /usr/sbin/vsftpd /etc/vsftpd/vsftpd2.conf

Mar 19 16:50:22 study.centos.mrcode systemd[1]: Stopped Vsftpd2 ftp daemon.
Mar 19 16:50:22 study.centos.mrcode systemd[1]: Starting Vsftpd2 ftp daemon...
Mar 19 16:50:22 study.centos.mrcode systemd[1]: Started Vsftpd2 ftp daemon.

[root@study system]# netstat -tlnp | grep vsftp
tcp6       0      0 :::555                  :::*                    LISTEN      5987/vsftpd         
tcp6       0      0 :::21                   :::*                    LISTEN      5986/vsftpd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

这样一个启动脚本的完成了

# 多重的重复设置方式:以 getty 为例

CentOS 7 开机后,有 6 个终端机可以使用(tty1 ~ tty6),是由 agetty 指令搞定的。终端机的功能涉及很多层面,主要管理的是 getty.target 这个 target unit,实际产生 tty1~tty6 的则是由 getty@.service 所提供的。

通过这个 getty@.service 的启动脚本来获取 @ 是啥含义

[root@study system]# cat /usr/lib/systemd/system/getty@.service
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Getty on %I
Documentation=man:agetty(8) man:systemd-getty-generator(8)
Documentation=http://0pointer.de/blog/projects/serial-console.html
After=systemd-user-sessions.service plymouth-quit-wait.service getty-pre.target
After=rc-local.service

# If additional gettys are spawned during boot then we should make
# sure that this is synchronized before getty.target, even though
# getty.target didn't actually pull it in.
Before=getty.target
IgnoreOnIsolate=yes

# On systems without virtual consoles, don't start any getty. Note
# that serial gettys are covered by serial-getty@.service, not this
# unit.
ConditionPathExists=/dev/tty0

[Service]
# the VT is cleared by TTYVTDisallocate
ExecStart=-/sbin/agetty --noclear %I $TERM
Type=idle
Restart=always
RestartSec=0
UtmpIdentifier=%I
TTYPath=/dev/%I
TTYReset=yes
TTYVHangup=yes
TTYVTDisallocate=yes
KillMode=process
IgnoreSIGPIPE=no
SendSIGHUP=yes

# Unset locale for the console getty since the console has problems
# displaying some internationalized messages.
Environment=LANG= LANGUAGE= LC_CTYPE= LC_NUMERIC= LC_TIME= LC_COLLATE= LC_MONETARY= LC_MESSAGES= LC_PAPER= LC_NAME= LC_ADDRESS= LC_TELEPHONE= LC_MEASUREMENT= LC_IDENTIFICATION=

[Install]
WantedBy=getty.target
DefaultInstance=tty1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

重要项: ExecStart,通过 man agetty 时发现它的语法应该是 agetty --noclear tty1 (笔者没有看懂里面哪里写的有这个,内容有点多),那么启动 6 个的话,就需要有 6 个 启动配置来分别配置启动指令 tty1~tty6,是在很繁琐,这就出现了 getty@.service 中的 @ 。先来看看他的上游 Before=getty.target

[root@study system]# systemctl show getty.target
Id=getty.target
Names=getty.target
Wants=getty@tty1.service
WantedBy=multi-user.target
Conflicts=shutdown.target
Before=multi-user.target
After=getty@tty1.service
# 这里,笔者的环境上与书上不一致了,书上是 getty@tty1.service getty@tty2.service 等 6 个
# 注意,这里笔者发现了为什么不一样
# cat /usr/lib/systemd/system/getty.target 里面内容很少,没有上面这些定义
# 但是用 show 可以显示出来,这个书上目前没有讲解
# 但是,通过切换到 tty2 tty3 上去的时候,使用  systemctl show getty.target 查看状态时,After 后面就会出现已经切换过之后的 tty。和书上展示是一样的效果
1
2
3
4
5
6
7
8
9
10
11
12
13

书上说,上面的 After 的配置,当 执行完 getty.target 之后,会持续要求 getty@tty1.service 等 6 个服务继续启动。那么 systemd 就会如下做:

  • 先查找 /usr/lib/systemd/system//etc/systemd/system/ 有无 getty@tty1.service 的设置,若有就启动,没有则执行下一步
  • 找到 getty@.service 的设置,则将 @ 后面的数据代入成 %I 的变量,进入 gett@.service 执行

也就是说 getty@tty1.service 实际上是不存在的,主要是通过 getty@.service 来执行,来简化多个执行的启动设置,他的命名方式如下:

  • 源文件:执行服务名称@.service
  • 执行文件:执行服务名称@范例名称.service

getty@.service 中的启动指令ExecStart=-/sbin/agetty --noclear %I $TERM,根据 getty.target 的信息输出来看,getty@tty1.service%I 的值是 tty1,因此执行脚本会变成 /sbin/agetty --noclear tty1

# 将 tty 的数量由 6 个降低到 4 个

这个配置是在 /etc/systemd/logind.conf 里面配置的。

# 1. 打开注释信息,并修改成 4 个
[root@study system]# vim /etc/systemd/logind.conf
[Login]
NAutoVTs=4
ReserveVT=0
# 2. 如果你切换到 tty5 和 tty6 的话,请将他们关闭后重启 getty.target
[root@study ~]# systemctl stop getty@tty5.service
[root@study ~]# systemctl stop getty@tty6.service
[root@study ~]# systemctl restart systemd-logind.service
1
2
3
4
5
6
7
8
9

当你回到桌面环境下,再切换到 tty5 和 tty6 时就切换不过去了。

到这里笔者貌似迷糊了,getty.target 只是一个定义,类似组,里面并没有定义什么,被其他的 unit 附加。有点不太明白和这里启动 1~6 个有什么关系?这里启动的是 systemd-logind.service ,而且切换只能在桌面环境下,也就是说 tty1~tty6 的切换是这个指令的功能,只是实际启动时用 getty@.service 启动的。但是具体怎么关联上的,是在是迷糊,看不懂

那么其实可以通过指令方式直接启动一个 tty。而无需其他的配置文件

systemctl start getty@tty8.service
1

如果只单独看这里的演示的话,笔者能明白的就是 getty@.service 是一个功能,可以通过 @ 传递参数给服务

# 暂时新增 vsftpd 到 2121 端口

vsftpd 也提供了 @ 的服务方式

[root@study ~]# ll /usr/lib/systemd/system | grep vsftpd
-rw-r--r--. 1 root root  171 Oct 31  2018 vsftpd.service
-rw-r--r--. 1 root root   89 Oct 31  2018 vsftpd.target
-rw-r--r--. 1 root root  184 Oct 31  2018 vsftpd@.service		# 这里
1
2
3
4
[root@study ~]# cat /usr/lib/systemd/system/vsftpd@.service
[Unit]
Description=Vsftpd ftp daemon
After=network.target
PartOf=vsftpd.target

[Service]
Type=forking
ExecStart=/usr/sbin/vsftpd /etc/vsftpd/%i.conf

[Install]
WantedBy=vsftpd.target
1
2
3
4
5
6
7
8
9
10
11
12

这里使用了 %i,也就是说大小写的的变量都可以,这里启动指令拼接了一个配置文件路径 /etc/vsftpd/%i.conf

那么先创建这个配置文件,然后通过 @ 方式启动

# 1. 制作一个 vsftpd3.conf ,并吧端口修改为 2121
[root@study ~]# cd /etc/vsftpd/    
[root@study vsftpd]# ll
total 28
-rw-------. 1 root root  125 Oct 31  2018 ftpusers
-rw-------. 1 root root  361 Mar 17 21:59 user_list
-rw-------. 1 root root 5201 Mar 19 16:35 vsftpd.conf
-rw-------. 1 root root 5199 Mar 19 16:35 vsftpd2.conf
-rwxr--r--. 1 root root  338 Oct 31  2018 vsftpd_conf_migrate.sh
[root@study vsftpd]# cp vsftpd.conf vsftpd3.conf
[root@study vsftpd]# vim vsftpd3.conf
listen_port=2121

# 2. 暂时启动,不要开机启动
[root@study vsftpd]# systemctl start vsftpd@vsftpd3.service
[root@study vsftpd]# systemctl status vsftpd@vsftpd3.service
* vsftpd@vsftpd3.service - Vsftpd ftp daemon
   Loaded: loaded (/usr/lib/systemd/system/vsftpd@.service; disabled; vendor preset: disabled)
   Active: active (running) since Thu 2020-03-19 17:48:57 CST; 5s ago
  Process: 7536 ExecStart=/usr/sbin/vsftpd /etc/vsftpd/%i.conf (code=exited, status=0/SUCCESS)
 Main PID: 7538 (vsftpd)
   CGroup: /system.slice/system-vsftpd.slice/vsftpd@vsftpd3.service
           `-7538 /usr/sbin/vsftpd /etc/vsftpd/vsftpd3.conf

Mar 19 17:48:57 study.centos.mrcode systemd[1]: Starting Vsftpd ftp daemon...
Mar 19 17:48:57 study.centos.mrcode systemd[1]: Started Vsftpd ftp daemon.

[root@study vsftpd]# netstat -tlnp | grep vsftpd
tcp6       0      0 :::2121                 :::*                    LISTEN      7538/vsftpd         
tcp6       0      0 :::555                  :::*                    LISTEN      5987/vsftpd         
tcp6       0      0 :::21                   :::*                    LISTEN      5986/vsftpd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

可以看到通过这种 @ 的方式,能让我们制作启动脚本的时候更为灵活一点

TIP

有一件事情,这次使用了 2121 端口,却不用修改 SELinux?因为默认启动小于 1024 以下的端口时,需要使用到 root 的权限,大于 2014 的相对来说对系统的影响可能小一些,就忽略了 SELinux 的限制了

笔者看这句话,貌似记不起有关 SELinux 端口限制的知识了?懵逼状态

# 自己的服务自己做

下面模拟自己做一个服务:制作一个可以备份自己系统的服务,这脚本放在 /backups 下,内容如下

[root@study ~]# mkdir /backups		# 下面脚本,上层目录不存在,都无法保存的
[root@study ~]# vim /backups/backup.sh
#!/bin/bash

source="/etc /home /root /var/lib /var/spool/{cron,at,mail}"
target="/backups/backup-system-$(date +%Y-%m-%d).tar.gz"

[ ! -d /backups ] && mkdir /backups

tar -zcvf ${target} ${source} $> /backups/backup.log

[root@study ~]# chmod a+x /backups/backup.sh
[root@study ~]# ll /backups/backup.sh
-rwxr-xr-x. 1 root root 222 Mar 20 09:24 /backups/backup.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14

准备 backup.service 的启动脚本

[root@study ~]# vim /etc/systemd/system/backup.service
[unit]
Description=backup my server
Requires=atd.service

[Service]
Type=simple
ExecStart=/bin/bash -c " echo /backups/backup.sh | at now"

[Install]
WantedBy=multi-user.target

# 因为 ExecStart 里面用到了 at 指令,因此 atd.service 是一定需要的服务

[root@study ~]# systemctl daemon-reload
[root@study ~]# systemctl start backup.service
[root@study ~]# systemctl status backup.service
* backup.service - backup my server
   Loaded: loaded (/etc/systemd/system/backup.service; disabled; vendor preset: disabled)
   Active: inactive (dead)

Mar 20 09:30:56 study.centos.mrcode systemd[1]: Started backup my server.
Mar 20 09:30:56 study.centos.mrcode bash[17748]: job 8 at Fri Mar 20 09:30:00 2020

# 可以看到服务在执行了,状态是 inactive ,这不是一个驻留内存的服务,执行完成就退出了
# 这里笔者看不懂为啥用 echo /backups/backup.sh | at now; 而不是直接直接给定脚本路径,而且貌似脚本里面的内容执行也有问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26