モダン initシステムの歴史(1992-2015)

本文書は、Unixライクなシステムにおけるプロセスマネジメント、監視、init(8)の歴史的解説と「ポピュラー文化」的解釈の問題点多さについて述べる。これにより、機能セットをめぐる混乱や、信頼性の高いプロセスマネジメントをUnix上で構築する方法についての誤解が生じ、この分野はすべての扇動者の恰好のターゲットとなっている。

GNU/Linuxに関しては、最も一般的な年代記はまずはsysvinitである.Longtime中使用されたが、Appleがlaunchdを作成するに至るまで、何も手は打たれなかった。Ubuntuは затем (同情する人間によって、あるいはNIHによって) Upstartを作成した just to block it with CLA.その後2010年にsystemdがすべてを変えた。Oh、それと、GentooはOpenRCと呼ばれるものを開発した。

実際には、イベントとソリューションの時系列是很难理解往往这就导致人们对既存のソリューションの新しさが過大評価される。最も重要なのは、良好な歴史的理解がなければ、過去の事例から Mistakes を犯し、最終的に不必 要なデザインで同じ Mistakes を犯すことになる可能性があることだ。

本文書の目的は、「モダン」initシステムの歴史を記録することである。「モダン」とは、古典的なBSDとSystem Vの初期化和服务管理の改善を試みるために広く使用されている anything と定義する。

本文書は技术的な 判断 を下すことを目的とせず、むしろシステムを研究に興味を持つ人、特にUnixプロセスの監視に携わる人々が、既存の技術のクイックリファレンスとして 사용할 수 있도록、完了したことを単純に記録することも目的としている。同様に、この問題のスペースにおけるソリューションの歴史をより casual な読者に知ってもらい、この controversial な問題に対する視界を広げることを目的としている。

深入的な技術分析を行うのではなく、一般的な概要と詳細情報のリンクを提供する。

(始める前に、本文書はsupervisord、monit、Circus、God、bluepill、Eyeなど、init(8)デーモンに収容されない基本的なスーパーバイザーはカバーしない。これらはWeb開発者の中で人気があり、PythonやRubyなどの言語で書かれているものが多いため、initシステムではなく、通常、新規性もない。)

IBMシステムリソースコントローラー(1992)

IBM AIXのために書かれた最初のモダンinitシステムかもしれない。現在よく知られている監視セマンティクスを最初に遵守した,也就是服務不會自己守護,而是由スーパーバイザー自身が守護する。

также它是第一个不仅在デーモン上运行,而是「サブシステム」 - 可以是デーモンと補助プログラムのグループ。13年后、Solaris SMF将重用相同的原则。

SRC是一个完全包含的框架,具有启动、停止、重新启动和获取子系统状态的工具。

SRC不依赖脚本甚至是传统的服务配置,而是依赖使用命令在其自己的对象数据库中注册子系统和服务器。同样,SMF将通过引入用于存储运行时数据和持久性配置的服务配置存储库来回应这一点,尽管仍然以XML清单的形式保留用户可见的配置文件。

daemontools(1997)+衍生物(1997-2015)

マクスウェルのUnixプロセスマネジメント式。

ダニエル・J・バーンスタインが1997年に最初に发行,后来它继续发挥巨大的影响力,并且经常习惯于今天。它激发了一些衍生品并直接影响了那些偏离其模型的系统,如minit、ninit和depinit。

事实上,所有init系统都可以并且已经以一种或另一种形式映射为daemontools的超集(例如systemd => nosh)。

请参阅JdeBP的「守护神之家」,Wayne Marshall的「djb方式」中的daemontools部分以及伯恩斯坦自己的页面,以获得比我所能给出的更好的概述。

rc.d(2000)

rc.d是一个模块化的、基于依赖性的initscript框架,最初由NetBSD于2000年左右采用,主要由Luke Mewburn设计。它已经扩展到其他BSD,取代旧的、完全平坦的/etc/rc。

在rc.d中,init(8)守护进程执行/etc/rc,它运行rcorder(8)程序,用于计算/etc/rc.d/中对initscripts的排序依赖性。Initscripts以一种干净的标准格式编写,它基于一个名为/etc/rc.subr的公共子程序文件,所有的initscripts都来自该文件。反过来,从/etc/rc.conf文件(由/etc/rc提供的shell脚本)处理启用/禁用、命名服务配置和其他模块的服务的全局系统和服务启动行为。简单的键值对。

