初识systemd-使用篇

微笑、不失礼 提交于 2020-02-06 16:53:50

Linux操作系统的开机过程是这样的,即从BIOS开始,然后进入Boot Loader,再加载系统内核,然后内核进行初始化,最后启动初始化进程。初始化进程作为Linux系统的第一个进程,它需要完成Linux系统中相关的初始化工作,为用户提供合适的工作环境。RHEL 7、CentOS7等linux发行版系统已经替换掉了熟悉的初始化进程服务System V init,正式采用全新的systemd初始化进程服务。systemd初始化进程服务采用了并发启动机制,开机速度得到了不小的提升。

一、systemd概述

systemd即为system daemon,是linux下的一种init软件,由Lennart Poettering带头开发,并在LGPL 2.1及其后续版本许可证下开源发布,开发目标是提供更优秀的框架以表示系统服务间的依赖关系,并依此实现系统初始化时服务的并行启动,同时达到降低Shell的系统开销的效果,最终代替现在常用的System V与BSD风格init程序。
systemd是一个专用于 Linux 操作系统的系统与服务管理器。当作为启动进程(PID=1)运行时,它将作为初始化系统运行,也就是启动并维护各种用户空间的服务。
为了与传统的 SysV 兼容,如果将 systemd 以 init 名称启动,并且"PID≠1",那么它将执行 telinit 命令并将所有命令行参数原封不动的传递过去。 这样对于普通的登录会话来说,无论是调用 init 还是调用 telinit 都是等价的。
当作为系统实例运行时,systemd将会按照system.conf配置文件以及system.conf.d配置目录中的指令工作;当作为用户实例运行时,systemd 将会按照user.conf配置文件 以及 user.conf.d配置目录中的指令工作。
systemd将各种系统启动和运行相关的对象,表示为各种不同类型的单元(unit),并提供了处理不同单元之间依赖关系的能力。大部分单元都静态的定义在单元文件中,但是有少部分单元则是动态自动生成的:其中一部分来自于其他传统的配置文件(为了兼容性),而另一部分则动态的来自于系统状态或可编程的运行时状态。单元既可以处于活动(active)状态,也可以处于停止(inactive)状态,当然也可以处于启动中(activating)或停止中(deactivating)的状态。还有一个特殊的失败(failed)状态,意思是单元以某种方式失败了(进程崩溃了、或者触碰启动频率限制、或者退出时返回了错误代码、或者遇到了操作超时之类的故障)。当进入失败(failed)状态时,导致故障的原因将被记录到日志中以方便日后排查。需要注意的是,不同的单元可能还会有各自不同的"子状态",但它们都被映射到上述五种状态之一。
systemd的优点是功能强大、使用方便,缺点是体系庞大、非常复杂。事实上,现在还有很多人反对使用systemd,理由就是它过于复杂,与操作系统的其他部分强耦合,违反"keep simple, keep stupid"的Unix哲学。

二、systemd与System V init的区别以及作用

与以前的发行版使用的System V风格init相比,systemd采用了以下新技术:(1) 采用Socket激活式与总线激活式服务,以提高相互依赖的各服务的并行运行性能;(2)用Cgroups代替PID来追踪进程,以此即使是两次fork之后生成的守护进程也不会脱离systemd的控制。
无论怎样,RHEL 7系统选择systemd初始化进程服务已经是一个既定事实,因此也没有了“运行级别”这个概念,Linux系统在启动时要进行大量的初始化工作,比如挂载文件系统和交换分区、启动各类进程服务等,这些都可以看作是一个一个的单元(Unit),systemd用目标(target)代替了System V init中运行级别的概念,这两者的区别如下所示。
表1:System V init和systemd的区别

System V init运行级别 systemd目标名称 作用
0 runlevel0.target -> poweroff.target 关机
1 runlevel1.target -> rescue.target 单用户模式
2 runlevel2.target -> multi-user.target 等同于级别3
3 runlevel3.target -> multi-user.target 多用户的文本界面
4 runlevel4.target -> multi-user.target 等同于级别3
5 runlevel5.target -> graphical.target 多用户的图形界面
6 runlevel6.target -> reboot.target 重启
emergency emergency.target 紧急shell

