blog:linux_system_manage:initsystem

linux init system


init system

init进程 PID为1,是除idle进程外另一个比较特殊的进程。由内核通过kernelthread产生的第一个进程,开始 在内核态执行,然后通过kernelexecve执行执行用户态的init程序。init 以守护进程方式存在,是除idle进程 外的所有进程的祖先。

仅仅将内核运行起来是没有实际用途的,必须由init系统启动一些服务.比如启动shell后便有由人机交互,比 如启动X图形系统以便提供人机界面.这里字符界面的shell或X系统都是一种预设的运行模式。

常见的init system有sysvinit, Upstart, systemd

sysvinit

system V风格的init系统, 已经风行了几十年的UNIX init系统,一直被各linux发行版采用. 优点:

1. 开发人员只需要编写启动和停止脚本,操作简单2. 执行顺序确定,安造启动文件数字的大小顺序执行,容易定位问题.

缺点:

1. 由于串行执行,导致系统启动速度慢低。2. sysvinit必须一次性把所有可能用到的服务都启动起来,由于动态设备加载的新特性导致sysvinit不能胜任这类需求

使用runlevel来定义预设的运行模式, 通过检查/etc/inittab文件中是否有'initdefault'来决定runlevel. 通常由8种runlevel,

��0到6和S. 每种linux发行版对runlevel的定义都不太一样,但0, 1, 6这三种模式的定义比较一致:

0: 关机 1: 单用户模式 2: 重启

Readhat定义的runlevel:

  runlevel 3为:将系统初始化为字符界面的shell模式
  runlevel 5为:将系统初始化为GUI模式 
  
  1. 首先读取/etc/inittab获取一些配置信息
    * runlevel * 启动getty和虚拟控制台 * …
    2. 运行/etc/rc.d/rc.sysinit
    执行一些重要的系统初始化任务
    * 激活udev和selinux * 设置定义在/etc/sysctl.conf中的参数 * 设置系统时钟 * 加载keymaps * 时能交换分区 * 设置hostname * 根分区检查,挂载文件系统 * 清除过期的locks和PID文件
    3. 运行/etc/rc.d/rc
    根据不同的runlevel, rc脚本将打开该runlevel对应的/etc/rcX.d目录,运行该目录下的所有启动脚本 启动脚本的命名规则如下: 以S开头的脚本是启动时应该运行的脚本,S后面的数字定义了这些脚本的执行顺序 以K开头的脚本将在系统关闭时调用,K后面的数字定义了他们的执行顺序 这些脚本都是软链接,真实的脚本放在/etc/init.d目录下.
    4. 运行/etc/rc.d/rc.local
    用户个性化设置的地方,可以将用户想设置和启动的东西放到这里
    ## 管理和控制功能

sysvinit软件包包含了一系列控制启动,运行和关闭所有其他程序的工具

command name description
—— ——
halt 停止系统
init sysvinit本身的进程实体
last 搜索/var/log/wtmp, 显示自从这个文件建立以来,所有用户的登陆情况
lastb 搜索/var/log/btmp,显示所有失败登陆的企图
pidof 找出制定程序的PID
poweroff 等于shutdown -h -p 或 init 0 关闭系统
reboot 等于shutdown -r 或者 init 6 重启系统
runlevel 显示运行模式
shutdown 以较安全的方式终止系统,所有已登陆的用户等会收到系统将要终止的通知,且不允许新的登陆
wall 向所有有信息权限的用户发送消息

UpStart

http://www.ibm.com/developerworks/cn/linux/1407_liuming_init2/

systemd

systemd是最新的初始化系统(init), 主要的设计目标: 克服sysvinit的缺点,提高系统的启动速度 从RHEL6开始使用UpStart替换了sysvinit 从RHEL7开始使用Systemd替换了UpStart 从Ubuntu15.04开始使用Systemd替换了UpStart 越来越多的发行版开始或准备切换到Systemd, 但同时systemd又是一个倍受争议的init system

多数反对者认为:

  systemd是一大堆复杂的高度耦合的二进制组成,这违反了UNIX哲学:'做一件事情,并把它做好'
  它超出了一个init程序的职责范围,因为它还有电源管理,设备管理,挂载管理,cron,syslog,网络配置,
  登陆/配置管理... ... 
  

为了减少系统启动时间,systemd的目标是:
* 尽可能启动更少的进程 * 尽可能将更多的进程并行启动

    ----
