1. Linux初始化系统: init system
Linux操作系统的启动首先从BIOS开始,接下来进入boot loader,由bootloader载入内核,进入内核初始化。内核初始化的最后一步就是启动pid为1的ini进程。这个进程是系统的第一个进程。它负责产生其他所有用户进程。init system就是通过控制init进程来实现它的各种功能的。常见的init system有sysvinit何systemd。
sysvinit: System V init
在 Linux 主要应用于服务器和 PC 机的时代,SysVinit 运行非常良好,概念简单清晰。它主要依赖于 Shell 脚本,这就决定了它的最大弱点:启动太慢。在很少重新启动的 Server 上,这个缺点并不重要。而当 Linux 被应用到移动终端设备的时候,启动慢就成了一个大问题。为了更快地启动,人们开始改进 sysvinit,于是systemd应运而生。
Sysvinit运行级别:runlevel
每种Linux发行版对runlevel的定义都不一样,通用的只有3个:
- 0 关机
- 1 单用户模式
- 6 重启
RedHat额外定义了runlevel 3和5。
- 3 将系统初始化为字符界面的 shell 模式
- 5 将系统初始化为 GUI 模式
RedHat中,Target和runlevel的对应关系:
Sysvinit 运行级别 | Systemd 目标 | 备注 |
---|---|---|
0 | runlevel0.target, poweroff.target | 关闭系统 |
1, s, single | runlevel1.target, rescue.target | 单用户模式 |
2, 4 | runlevel2.target, runlevel4.target, multi-user.target | 用户定义/域特定运行级别。默认等同于 3 |
3 | runlevel3.target, multi-user.target | 多用户,非图形化。用户可以通过多个控制台或网络登录 |
5 | runlevel5.target, graphical.target | 多用户,图形化。通常为所有运行级别 3 的服务外加图形化登录 |
6 | runlevel6.target, reboot.target | 重启 |
emergency | emergency.target | 紧急 Shell |
2. Systemd
Systemd是Linux系统中最新的初始化系统(init system),Systemd的很多概念来源于苹果Mac OS操作系统上的launchd。
Systemd主要的设计目标是克服sysvinit启动速度慢的问题,在sysvinit中,每个启动项目都由一个独立的脚本负责,由sysVinit顺序的串行的调用,无论各项目之间是否有依赖。而Systemd可以实现多个启动项目并行启动,无论各项目之间是否有依赖。
2.1 Systemd 采用 Linux 的 Cgroup 特性跟踪和管理进程的生命周期
init 系统的一个重要职责就是负责跟踪和管理服务进程的生命周期。它不仅可以启动一个服务,也必须也能够停止服务。这看上去没有什么特别的,然而在真正用代码实现的时候,您或许会发现停止服务比一开始想的要困难。
systemd 利用了 Linux 内核的特性即 CGroup 来完成跟踪的任务。当停止服务时,通过查询 CGroup,systemd 可以确保找到所有的相关进程,从而干净地停止服务。
CGroup 已经出现了很久,它主要用来实现系统资源配额管理。CGroup 提供了类似文件系统的接口,使用方便。当进程创建子进程时,子进程会继承父进程的 CGroup。因此无论服务如何启动新的子进程,所有的这些相关进程都会属于同一个 CGroup,systemd 只需要简单地遍历指定的 CGroup 即可正确地找到所有的相关进程,将它们一一停止即可。
2.2 日志服务
systemd 自带日志服务 journald,该日志服务的设计初衷是克服现有的 syslog 服务的缺点。比如:
- syslog 不安全,消息的内容无法验证。每一个本地进程都可以声称自己是 Apache PID 4711,而 syslog 也就相信并保存到磁盘上。
- 数据没有严格的格式,非常随意。自动化的日志分析器需要分析人类语言字符串来识别消息。一方面此类分析困难低效;此外日志格式的变化会导致分析代码需要更新甚至重写。
Systemd Journal 用二进制格式保存所有日志信息,用户使用 journalctl 命令来查看日志信息。无需自己编写复杂脆弱的字符串分析处理程序。Systemd Journal 的优点如下:
- 简单性:代码少,依赖少,抽象开销最小。
- 零维护:日志是除错和监控系统的核心功能,因此它自己不能再产生问题。举例说,自动管理磁盘空间,避免由于日志的不断产生而将磁盘空间耗尽。
- 移植性:日志 文件应该在所有类型的 Linux 系统上可用,无论它使用的何种 CPU 或者字节序。
- 性能:添加和浏览 日志 非常快。
- 最小资源占用:日志 数据文件需要较小。
- 统一化:各种不同的日志存储技术应该统一起来,将所有的可记录事件保存在同一个数据存储中。所以日志内容的全局上下文都会被保存并且可供日后查询。例如一条固件记录后通常会跟随一条内核记录,最终还会有一条用户态记录。重要的是当保存到硬盘上时这三者之间的关系不会丢失。Syslog 将不同的信息保存到不同的文件中,分析的时候很难确定哪些条目是相关的。
- 扩展性:日志的适用范围很广,从嵌入式设备到超级计算机集群都可以满足需求。
- 安全性:日志 文件是可以验证的,让无法检测的修改不再可能。
2.3 Systemd 的并发启动原理
- 解决 socket 依赖 绝大多数的服务依赖是套接字依赖。比如服务 A 通过一个套接字端口 S1 提供自己的服务,其他的服务如果需要服务 A,则需要连接 S1。因此如果服务 A 尚未启动,S1 就不存在,其他的服务就会得到启动错误。所以传统地,人们需要先启动服务 A,等待它进入就绪状态,再启动其他需要它的服务。Systemd 认为,只要我们预先把 S1 建立好,那么其他所有的服务就可以同时启动而无需等待服务 A 来创建 S1 了。如果服务 A 尚未启动,那么其他进程向 S1 发送的服务请求实际上会被 Linux 操作系统缓存,其他进程会在这个请求的地方等待。一旦服务 A 启动就绪,就可以立即处理缓存的请求,一切都开始正常运行。
那么服务如何使用由 init 进程创建的套接字呢?
Linux 操作系统有一个特性,当进程调用 fork 或者 exec 创建子进程之后,所有在父进程中被打开的文件句柄 (file descriptor) 都被子进程所继承。套接字也是一种文件句柄,进程 A 可以创建一个套接字,此后当进程 A 调用 exec 启动一个新的子进程时,只要确保该套接字的 close_on_exec 标志位被清空,那么新的子进程就可以继承这个套接字。子进程看到的套接字和父进程创建的套接字是同一个系统套接字,就仿佛这个套接字是子进程自己创建的一样,没有任何区别。
这个特性以前被一个叫做 inetd 的系统服务所利用。Inetd 进程会负责监控一些常用套接字端口,比如 Telnet,当该端口有连接请求时,inetd 才启动 telnetd 进程,并把有连接的套接字传递给新的 telnetd 进程进行处理。这样,当系统没有 telnet 客户端连接时,就不需要启动 telnetd 进程。Inetd 可以代理很多的网络服务,这样就可以节约很多的系统负载和内存资源,只有当有真正的连接请求时才启动相应服务,并把套接字传递给相应的服务进程。
和 inetd 类似,systemd 是所有其他进程的父进程,它可以先建立所有需要的套接字,然后在调用 exec 的时候将该套接字传递给新的服务进程,而新进程直接使用该套接字进行服务即可。
- 解决 D-Bus 依赖 D-Bus 是 desktop-bus 的简称,是一个低延迟、低开销、高可用性的进程间通信机制。它越来越多地用于应用程序之间通信,也用于应用程序和操作系统内核之间的通信。很多现代的服务进程都使用D-Bus 取代套接字作为进程间通信机制,对外提供服务。比如简化 Linux 网络配置的 NetworkManager 服务就使用 D-Bus 和其他的应用程序或者服务进行交互:邮件客户端软件 evolution 可以通过 D-Bus 从 NetworkManager 服务获取网络状态的改变,以便做出相应的处理。
D-Bus 支持所谓"bus activation"功能。如果服务 A 需要使用服务 B 的 D-Bus 服务,而服务 B 并没有运行,则 D-Bus 可以在服务 A 请求服务 B 的 D-Bus 时自动启动服务 B。而服务 A 发出的请求会被 D-Bus 缓存,服务 A 会等待服务 B 启动就绪。利用这个特性,依赖 D-Bus 的服务就可以实现并行启动。
- 解决文件系统依赖
系统启动过程中,文件系统相关的活动是最耗时的,比如挂载文件系统,对文件系统进行磁盘检查(fsck),磁盘配额检查等都是非常耗时的操作。在等待这些工作完成的同时,系统处于空闲状态。那些想使用文件系统的服务似乎必须等待文件系统初始化完成才可以启动。但是 systemd 发现这种依赖也是可以避免的。
Systemd 参考了 autofs 的设计思路,使得依赖文件系统的服务和文件系统本身初始化两者可以并发工作。autofs 可以监测到某个文件系统挂载点真正被访问到的时候才触发挂载操作,这是通过内核 automounter 模块的支持而实现的。比如一个 open()系统调用作用在"/misc/cd/file1"的时候,/misc/cd 尚未执行挂载操作,此时 open()调用被挂起等待,Linux 内核通知 autofs,autofs 执行挂载。这时候,控制权返回给 open()系统调用,并正常打开文件。
Systemd 集成了 autofs 的实现,对于系统中的挂载点,比如/home,当系统启动的时候,systemd 为其创建一个临时的自动挂载点。在这个时刻/home 真正的挂载设备尚未启动好,真正的挂载操作还没有执行,文件系统检测也还没有完成。可是那些依赖该目录的进程已经可以并发启动,他们的 open()操作被内建在 systemd 中的 autofs 捕获,将该 open()调用挂起(可中断睡眠状态)。然后等待真正的挂载操作完成,文件系统检测也完成后,systemd 将该自动挂载点替换为真正的挂载点,并让 open()调用返回。由此,实现了那些依赖于文件系统的服务和文件系统本身同时并发启动。
当然对于"/“根目录的依赖实际上一定还是要串行执行,因为 systemd 自己也存放在/之下,必须等待系统根目录挂载检查好。
不过对于类似/home 等挂载点,这种并发可以提高系统的启动速度,尤其是当/home 是远程的 NFS 节点,或者是加密盘等,需要耗费较长的时间才可以准备就绪的情况下,因为并发启动,这段时间内,系统并不是完全无事可做,而是可以利用这段空余时间做更多的启动进程的事情,总的来说就缩短了系统启动时间。
- Systemd 的使用
开发人员需要了解 systemd 的更多细节。比如您打算开发一个新的系统服务,就必须了解如何让这个服务能够被 systemd 管理。这需要您注意以下这些要点:
- 后台服务进程代码不需要执行两次派生来实现后台精灵进程,只需要实现服务本身的主循环即可。
- 不要调用 setsid(),交给 systemd 处理
- 不再需要维护 pid 文件。
- Systemd 提供了日志功能,服务进程只需要输出到 stderr 即可,无需使用 syslog。
- 处理信号 SIGTERM,这个信号的唯一正确作用就是停止当前服务,不要做其他的事情。
- SIGHUP 信号的作用是重启服务。
- 需要套接字的服务,不要自己创建套接字,让 systemd 传入套接字。
- 使用 sd_notify()函数通知 systemd 服务自己的状态改变。一般地,当服务初始化结束,进入服务就绪状态时,可以调用它。
Systemd和sysvinit命令的对照表:
Sysvinit 命令 | Systemd 命令 | 备注 |
---|---|---|
service foo start | systemctl start foo.service | 用来启动一个服务 (并不会重启现有的) |
service foo stop | systemctl stop foo.service | 用来停止一个服务 (并不会重启现有的) |
service foo restart | systemctl restart foo.service | 用来停止并启动一个服务 |
service foo reload | systemctl reload foo.service | 当支持时,重新装载配置文件而不中断等待操作 |
service foo condrestart | systemctl condrestart foo.service | 如果服务正在运行那么重启它 |
service foo status | systemctl status foo.service | 汇报服务是否正在运行 |
ls /etc/rc.d/init.d/ | systemctl list-unit-files –type=service | 用来列出可以启动或停止的服务列表 |
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 –type=service | 输出在各个运行级别下服务的启用和禁用情况 |
chkconfig foo –list | ls /etc/systemd/system/*.wants/foo.service |
用来列出该服务在哪些运行级别下启用和禁用 |
chkconfig foo –add | systemctl daemon-reload | 当您创建新服务文件或者变更设置时使用 |
telinit 3 | systemctl isolate multi-user.target | 改变至多用户运行级别 |
systemd的电源管理功能:
- systemctl reboot 重启机器
- systemctl poweroff 关机
- systemctl suspend 待机
- systemctl hibernate 休眠
- systemctl hybrid-sleep 混合休眠模式(同时休眠到硬盘并待机)
- 删掉missing systemd units
systemctl list-units总能查到已经删掉的服务,这个时候可以使用 systemctl reset-failed 清除这些服务
2.4 systemd和ulimit的关系
systemd默认的LimitNOFILE配置,不受/etc/security/limits.conf文件的影响,/etc/security/limits.conf文件只能影响从shell上执行的命令行的句柄数。
/etc/security/limits.conf文件设置了通过PAM登录的用户的资源限制,这对系统服务(systemd service)的资源限制没有影响。
|
|
systemd默认的LimitNOFILE配置,受/etc/systemd/system.conf文件的影响,可以在这个文件上配置DefaultLimitNOFILE和DefaultLimitNPROC来控制进程可以打开的最大句柄数和用户可以创建的最大进程数。
/etc/systemd/system.conf文件默认所有配置都是注释了的,这个时候,systemd的nofile和nproc的值,取决于systemd的版本号,systemd 219版本是4096,systemd 243版本是512*1024=524288
,这些值是写死在systemd的源代码里面的。
修改完system.conf文件后,需要执行 systemctl daemon-reexec 命令使配置生效。
可以通过systemctl show 服务名.service –property=LimitNOFILE 来查nofile的值。
或者,在service文件中,添加LimitNOFILE和LimitNPROC配置来控制。
查看进程的软链接和硬链接: cat /proc/<pid>/limits | grep "Max open files"
2.5 systemd和sysvinit之间的关系
案例: iptables.service是systemd服务,但是却可以使用service iptables save
命令,这是为什么?
只要是LSB(Linux Standard Base)定义的基本动作(start, stop, restart, try-restart, reload, force-reload, status),systemctl命令就可以转换成service命令。
例如: systemctl status nginx,可以转换成 service nginx status
save并不是base LSB actions,为何可以执行 service iptables save
呢?
/usr/libexec/initscripts/legacy-actions 目录在某些 Linux 发行版(尤其是基于 Red Hat 的系统,如 RHEL、CentOS)中用于存放与传统 SysVinit 初始化脚本兼容的辅助脚本和动作处理程序。
所以,在 /usr/libexec/initscripts/legacy-actions 目录下创建 iptables/save 文件,就能实现 service iptables save命令。
其中 /usr/libexec/initscripts/legacy-actions/iptables/save 文件的内容如下:
#!/bin/bash
exec /usr/libexec/iptables/iptables.init save
注意,该文件必须有可执行权限。
本文发表于 0001-01-01,最后修改于 0001-01-01。
本站永久域名「 jiavvc.top 」,也可搜索「 后浪笔记一零二四 」找到我。