每个Unit都有一个配置文件,告诉Systemd怎么启动这个Unit。Systemd默认从目录/etc/systemd/system/读取配置文件,但是里面存放的大部分文件都是符号链接,指向目录/usr/lib/systemd/system/或者/lib/systemd/system/,Unit的配置文件都存放在这个目录里面。
例如:将系统默认的运行模式修改为“多用户,无图形”,可直接用ln命令把多用户模式目标文件链接到/etc/systemd/system/目录
ln -sf /lib/systemd/system/multi-user.target /etc/systemd/system/default.target
初识systemd-使用篇
在RHEL6及之前的发行版系统是使用service、chkconfig等命令来管理系统服务,在RHEL7系统中是使用systemctl命令来管理服务的。我们来看一下它们之间的区别。
表2:服务的启动、重启、停止等常用命令对比

System V init命令 systemctl命令 作用
service foo start systemctl start foo.service 启动服务
service foo restart systemctl restart foo.service 重启服务
service foo stop systemctl stop foo.service 停止服务
service foo reload systemctl reload foo.service 重新加载配置文件(不停止服务)
service foo status systemctl status foo.service 查看服务状态

表3:设置服务开机启动、不启动等常用命令对比

System V init命令 systemctl命令 作用
chkconfig foo on systemctl enable foo.service 开机自动启动
chkconfig foo off systemctl disable foo.service 开机不自动启动
chkconfig foo systemctl is-enabled foo.service 查看服务是否为自动启动
chkconfig --list systemctl list-unit-files 查看各个服务的启动与禁用情况

三、系统管理相关命令

(1)systemctl是systemd的主命令,用于管理系统和服务。
重启系统
systemctl reboot
关闭系统,切断电源
systemctl poweroff
CPU停止工作
systemctl halt
暂停系统
systemctl suspend
让系统进入冬眠状态
systemctl hibernate
让系统进入交互式休眠状态
systemctl hybrid-sleep
启动进入救援状态(单用户状态)
systemctl rescue
(2)systemd-analyze命令用于查看启动耗时。
查看系统启动耗时
systemd-analyze
查看每个服务的启动耗时
systemd-analyze blame
显示瀑布状的系统启动过程流
systemd-analyze critical-chain
显示指定服务的启动流
systemd-analyze critical-chain atd.service
(3)hostnamectl命令用于查看当前主机的信息。
显示当前主机的信息
hostnamectl
设置主机名。
hostnamectl set-hostname xuad1
(4)localectl命令用于查看本地化设置。
查看本地化设置
localectl
设置本地化参数。

localectl set-locale LANG=zh_CN.UTF-8
localectl set-keymap zh_CN

(5)timedatectl命令用于查看当前系统时区设置。
查看当前时区设置
timedatectl
显示所有可用的时区
timedatectl list-timezones
设置当前时区

timedatectl set-timezone Asia/Shanghai
timedatectl set-time YYYY-MM-DD
timedatectl set-time HH:MM:SS

(6)loginctl命令用于查看当前登陆的用户。
列出当前session
loginctl list-sessions
列出当前登录用户
loginctl list-users
列出显示指定用户的信息
loginctl show-user root

四、Unit相关命令

Systemd可以管理所有系统资源。不同的资源统称为Unit(单位),Unit一共分成12种。

Service unit:系统服务
Target unit:多个Unit构成的一个组
Device Unit:硬件设备
Mount Unit:文件系统的挂载点
Automount Unit:自动挂载点
Path Unit:文件或路径
Scope Unit:不是由Systemd启动的外部进程
Slice Unit:进程组
Snapshot Unit:Systemd快照,可以切回某个快照
Socket Unit:进程间通信的socket
Swap Unit:swap文件
Timer Unit:定时器