rc.d设法保持高度的可配置性和灵活性,最常见的关于基于System V initcript的系统的痛点被彻底根除,但它再次被限制为基本的设计管理框架。Arch Linux在2012年迁移到systemd之前使用了类似rc.d的设置。

simpleinit、jinit和need(8)概念(2001-3)

理查德·古奇在他2002年的论文「Linux Boot Scripts」中设想了SysV和BSD之间的中间地带。

简而言之,它基于/sbin/init.d中的简短脚本的平面目录,提供服务并通过使用两个小实用程序进行同步:need(8)和provide(8),它们依次符号链接到initctl的argv0

这两个原语旨在废弃运行级别,支持状态转换并提供依赖关系管理。provide(8)基于名称注册服务,并且need(8)根据名称是否存在以阻塞方式启动或停止服务。show(8)用于状态列表。否则,保留标准SysV inittab(5)。另请参见initctl(8)联机帮助页。

它确实获得了一些适度的成功,包括在几个嵌入式或实验性项目中,以及作为基于依赖性的init的早期示例。它至今仍被至少一个活跃发行版源码Mage GNU/Linux使用。

simpleinit直接激发了John Fremlin在C++中编写了一个名为jinit的衍生产品,该产品异常地将System V消息队列用于其IPC。它自2003年以来一直没有更新。

minit(2001-2)

由Fefe(Felix von Leitner)设计,它可以 被描述为倒置(内生)daemontools。

daemontools为每个服务生成一个单独的监督过程,minit将其统一到一个中央msvc主管。它还有一个非常基本的依赖系统,通过每个服务目录有一个「依赖」文件,实际上只是一种计算服务排序的方法。它可以同步和异步启动服务。

它非常小,占地面积极小,旨在链接到由同一作者制作的dietlibc。一个2004年呈现在Linux的Kongress大会进入 behind MINIT原则的更多细节。

Nikola Vladov后来在2007年左右开始创建ninit,它通过更多服务配置选项扩展了minit,并增加了sysvinit兼容性。

depinit(2002)

depinit,由理查德·莱特曼于2002年左右撰写,是一个自称为「[并入]来自sysvinit、simpleinit、daemontools和make」的思想的系统。

它支持并行服务启动,这是一个相对智能(当时)的依赖系统,其中必须停止某些事件时计算最少数量的从属服务,通过管道旋转记录器以及如何操作进程的用户可配置信号。它避开了运行级别,转而支持在文件系统中按名称对服务进行分组,并且具有完全自包含的关闭过程,该过程不依赖于脚本。

它使用了shell脚本,但由于拥有一个理智的流程管理系统,因此它们更加简洁。遗憾的是,它没有获得重大通知而已经消失。

daemond(2002-3)

虽然很少有人知道但历史上有趣的系统,daemond在解决依赖关系的时间系统(包括内核模块的特殊节)中有一个相对错综复杂的系统,并且它自己的配置语法基于可以选择包含来自shell脚本的片段的块(类似于Upstart)工作)。

service "fsck" {
description "ファイルシステムのチェック";
require "lvm";
setup "/sbin/fsck -C -R -A -a";
}

service "mount-local" {
require "fsck";
description "ローカルファイルシステムをマウント";
setup "/sbin/mount -a -v -t nonfs,nosmbfs";
}

除了服务之外,它还从/etc/daemond.rc读取初始配置文件,并围绕快速并行启动进行设计。

它的依赖节如下:

require "ファイルまたはサービス";
これは、ファイルが存在しない限りサービスは全く開始できないこと、
またはサービスが正常に開始されたことを示す。

need "ファイルまたはサービス";
require と同じだが、依存関係が満たされない場合、
サービス全体が利用不可になる( 마치 존재하지 않는 것처럼 )。
これにより、特定条件下で開始する必要があるが、
otherwiseはオプションであるようなサービスを希望する場合に便利。

want "サービス";
これは適切な依存関係ではなく、「協調」サービスである。
この指令は、書いたサービスが開始되면、
サービスも試みられる必要があるが、成功する必要はないことを示す。