T1 -| JobA
    ----
      v   
    ----
T2 -| JobB
    ----                                     ----            ----        ----
      v                             T1      -| JobA         -| JobE     -| JobF
    ----                                     ----            ----        ----
T3 -| JobC                                     v               v
    ----                     |\              ----            ----
      v         -------------- \    T2      -| JobB         -| JobG
    ----        -------------- /             ----            ----
T4 -| JobD                   |/            /      \
    ----                                ----       ----
      v                             T3 -| JobC    -| JobD
    ----                                ----       ----
T5 -| JobE
    ----
      v 
    ----
T6 -| JobF
    ----
      v 
    ----
T7 -| JobG
    ----

假设有7个不同的启动项目,比如JobA, JobB … 在sysvinit中,每个项目都由一个独立的脚本负责,由sysvinit顺序地串行地调用 因此总的启动时间是: T1 + T2 + T3 + T4 + T5 + T6 + T7.

其中有一些任务与以来关系,比如A,B,C,D. 而JobE 和 JobF却与A,B,C,D无关,所以UpStart可以并发的执行任务(E, F, {A,B,C,D}) 使得总启动时间减少为 T1 + T2 + T3 在UpStart 中,有依赖关系的服务还是必须先后启动。比如任务 A,B,(C,D)因为存在依赖关系,所以在这个局部,还是串行执行。

Systemd 能够更进一步提高并发性,即便对于那些 UpStart 认为存在相互依赖而必须串行的服务也可以并发启动。从而实现如下图所示的并发启动过程:

    ----
T7 -| JobA JobB JobC JobD JobE JobF JobG
    ----

所有的任务都同时并发执行,总的启动时间被进一步降低为 T1。

sysvinit启动的时候会将所有可能用到的后台进程全部启动 某些服务可能在很长的一段时间内都不会被使用,例如CUPS(Common Unix Printing System). 打印服务通常不会用到. 花费在启动这些服务上的时间是不必要的,对系统资源也是一种浪费。

init需要管理服务进程的生命周期。它不仅要启动一个服务,还要能停止服务。但是停止服务会比较困难,因为需要准备找到服务进程的PID才能停止服务. 服务进程一般作为daemon进程运行。该进程有可能会fork一次,也有可能会fork两次. 在UpStart中需要正确配置expect小节,UpStart通过对fork系统调用进程计数,从而获取真正的daemon进程的PID

         fork        fork
    P1 ------> P1' ------> P1''

如果Upstart找错了,例如将P1'作为那么服务进程,那么停止服务的时候UpStart会试图Kill P1'进程,而真正的服务程序却仍然在运行。 SystemdCGroup在跟踪任务。创建子进程时,会继承父进程的CGroup,因此无论如何启动进程,所有这些相关进程都会同属一个CGroup, 所以systemd可以正确地找到所有相关进程.

系统初始化需要做的事情非常多。 需要启动后台服务,比如启动sshd服务 需要做配置工作,比如挂载文件系统

启动过程中的每一步都被systemd抽象为一个配置单元(unit), 可以认为一个服务是一个配置单元,一个挂载点是一个配置单元,一个交换分区的配置是一个配置单元.

systemd的配置单元有如下类型:

  • service: 代表一个后台服务进程,比如sshd. 这是最常用的一类
  • socket: systemd目前支持stream, 数据报和连续包的AFINET,AFINET6,AF_UNIX socket 每个套接字单元都由一个对应的服务配置单元,相应的服务会在第一个“连接进入套接字时就会启动”
  • device: 封装一个存在与Linux设备树中的设备,每一个使用udev标记的设备都将会在systemd中作为一个配置单元出现.
  • mount: 封装文件系统层次的一个挂载点,systemd将会这个挂载点进程监控和管理。比如可以在启动时自动将其挂载。在某些条件下自动卸载. systemd将/etc/fstab中的条目都转换为挂载点
  • automount 封装系统结构层次中的一个自挂载点。当该自动挂载点被访问时,systemd执行挂载点中定义的挂载行为
  • target 为其他配置单元进程逻辑分组,他们本身不做什么,只是引用其他配置单元。这样就可以对配置单元做一个统一的控制. 通过target可以实现运行级别的概念.比如想进入图形化模式,需要运行许多服务和配置命令。这些操作都由一个个配置单元表示. 将所有这些配置单元组合成一个target,就表示需要将这些配置单元全部执行一遍
  • timer 定时器配置单元,取代atd,cron等传统的定时任务
    每个配置单元都一个对应的配置文件,例如: “/usr/lib/systemd/system/sshd.service”