systemctl list-units命令可以查看系统的所有Unit信息。
列出已启动的Unit
systemctl list-units
列出所有Unit,包括没有找到配置文件的或者启动失败的
systemctl list-units --all
列出所有没有启动的Unit
systemctl list-units --all --state=inactive
列出所有启动失败的Unit
systemctl list-units --failed
列出所有正在运行的、类型为service的Unit
systemctl list-units --type=service
systemctl status命令用于查看系统状态和单个Unit的状态。
显示系统状态
systemctl status
显示单个 Unit 的状态
sysystemctl status httpd.service
显示远程主机的nginx服务的状态
systemctl -H root@192.168.2.204 status nginx.service
除了status外,systemctl还提供了三个查询状态的简单方法,主要供脚本内部的判断语句使用。
显示某个 Unit 是否启动
systemctl is-active application.service
显示某个 Unit 是否启动失败
systemctl is-failed application.service
显示某个 Unit 服务是否开机启动
systemctl is-enabled application.service
systemctl常用命令
立即启动一个服务
systemctl start httpd.service
立即停止一个服务
systemctl stop httpd.service
重启一个服务
systemctl restart httpd.service
杀死一个服务的所有子进程
systemctl kill httpd.service
重新加载一个服务的配置文件
systemctl reload httpd.service
重载所有修改过的配置文件
systemctl daemon-reload
显示某个 Unit 的所有底层参数
systemctl show httpd.service
显示某个 Unit 的指定属性的值
systemctl show -p CPUShares httpd.service
设置某个 Unit 的指定属性
systemctl set-property httpd.service CPUShares=500
Unit的后缀名.service可以省略,例如启动ssh的命令systemctl start sshd.service可以写成systemctl start sshd。
Unit之间存在依赖关系,A依赖于B,就意味着systemd在启动A的时候,同时会去启动B。
systemctl list-dependencies命令列出一个Unit的所有依赖。
systemctl list-dependencies nginx.service
上面命令的输出结果之中,有些依赖是Target类型,默认不会展开显示。如果要展开Target,就需要使用--all参数。
systemctl list-dependencies --all nginx.service
systemctl list-unit-files命令用于列出所有Unit的状态。
列出所有Unit状态
systemctl list-unit-files
列出指定类型的Unit状态
systemctl list-unit-files --type=service
Unit的四种状态

enabled:开机启动
disabled:不开机启动
static:该Unit没有[Install]部分(无法执行),只能作为其他Unit的依赖
masked:该Unit被禁止开机启动

如果修改了某个服务的配置文件,就要重新加载配置,然后重新启动,否则修改不会生效。

systemctl daemon-reload
systemctl restart httpd.service

系统运行模式target相关命令
查看当前系统的所有Target
systemctl list-unit-files --type=target
查看一个 Target 包含的所有Unit
systemctl list-dependencies multi-user.target
查看启动时的默认Target
systemctl get-default
设置启动时的默认Target
systemctl set-default multi-user.target
切换Target时,默认不关闭前一个Target启动的进程,systemctl isolate命令改变这种行为,关闭前一个Target里面所有不属于后一个Target的进程
systemctl isolate multi-user.target

五、日志管理命令

systemd统一管理所有Unit的启动日志,可以只用journalctl一个命令查看所有日志(内核日志和应用日志),它的配置文件是/etc/systemd/journald.conf。
查看所有日志(默认情况下 ,只保存本次启动的日志)
journalctl
查看内核日志(不显示应用日志)
journalctl -k
查看系统本次启动的日志

journalctl -b
journalctl -b -0

查看上一次启动的日志(需更改设置)
journalctl -b -1
查看指定时间的日志

journalctl --since="2012-10-30 18:17:16"
journalctl --since "20 min ago"
journalctl --since yesterday
journalctl --since "2015-01-10" --until "2015-01-11 03:00"
journalctl --since 09:00 --until "1 hour ago"

显示尾部的最新10行日志
journalctl -n
显示尾部指定行数的日志
journalctl -n 20
实时滚动显示最新日志
journalctl -f
查看指定服务的日志
journalctl /usr/lib/systemd/systemd
查看指定进程的日志
journalctl _PID=1
查看某个路径的脚本的日志
journalctl /usr/bin/bash
查看指定用户的日志
journalctl _UID=33 --since today
查看某个 Unit 的日志

journalctl -u nginx.service
journalctl -u nginx.service --since today

实时滚动显示某个 Unit 的最新日志
journalctl -u nginx.service -f
合并显示多个 Unit 的日志
journalctl -u nginx.service -u php-fpm.service --since today
查看指定优先级(及其以上级别)的日志,共有8级

0: emerg
1: alert
2: crit
3: err
4: warning
5: notice
6: info
7: debug