require module "モジュール";
need module "モジュール";
最初の2つと同じだが、カーネルモジュール用。
大半はカーネルの自動ロードに依存する方が 通常は更好。

group "グループ";
これはサービスをグループに配置する。
そのグループは_serviceのように参照でき(グループ全体を開始)、
グループ全てのメンバーが開始されれば成功したとみなされる、
unless...

require any "グループ";
...が使用された場合、グループのいずれかのサービスが開始されれば
成功したとみなされる。

mode "モード" { ... };
これはターゲットモード(initのrunlevels类似的)を定義する。
依存関係のみを含むことができる。

它是用C++而不是C编写的,作者显然有野心,在他的自述文件中说:

我希望有一天能成为古老的SysV和BSD inits的主流替代品,它需要受到很多人的打击。如果您创建了服务定义文件以使系统正常启动,我几乎肯定想要一个副本 - 特别是如果您使用标准分发 - 以便我也可以分发它们。

它没有留下印象。

GNU dmd(2003)

dmd(daemon manage daemons)是一个最初由Wolfgang Jährling于2003年推出的系统,最著名的是完全可以在Guile Scheme中编写和配置。

近十年来它一直处于昏迷状态(如果不是死的),它在2013年作为Guix交易包管理系统的一部分重新启动,该系统也是用Guile编写的。今天,它被用作Guix系统分发的init守护进程和服务管理器。

它具有良好的文档记录并且通常很简单,基于提供/需求关系形式的依赖关系,并且服务配置例程是可重用的Scheme宏,包括用于封装各种执行规程的所谓构造函数。

由于底层Scheme语言的首要地位,因此它具有非常灵活和可扩展性。以下是GuixSD来源的示例:

(define (root-file-system-service)
"シャットダウン時にルートファイルシステムをreetryに再マウントする
( aka. クリーンに「umounting」ルート) サービスを返す。

このサービスはサービス依存グラフのルートである必要があり、
dmdが唯一のプロセスとして残った時にその'stop'アクションが呼び出される。"
(with-monad %store-monad
(return
(service
(documentation "ルートファイルシステムの世話を 한다.")
(provision '(root-file-system))
(start #~(const #t))
(stop #~(lambda _
;; 正常に停止したら#fを返す。
(sync)

(call-with-blocked-asyncs
(lambda ()
(let ((null (%make-void-port "w")))
;; 'dmd.log'を閉じる
(display "logを閉じる\n")
;; XXX: 理想的には'stop-logging'を使いたいが、
;; dmd 0.1の段階では実際にポートを閉じていない。
(close-port (@@ (dmd comm) log-output-port))
(set! (@@ (dmd comm) log-output-port) null)

;; デフォルト出力ポートをリダイレクト..
(set-current-output-port null)
(set-current-error-port null)

;; /dev/consoleを閉じる。
(for-each close-fdes '(0 1 2))

;; この時点で開いているファイルはもうないので、
;; ルートファイルシステムをreadonlyで再マウントできる。
(mount #f "/" #f
(logior MS_REMOUNT MS_RDONLY)
#:update-mtab? #f)

#f)))))
(respawn? #f)))))

pinit(2003)

pinit是一个鲜为人知但却非常重要的初始系统,由Wouter von Klaunen设计,大约在2003年。

它可以说是第一个使用XML作为其服务配置语言的系统,甚至早于launchd和SMF。这些类似于以下内容:

<?xml version="1.0"?>
<command provides="system.swap">
<startup message="すべてのswapパーティションをアクティブ化...">
/sbin/swapon -a
</startup>
<shutdown message="すべてのswapパーティションを非アクティブ化...">
/sbin/swapoff -a
</shutdown>
<dependency name="system.checkfs"/>
</command>

可以证明,它有一个依赖系统(更多的是基于排序的,而不是像SMF或systemd那样的完全事务依赖解析器)。它支持并行服务启动,也可能是第一个拥有插件系统的人。也就是说,各种启动过程,而不是硬编码到init守护程序或作为脚本启动,是作为共享对象动态加载到pinit的地址空间中或从pinit的地址空间卸载的,尽管似乎没有正式定义的API。

预留运行级别以支持静态配置文件,列出启用/禁用状态转换的内容,与systemd预设不同。

与其前辈相比,它有点重量级,使用libxml和GLib作为其实用程序库。

它被遗弃,最终没有给人留下印象。

initng(2005)

最初由Jimmy Wennlund于2005年3月发布并在Gentoo系统上进行了大量测试,initng是一个更雄心勃勃、更完整的新学校系统。除了通常的流程管理、监督、服务分组(这里称为运行级别)、依赖性和并行性之外,它最引人注目的是其极其全面的插件系统,在最终版本的基础系统中有47个。插件能够连接到20多个专用于initng的不同分段子系统。这些都记录在这里,但他们有效地将init守护进程转换为专用模块加载器和处理程序。

服务本身是以基于块的格式配置的,称为ifiles,initng提供许多预先写好的文件,例如

service service/aumix {
use = service/alsasound;
need = system/initial system/bootmisc;
stdall = /dev/null;
script start = {
if [ -f /etc/aumixrc ]
then
@/usr/bin/aumix@ -f /etc/aumixrc -L
else
@/usr/bin/aumix@ -v75 -c75 -w75
fi
};
exec stop = @/usr/bin/aumix@ -f /etc/aumixrc -S;
}

因此,它可以被视为meta-init的综合示例。

它曾被Ubuntu考虑过,但他们最终决定创建Upstart。从那以后,initng项目悄然死亡,几乎没有成功。

launchd(2005)

可以说是第一个「 新 学校」初始化系统,其特点是在使用控制实用程序与init(8)守护进程通信时,所有逻辑都包含在内。通过XML plists配置。在OS X中,它也是Mach内核服务的引导守护程序(发现注册表)。推广「套接字激活」的流行语。从代理(每个用户)隔离守护进程(系统范围),后者被分组为焊接到OS X特定子系统(如loginwindow或Aqua UI)的类型。围绕纯粹的延迟加载服务,没有正式的依赖模型,而是期望服务在整个OS X堆栈的其余部分通过IPC同步。Couples将类型处理为调度策略和资源限制,显然意味着保留桌面响应。

它被认为是Ubuntu,由于许可原因(当时它使用GPL不兼容的Apple Public Source License)而被丢弃。目前正在由NextBSD项目进行探索,并可能在以后的FreeBSD中进行探索。

服务管理设施(SMF)(2005)

Solaris SMF可能是第一个拥有复杂事务依赖系统的系统,这些系统在内部图形引擎中进行跟踪。专为复杂的服务器管理方案而设 与Solaris Fault Manager深度集成以跟踪硬件异常,每个服务都通过FMRI(故障管理资源标识符)进行识别。使用XML清单配置服务,然后将其编译到称为服务配置存储库的数据库中(服务也可以选择存储运行时数据),可以使用svcprop(1)读取并使用svccfg(1)动态配置。服务实例本身通过svcadm(1)控制,并通过svcs(1)获得状态。

SMF区分主重启动器(svc.startd,默认依赖关系管理器)和委托重启器,它们导出与主重启器相同的服务状态,但具有不同的特定于应用程序的行为。在Solaris下,inetd是一个委托的重新启动器。

可以在Oracle文档中找到更多信息。

eINIT(2006)

与initng类似,它基于大量插件,init守护进程只是一个处理程序。也针对Gentoo。以XML格式配置。比initng明显更高的元可配置性,每个模块都可以在einit.xml清单中使用XML进行配置。基于提供/需要类型的依赖项和事件子系统,但更多内部事件(如监视模块加载/卸载)比服务注册的内容更多。

<einit prefix="services-virtual-module">
<daemon id="daemon-boinc"
name="BOINC client"
provides="boinc"
requires="mount-critical"
command="cd /var/lib/boinc; boinc_client"
restart="yes" />
</einit>

未能获得牵引力并因此被放弃。

Upstart(2006)

最初由Scott James Remnant为Ubuntu设计。在Fedora中简单使用,仍然在ChromeOS中(Ubuntu本身转向systemd)。

Upstart围绕着发出事件和采取行动以响应的想法,例如启动和停止服务。它提供了几个称为桥的模块,用于将各种内核或用户空间事件转换为其自己的本机队列。内置事件列表在upstart-events(7)中定义。

首先使用D-Bus作为PID 1内部的通信机制。

因此,事件是针对服务采取行动的抽象前提条件或后置条件。它们与动态和延迟加载同样重要。

Upstart Cookbook深入探讨了这个架构。

Asus eeePC fastinit +衍生品(2007-2015)

作为华硕eeePC笔记本电脑系列的一部分,它在2007-2008期间预装了一个名为Xandros的GNU/Linux发行版的变体,他们编写了一个名为fastinit的专有init(8)替代品,专门用于......启动速度非常快,我想。

2008年,它被Claudio Matsuoka逆向工程。其速度的原因很简单。它是一个小型C程序中完全独立的引导逻辑,它直接调用POSIX来执行通常作为shell脚本运行的所有操作。它通过编辑硬编码的C宏在编译时设计用于静态可配置性。因此,它也不灵活。

然而,在2014 - 2015年左右,它由嵌入式开发人员Joachim Nilsson和重新推出的finit进行了分叉和显著扩展。

与前面提到的pinit非常相似,finit适用于基于插件的系统,用于动态连接到引导逻辑,除了它实际上有一个明确定义的API。它与SysV运行级别保持兼容,并带有用于预打开服务器套接字的嵌入式inetd,并使用平面/etc/finit.conf文件进行配置,如下所示:

user admin
host testbed

check /dev/vda1

module button
module evdev
module loop
module psmouse

runlevel 2

network service networking start

tty /dev/tty1
tty /dev/tty2
tty /dev/tty3

# Alternative method instead of runparts
#task [S] /etc/init.d/keyboard-setup start -- キーボード設定
#task [S] /etc/init.d/acpid start          -- ACPIデーモン開始
#task [S] /etc/init.d/kbd start            -- コンソール準備
#run [2] /etc/init.d/networking start      -- ネットワーキング開始

# 必要なサービスを監視して再起動
service [2345] /sbin/klogd -n             -- カーネルロギングサーバー
service [2345] /sbin/syslogd -n           -- Syslogサーバー
service [3] /usr/sbin/gdm                 -- GNOMEディスプレイマネージャー

# このディレクトリから起動スクリプトを実行
# runparts /etc/start.d

# Inetdサービス
inetd time/udp                wait [2345] internal          -- UNIX rdateサービス
inetd time/tcp              nowait [2345] internal          -- UNIX rdateサービス
inetd ssh@eth0:222/tcp      nowait [2345] /usr/sbin/sshd -i -- SSHサービス
inetd ssh/tcp               nowait [2345] /usr/sbin/sshd -i -- SSHサービス

# 同じサービスの複数のインスタンスの場合、
# service/run/taskキーワードとコマンドの間に:IDを追加
service :1 [2345] /sbin/httpd -f -h /http -p 80   -- Webサーバー
service :2 [2345] /sbin/httpd -f -h /http -p 8080 -- 旧Webサーバー

finit绝对是面向更有限的系统,但它设法通过其小表面赋予了很大的灵活性。

OpenRC(2007)

主要由Gentoo使用,但也使用Alpine Linux等,旨在取代早期的Gentoo baselayout脚本。OpenRC实际上并不提供init守护进程,而是提供了一个全面的进程管理框架(尽管很少有监督,因为它意味着与外部主管集成,例如它对s6 daemontools-like supervisor的明确支持)受BSD系统的rc.d影响很大。由此得名。它已成功从Busybox init + mdev和sysvinit启动。

查看Gentoo wiki和ArchWiki概述。

Android init(2008)

Android init是一个专门的init守护进程,用于处理特定于平台的功能,如系统属性,并提供基于称为操作的事件机制的一般延迟加载系统,一些用户定义的,其他由init守护程序本身设置。它使用基于行的配置语言在单片/init.rc中配置。

我有一篇关于其工作原理的详细文章,可以在这里阅读。它没有太多新颖性,而是供应商可以在其initramfs中配置一次然后忘记。

systemd(2010)

原本打算叫做Babykit。

无需进一步澄清。

procd(2012)

procd是一个小型的init守护进程,具有专门为OpenWrt设计的监控,因此适用于路由器等系统。它使用一种称为ubus的小型、面向对象的消息总线实现进行通信,并通过名称空间和seccomp-bpf之类的系统调用过滤支持服务沙箱。

使用了一个特殊的init脚本库,例如

START=50
USE_PROCD=1

start_service() {
procd_open_instance  
procd_set_param command /usr/bin/xupnpd
procd_append_param command -d /usr/share/xupnpd

procd_set_param respawn
procd_close_instance
}

procd_例程将参数序列化为JSON并通过ubus传递它们。

Epoch(2014)

Epoch是一个故意极简主义的init守护进程,具有完整的进程管理和监督,但是以串行/同步方式执行所有进程。使用Object隐喻进行服务,类似于Android init,使用中心文件(这里是INI)配置,例如

Hostname=FILE /etc/hostname
DefaultRunlevel=boot
EnableLogging=true
DisableCAD=true
BlankLogOnBoot=true
MountVirtual=procfs sysfs devpts+ devshm+

ObjectID=sysclock
ObjectDescription=システムクロックの設定
ObjectStartCommand=hwclock -s
ObjectStopCommand=hwclock -w
ObjectStartPriority=1
ObjectStopPriority=2
ObjectEnabled=true
ObjectOptions=RAWDESCRIPTION
ObjectRunlevels=boot core

ObjectID=mountruntmp
ObjectDescription=/runと/tmpのマウント
ObjectStartCommand=/etc/epoch/scripts/mountruntmp.sh
ObjectStopCommand=NONE
ObjectStartPriority=2
ObjectStopPriority=0
ObjectEnabled=true
ObjectOptions=RAWDESCRIPTION
ObjectRunlevels=boot core hurr

ObjectID=rwfs
ObjectDescription=ルートファイルシステムの読み書きサポート
ObjectStartCommand=/bin/mount -o remount,rw /
ObjectStopCommand=/bin/mount -o remount,ro /
ObjectStartPriority=4
ObjectStopPriority=6
ObjectEnabled=true

使用基于System V共享内存的私有基本消息总线实现。

正如所观察到的,它具有排序优先级的概念而不是依赖性。优先级也可用于逻辑分组,优先级0等同于掩码或硬禁用。

sinit(2014)

关于一切。

# MITライセンス

#include <sys/types.h>
#include <sys/wait.h>

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define LEN(x) (sizeof (x) / sizeof *(x))

static void sigpoweroff(void);
static void sigreap(void);
static void sigreboot(void);
static void spawn(char *const []);

static struct {
int sig;
void (*handler)(void);
} sigmap[] = {
{ SIGUSR1, sigpoweroff },
{ SIGCHLD, sigreap     },
{ SIGINT,  sigreboot   },
};

static char *const rcinitcmd[]     = { "/bin/rc.init", NULL };
static char *const rcrebootcmd[]   = { "/bin/rc.shutdown", "reboot", NULL };
static char *const rcpoweroffcmd[] = { "/bin/rc.shutdown", "poweroff", NULL };

static sigset_t set;

int
main(void)
{
int sig;
size_t i;

if (getpid() != 1)
return 1;
chdir("/");
sigfillset(&set);
sigprocmask(SIG_BLOCK, &set, NULL);
spawn(rcinitcmd);
while (1) {
sigwait(&set, &sig);
for (i = 0; i < LEN(sigmap); i++) {
if (sigmap[i].sig == sig) {
sigmap[i].handler();
break;
}
}
}
/* 到達不能 */
return 0;
}

static void
sigpoweroff(void)
{
spawn(rcpoweroffcmd);
}

static void
sigreap(void)
{
while (waitpid(-1, NULL, WNOHANG) > 0)
;
}

static void
sigreboot(void)
{
spawn(rcrebootcmd);
}

static void
spawn(char *const argv[])
{
pid_t pid;

pid = fork();
if (pid < 0) {
perror("fork");
} else if (pid == 0) {
sigprocmask(SIG_UNBLOCK, &set, NULL);
setsid();
execvp(argv[0], argv);
perror("execvp");
_exit(1);
}
}

最後のメモ

Dark n'EgygyフォーラムでVRに連絡するか、不正確な点がある場合はコメントしてください。本文書の説明是一般的なものであり、必要に応じて、各プロジェクトごとにさらに調査を行うべきです。

タグ: init systemd boot Unix linux

6月21日 00:37 投稿