监视和控制systemd的主要命令是systemctl

查看激活的unit

  systemctl list-units

查看运行失败的unit

  systemctl --failed

查看所有已安装服务

  systemctl list-unit-files
  systemctl list-units --type=target

在systemd中所有服务都可以并发启动,服务程序之间的相互依赖关系可以分为3种类型:

  socket依赖,D-Bus依赖,文件系统依赖
  

绝大多数服务依赖是套接字依赖。比如服务A通过套接字端口S1提供自己的服务,其他服务如果需要服务A,则需要连接S1, 因此服务A没有启动,S1就不存在,其他服务就会启动失败。

如果预先把S1创建好,那么其他所有的服务都可以同时启动,而无需等待服务A来创建S1了, 如果服务A没有启动,那么其他进程向服务A 发出的服务请求实际上会被系统缓存,其他进程会在这个请求的地方等待。一旦服务A启动,就可以立即处理缓存的请求。

那么服务如何使用由init创建的套接字呢?

当使用fork或者exec创建子进程后,所有在父进程中被打开的文件描述符都被子进程继承(前提是清空该文件描述符的closeonexec标志)。

inetd这个服务进程使用了这样的特性,inetd负责监控一些套接字端口,比如telnet.当该端口有连接请求时,inetd才会启动telnetd进程. 并把所有连接的套接字传递给新的telnetd进程处理。这样当系统没有telnet客户端连接时就不需要启动telnetd进程。

和inetd类似,systemd也是其他所有进程的父进程,可以先建立所有需要的套接字,然后在启动服务进程时将该套接字传递给新的服务进程。

D-Bus 是Desktop-Bus的简称,是一个低延时、低开销、高可用性的进程间通信机制, 很多服务进程都使用D-Bus取代套接字作为进程间通信的机制。 比如NetworkManager就使用D-Bus和其他应用程序或者服务进行交互。邮件客户端软件evolution可以通过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参考了autofs的设计,使得依赖文件系统的服务和文件系统的初始化可以并行工作。 autofs检测到某个挂载点真正被访问的时候才出发挂载操作。通过内核automounter模块的支持而实现。 例如: open “/mnt/CD/file”的时候,挂载点/mnt/CD/并为挂载,此时open系统调用被挂起, 内核通知autofs,autofs执行挂载。挂载完成后,open解除阻塞,继续执行.

systemd继承了autofs的实现,对于系统中的挂载点,例如/home,当系统启动的时候systemd为其创建一个临时挂载点。在这个时刻真正的设备尚未启动好。真正的挂载操作 还没有执行。文件系统检测也还没有完成。那些依赖该目录的进程已经可以启动,他们的open操作为内置的autofs捕获,open操作被挂起。 当真正的挂载操作完成,文件系统也检测完成后,systemd将该自动挂载点替换为真正的挂载点。

实际上对根目录“/“的挂载还是需要串行执行,因为systemd也存放在/下,必须等待根目录挂载检查好。

对于/home这类挂载点,这种并发可以提高启动速度,尤其是对于远程的NFS节点等需要耗费较长时间才可以准备就绪的情况。

  • 后台服务进程代码不需要执行2次派生来实现daemon进程,只需要实现服务本身的主循环即可.
  • 不要调用setsid, 交给systemd处理
  • 不再需要维护pid文件
  • systemd提供了日志功能,服务进程只需要输出到stderr即可,无需使用syslog
  • 处理信号SIGTERM, 对这个信号的处理是停止当前服务
  • 处理信号SIGHUP, 对这个信号的处理是重启服务
  • 需要套接字的服务,不要自己创建套接字,让systemd传入套接字
  • 使用sd_notify函数通知systemd服务自己的状态改变,一般当服务初始化完成,进入就绪状态时,可以调用它

一个单元配置文件可以描述如下内容之一:

系统服务(.service)、挂载点(.mount)、sockets(.sockets) 、系统设备(.device)、
交换分区(.swap)、文件路径(.path)、启动目标(.target)、由 systemd 管理的计时器(.timer)