journalctl -p err -b
日志默认分页输出,--no-pager 改为正常的标准输出
journalctl --no-pager
以 JSON 格式(单行)输出
journalctl -b -u nginx.service -o json
以 JSON 格式(多行)输出,可读性更好
journalctl -b -u nginx.service -o json-pretty
显示日志占据的硬盘空间
journalctl --disk-usage
指定日志文件占据的最大空间
journalctl --vacuum-size=1G
指定日志文件保存多久
journalctl --vacuum-time=1years

六、Unit配置文件

前面已经讲过,配置一个服务开机启动的命令是systemctl enable <服务名>.service,实际上只是在/etc/systemd/system/目录下做了个文件链接而已。

systemctl enable httpd.service
等同于
ln -s '/usr/lib/systemd/system/httpd.service' '/etc/systemd/system/httpd.service'

Unit的配置文件的内容,一般会有以下三个区块,具体含义如下
[Unit]区块通常是配置文件的第一个区块,用来定义Unit的元数据,以及配置与其他Unit的关系,它的主要字段如下:

Description:简单描述
Documentation:服务的启动文件和配置文件
Requires:当前Unit依赖的其他Unit,如果它们没有运行,当前Unit会启动失败
Wants:与当前Unit配合的其他Unit,如果它们没有运行,不影响当前Unit的启动
BindsTo:与Requires类似,它指定的Unit如果退出,会导致当前Unit停止运行
Before:如果该字段指定的Unit也要启动,那么必须在当前Unit之后启动
After:如果该字段指定的Unit也要启动,那么必须在当前Unit之前启动
Conflicts:这里指定的Unit不能与当前Unit同时运行
Condition...:当前Unit运行必须满足的条件,否则不会运行
Assert...:当前Unit运行必须满足的条件,否则会报启动失败

[Service]区块配置,只有Service类型的Unit才有这个区块,它的主要字段如下:

Type:定义启动时的进程行为,它有以下几种值。
Type=simple:默认值,执行ExecStart指定的命令,启动主进程
Type=forking:以fork方式从父进程创建子进程,之后父进程会退出,子进程成为主进程
Type=oneshot:一次性进程,Systemd会等当前服务退出,再继续往下执行
Type=dbus:当前服务通过D-Bus启动
Type=notify:当前服务启动完毕,会通知Systemd,再继续往下执行
Type=idle:若有其他任务,则其他任务执行完毕,当前服务才会运行
ExecStart:启动当前服务的命令
ExecStartPre:启动当前服务之前执行的命令
ExecStartPost:启动当前服务之后执行的命令
ExecReload:重启当前服务时执行的命令
ExecStop:停止当前服务时执行的命令
ExecStopPost:停止当其服务之后执行的命令
RestartSec:自动重启当前服务间隔的秒数
Restart:定义何种情况Systemd会自动重启当前服务,可能的值包括always(总是重启)、on-success、on-failure、on-abnormal、on-abort、on-watchdog
TimeoutSec:定义Systemd停止当前服务之前等待的秒数
Environment:指定环境变量

[Install]通常是配置文件的最后一个区块,用来定义运行模式(Target)、Unit别名等设置,以及是否开机启动,它的主要字段如下:

WantedBy:它的值是一个或多个Target,当前Unit激活时(enable)时,符号链接会放入/etc/systemd/system目录下面以Target名+.wants后缀构成的子目录中
RequiredBy:它的值是一个或多个Target,当前Unit激活时,符号链接会放入/etc/systemd/system目录下面以Target名+.required后缀构成的子目录中
Alias:当前Unit可用于启动的别名
Also:当前Unit激活(enable)时,会被同时激活的其他Unit

Unit配置文件的完整字段清单,请参考官方文档。
systemctl cat命令可以用来查看配置文件,那么我们来看一下sshd配置文件的内容,分析下它每项配置的含义是什么。
systemctl cat sshd.service

[Unit]
Description=OpenSSH server daemon   # 当前服务的简单描述
Documentation=man:sshd(8) man:sshd_config(5)  # sshd是启动脚本,sshd_config是配置文件
After=network.target sshd-keygen.service  # 启动ssh服务之前会先启动这两个Unit
Wants=sshd-keygen.service  # 此Unit启动成功与否不影响ssh服务的正常启动

[Service]
Type=notify  # ssh服务启动成功后会通知systemd,再启动其他依赖服务
EnvironmentFile=/etc/sysconfig/sshd  # 指定ssh服务的环境参数配置文件
ExecStart=/usr/sbin/sshd -D $OPTIONS  # 启动ssh服务执行的命令
ExecReload=/bin/kill -HUP $MAINPID  # 重启ssh服务执行的命令
KillMode=process  # process表示只停止主进程,不停止子进程
Restart=on-failure  # 进程非正常退出时,包括信号终止和超时,会重启服务
RestartSec=42s  # 上面Restart重启之前需要等待42秒再重启

[Install]
WantedBy=multi-user.target   # ssh服务所在的系统运行模式

[Unit]区块:启动顺序与依赖关系
After字段定义哪些服务在ssh服务启动之前启动,Before字段定义哪些服务在ssh服务启动之后启动,After和Before字段只涉及启动顺序,不涉及依赖关系。
举例来说,某web应用需要postgresql数据库储存数据,在配置文件中只定义了postgresql在该web应用之前启动,而没有定义依赖关系,由于某种原因postgresql需要重新启动,在postgresql停止期间,该web应用就会无法建立数据库连接。
设置依赖关系,需要使用Wants字段和Requires字段。
Wants字段表示sshd.service与sshd-keygen.service之间属于“弱依赖”关系,即sshd-keygen.service启动失败或者停止运行,不影响sshd.service的运行。
Requires字段则表示“强依赖”关系,即如果该服务启动失败或者停止运行,那么sshd.service也不会启动。
注意:Wants字段与Requires字段只涉及依赖关系,与启动顺序无关,默认情况下是同时启动的。
[Service]区块:启动、停止、重启行为
EnvironmentFile字段:指定ssh服务的环境参数配置文件,该文件内部的key=value键值对,可以用$key的形式,在当前配置文件中获取。
ExecStart字段定义了启动ssh服务的命令是/usr/sbin/sshd -D $OPTIONS,其中的变量$OPTIONS就来自EnvironmentFile字段定义的环境参数配置文件。
所有的启动设置前面都可以加上一个符号(-),表示“抑制错误”,即发生错误也不影响其他命令的执行。比如,EnvironmentFile=-/etc/sysconfig/sshd表示即使/etc/sysconfig/sshd文件不存在,也不会抛出错误。
Type字段定义启动类型,Type=notify表示ssh服务启动后会向Systemd发出通知信号,然后Systemd再启动其他服务。
举个例子,笔记本电脑启动时,要把触摸板关掉,配置文件可以这样写。

[Unit]
Description=Switch-off Touchpad

[Service]
Type=oneshot
ExecStart=/usr/bin/touchpad-off

[Install]
WantedBy=multi-user.target

Type设置为oneshot时,就表示这个服务只要运行一次就够了,不需要长期运行。
如果关闭后,将来某个时候还想打开,配置文件修改如下。

[Unit]
Description=Switch-off Touchpad

[Service]
Type=oneshot
ExecStart=/usr/bin/touchpad-off start
ExecStop=/usr/bin/touchpad-off stop
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

RemainAfterExit字段设置为yes,表示进程退出后,服务仍然保持执行。这样的话,一旦使用systemctl stop命令停止服务,ExecStop定义的命令就是执行,从而重新开启触摸板。
KillMode字段设为process表示只停止主进程,不停止任何子进程,即子进程打开的ssh session仍然保持连接。这个设置不太常见,但对ssh服务很重要,否则你停止服务的时候,会连自己打开的ssh session一起杀掉。
KillMode字段可以设置的值如下:

control-group(默认值):当前控制组里面的所有子进程,都会被杀掉
process:只杀主进程
mixed:主进程将收到SIGTERM信号,子进程收到SIGKILL信号
none:没有进程会被杀掉,只是执行服务的stop命令。

Restart设为on-failure表示任何意外的失败,都将重启ssh服务。如果ssh服务正常停止(比如执行systemctl stop sshd.service命令),ssh服务就不会重启。
Restart字段可以设置的值如下:

no(默认值):退出后不会重启
on-success:只有正常退出时(退出状态码为0),才会重启
on-failure:非正常退出时(退出状态码非0),包括被信号终止和超时,才会重启
on-abnormal:只有被信号终止和超时,才会重启
on-abort:只有在收到没有捕捉到的信号终止时,才会重启
on-watchdog:超时退出,才会重启
always:不管是什么退出原因,总是重启