使用 systemctl 控制单元时,通常需要使用单元文件的全名,包括扩展名(例如 sshd.service)。

  • 如果无扩展名,systemctl 默认把扩展名当作 .service。例如 netcfg 和 netcfg.service 是等价的。
  • 挂载点会自动转化为相应的 .mount 单元。例如 /home 等价于 home.mount。
  • 设备会自动转化为相应的 .device 单元,所以 /dev/sda2 等价于 dev-sda2.device。

所有可用的Unit文件存放在 /usr/lib/systemd/system/ 和 /etc/systemd/system/ 目录(后者优先级更高) 每个unit配置文件都需要由一个指定类型的section [Service] for a service unit [Socket] for a socket unit [Mount] for a mount unit [Automount] for a automount unit [Timer] for a timer unit

device unit 不需要单独的[Device]字段,通常在Unit和Install中配置即可

可以通过正确编写配置文件来解决依赖关系。 例如:

  单元A要求单元B在单元A启动之前运行,需要想单元A的配置文件的 [Unit] 字段中添加Requires=B和After=B即可。
  若此依赖关系是可选的,则可添加Wants=B和After=B, 注意Wants=和Requires=并不意味着After, 即如果
  After没有指定,则A和B将被并行启动。

依赖关系通常被用在服务(service)上,而不是目标target上。

编写自定义的 service 文件时,可以选择几种不同的服务启动方式。可通过配置[Service] 段中的 Type= 参数进行设置。 Type默认值为simple

simple:

systemd认为该服务将立即启动。服务进程不会fork。 如果该服务要启动其他服务,不要使用此类型启动,除非该服务是socket激活型。

forking:

systemd认为当该服务进程fork,且父进程退出后服务启动成功。 对于常规的守护进程(daemon),除非你确定此启动方式无法满足需求,使用此类型启动即可。 使用此启动类型应同时指定 PIDFile=,以便systemd能够跟踪服务的主进程。

oneshot:

适用于只执行一项任务、随后立即退出的服务。 可能需要同时设置 RemainAfterExit=yes 使得 systemd 在服务进程退出之后仍然认为服务处于激活状态。

notify:

与Type=simple 相同,但约定服务会在就绪后向 systemd 发送一个信号。 这一通知的实现由 libsystemd-daemon.so 提供。

dbus:

若以此方式启动,当指定的 BusName 出现在DBus系统总线上时,systemd认为服务就绪。

idle:

和Type=simple 类似,但是,它会在所有任务(jobs)都处理后,才会执行。 这种方式可以避免不同shell服务输出到控制台的状态信息出现交错的情况。

ExecStart 指定启动单元的命令和脚本 ExecStartPre 指定在ExecStart之前用户自定义执行的脚本 ExecStartPost 指定在ExecStart之后用户自定义执行的脚本 ExecStop 指定单元停止时执行的命令或者脚本 ExecReload 指定单元重新加载是执行的命令或者脚本 RemainAfterExit 如果设置这个选择为真,服务会被认为是在激活状态,即使所以的进程已经退出,默认的值为假,

                这个选项只有在Type=oneshot时需要被配置。

例如: samba nmb服务的配置单元文件,服务配置单元文件以service结尾 cat /lib/systemd/system/nmb.service

[Unit]
Description=Samba NMB Daemon
After=syslog.target network.target

[Service]
Environment=KRB5CCNAME=FILE:/run/samba/krb5cc_samba
Type=notify
NotifyAccess=all
PIDFile=/run/nmbd.pid
EnvironmentFile=-/etc/sysconfig/samba
ExecStart=/usr/sbin/nmbd $NMBDOPTIONS
ExecReload=/usr/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target

mount unit的命名规则: 例如挂载点为/home/lennart, 那么mount unit文件应该为: home-lennart.mount mount unit 定义了What Where Type这三项

What:

  设备节点

Where:

  挂载点

Type:

  文件系统类型

Options:

  mount options, 使用逗号分隔
[Unit]
Description=Temporary Directory
Documentation=man:hier(7)
Documentation=http://www.freedesktop.org/wiki/Software/systemd/APIFileSystems
ConditionPathIsSymbolicLink=!/tmp
DefaultDependencies=no
Conflicts=umount.target
Before=local-fs.target umount.target

[Mount]
What=tmpfs
Where=/tmp
Type=tmpfs
Options=mode=1777,strictatime
  • blog/linux_system_manage/initsystem.txt
  • 最后更改: 2022/01/09 22:59
  • 127.0.0.1