对于需要守护的进程,推荐设为on-failure;对于那些允许发生错误退出的服务,可以设为on-abnormal。
RestartSec表示Systemd重启服务之前,需要等待的秒数。
[Install]区块:定义服务启动的系统运行模式,即该服务在哪个运行模式下开机启动。
WantedBy定义服务所在的Target,WantedBy=multi-user.target指的是ssh服务所在的Target是multi-user.target(多用户命令行模式)。
当我们执行systemctl enable sshd.service命令时,sshd.service的一个符号链接,就会放在/etc/systemd/system目录下面的multi-user.target.wants子目录之中。
Systemd有默认启动的Target,multi-user.target(多用户命令行模式)。
初识systemd-使用篇
上图的结果表示,在multi-user.target.wants目录下面的所有服务,都将开机启动,这就是为什么systemctl enable命令能设置开机启动的原因。
查看 multi-user.target 包含的所有服务
systemctl list-dependencies multi-user.target
切换到另一个 target
shutdown.target 就是关机状态
systemctl isolate shutdown.target
前面已经讲过systemd的系统运行目标有哪些,我们经常用到的有两个:一个是multi-user.target,表示多用户命令行状态;另一个是graphical.target,表示图形用户状态,它依赖于multi-user.target。官方文档有一张非常清晰的Target依赖关系图。

七、Target的配置文件

Target的配置文件存放在/lib/systemd/system/目录下,我们来看一下multi-user.target的内容。
systemctl cat multi-user.target
初识systemd-使用篇
Requires=basic.target:要求basic.target一起运行。
Conflicts字段:冲突字段,如果rescue.service或rescue.target正在运行,multi-user.target就不能运行,反之亦然。
After:表示multi-user.target在basic.target、rescue.service、rescue.target之后启动,如果它们有启动的话。
AllowIsolate:允许使用systemctl isolate命令切换到multi-user.target。
注意:修改配置文件以后,需要重新加载配置文件,然后重新启动相关服务。
重新加载配置文件
systemctl daemon-reload
重启相关服务
systemctl restart foo

八、编写服务配置文件实例

我们首先编写一个xuad.service配置文件,功能为启动、重启、停止nginx服务,放在/usr/lib/systemd/system/目录下,内容如下:
vim /usr/lib/systemd/system/xuad.service

[Unit]
Description=xuad server

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/bin/rm -f /run/nginx.pid
EnvironmentFile=/etc/sysconfig/xuad
ExecStart=/usr/sbin/nginx
ExecStartPost=/bin/echo $OPTIONS
ExecReload=/bin/kill -s HUP $MAINPID
KillSignal=SIGQUIT
ExecStopPost=/bin/echo stop

[Install]
WantedBy=multi-user.target

接下来再编写一个xuad文件,放在/etc/sysconfig/目录下,内容如下:
OPTIONS=Start
这样我们相当于定义了一个xuad服务。
首先确认nginx是否启动。
systemctl status nginx.service
初识systemd-使用篇
通过上图可以看到运行状态为inactive,很明显nginx未运行。
接下来我们来启动xuad.service这个服务,前面有讲过.service可以省略。

systemctl start xuad
systemctl status xuad  # 查看xuad服务状态

初识systemd-使用篇
很明显xuad服务已经成功启动,并且成功打印Start字符串,接下来我们确认一下nginx是否已经启动。
ps aux | grep nginx
初识systemd-使用篇
通过上图可以确认nginx已经成功启动。
接下来我们重启xuad.service这个服务,看看结果如何。

systemctl restart xuad
systemctl status xuad  # status只能看到启动成功后的状态
journalctl -u xuad.service -f  # 通过日志来看重启结果

初识systemd-使用篇
通过上图我们看到成功打印stop和Start两个字符串,说明重启nginx成功。
最后我们停止xuad.service,看看结果如何。

systemctl stop xuad
systemctl status xuad

初识systemd-使用篇
运行状态已经变成inactive(未运行),并且成功打印stop字符串,说明停止nginx成功。这时执行ps aux | grep nginx应该看不到nginx的进程了。
本文所有命令参考文献:
http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html
在此感谢这位作者,谢谢!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!