$ Systemd作者博客 译文

译自:http://0pointer.de/blog/projects/systemd.html (opens new window)

如果你能意会或者擅长阅读这些段落你可能已经知道这篇博客示出的是关于什么的了。但即使那样你也会发现这个故事很有趣。所以喝一杯咖啡,坐下来读读发生了什么。

这篇故事很长,但即使这样我也建议读这篇文章,用一句话总结:我们正体验一个崭新的初始化系统,它很有趣。

这是代码。故事是这样的:

$ 进程标识符1

在么个unix系统中都有一个有着特殊进程标识符1的进程。它在所有其他进程之前由内核启动,是那些其他没有父进程的进程的父进程。由于这样它能作许多其他进程不能做的工作,能响应其他进程不能响应的一些东西,像是在启动时建立和维护用户空间。

历史上在Linux中做着PID1事的软件是令人尊敬的sysvinit包,尽管它已经显示出它的年事已高。尽管有许多替代者被建议,但只有其中一个成功了:Upstart,现在已经被许多主要的发行版收录。

如上所述,

一个初始化系统中心的责任是 产生用户空间。一个好用的初始化系统做的很快。不幸的是,传统的SysV初始化系统不是特别快。

能够做到一个更快的和有效率的启动有两件事是关键的:

启动的更少。

并且并行运行更多。

那意味着什么?启动的更少意味着启动更少的服务或者延迟服务的启动直到他们真正被需要。

D-Bus system bus, 有一些服务我知道他们或早或晚都将被需要(syslog,D-Bus system bus, 等)但是对其他许多服务来说并不是这样的。比如,蓝牙不必运行,除非一个蓝牙加密狗确实被插入或者一个应用翔和他的D-Bus接口对话。同样适用于一个打印系统:除非一台机器在物理上已经连接到一个打印机,或者一个应用想要打印什么东西,没必要运行像CUPS.Avahi:如果这个机器没有连接到一个网络,没必要运行Avahi,除非一些应用要去使用它的API。甚至SSH:只要没有人想要联系你的机器也就没有必要去运行,只要它在第一次连接时就启动。(不得承认的是,在大多数机器上可能一直在监听大约每个月连一次的那些人。

并行启动更多意味着如果我们必须去运行一些东西,我们不应该串行启动(像sysvinit那样做),而是应该一次运行全部的服务,因此可用的cpu和磁盘输入输出带宽被最大化的空出,因此以上的启动时间会减少。

硬件软件动态改变

现代化系统(特别是一般用途的系统)是高度动态化它的配置和使用:他们是移动的,不同的应用被启动和停止,不同的硬件被重复地添加和移除。一个为维护服务而负责的初始化系统需要监听硬件和软件的更改。它需要动态的启动(有时是停止)服务,因为他们需要去运行程序或者使某些硬件生效。

大多数现在的系统都试图去并行启动但是涉及到同步启动各种各样的守护进程:因为Avahi需要D-Bus首先启动,只有D-Bus信号准备就绪,Avahi才能够启动。其他服务也很类似:livirtd和X11需要HAL(好吧,我这里考虑的是Fedora13的服务忽略掉HAL其实是过时的),因此HAL在livirtd和X11之前先启动。并且libvirtd也需要Avahi,所以它也在等待Avahi.并且所有这些都要求syslog,所以他们都在等Syslog完全启动和初始化,依此类推。

$ 并行套接字服务

这种启动同步导致了显著的一部分启动进程的串行化。如果我们能够清除同步和串行化消耗那不是再好不过了吗?好吧,实际上我们能做到。为了实现它,我们需要明白那些守护进程究竟要求其他的哪些,和为什么启动被推迟了。对于传统的unix守护进程,有一种解释是:他们等待直到其他守护进程提供服务的套接字已经准备好连接了。通常那是一种文件系统中的AF_UNIX 套接字,但是也可能是AF——INET。比如,D-Bus客户等待/var/run/dbus/system_bus_socket能够被接通,syslog客户等待/dev/log,CUPS客户等待

/var/run/cups/cups.sock ,并且NFS挂载点 等待 /var/run/rpcbind.sock以及ip端口映射器,等等。想想看,这实际上是他们唯一等待的东西!

现在,如果那是他们都在等待的东西,如果我们能够让那些套接字早一些能够连接和只等待确实需要的而不是整个守护进程的启动,然后我们就能加快整个启动并且能让更多进程并行。所以,怎样能够做到?事实上在类UNIX系统中那很简单:我们可以在我们真正启动那个守护进程之前创建那个监听套接字,然后仅仅在执行的时候传递哪个套接字。那样的话,我们可以在初始化系统中为所有守护进程创建所有套接字,并且在第二步时一次启动所有的守护进程。如果一个服务需要另一个没有完全启动的服务,那也完全可以:即将发生的是这个连接在提供的服务的队列中排着,客户端将潜在地阻碍那个申请。同样,服务之间的依赖关系不再需要为了允许适合的并行启动而配置:如果我们立即启动所有套接字并且一个服务需要另一个,那么可以肯定的是它可以连接到他的套接字。

因为这是接下来的核心内容,所以让我用不同的字和例子再说一遍:如果你同时启动了syslog和各种syslog客户端,在上面说过的安排中将会发生的是客户端的信息将会被添加到/dev/log套接字缓存。只要那个缓存没有满,那个客户端将不必无论如何都去等待,并且可以立即进行它的启动。只要syslog自己完成了启动,他将从队列中取出所有信息并运行他们。另一个例子:我们同时启动D-Bus和几个客户端。如果一个同步bus请求送过来因而期望一个回复,将会发生的是客户端将被阻塞,然而只有那一个客户端只需等到D-Bus做到捕获并进行它。

基本上,内核套接字缓存帮我们最大化并行,并且执行顺序和同步内核会去做,不需要用户空间任何深入的管理!如果在守护进程真正启动前所有的套接字都可用,依赖管理也会是多余的(或者居第二位):如果一个守护进程需要另一个,它会立即连接。如果另一个守护进程已经启动,它会立即成功。如果它没有启动但是正在启动,第一个守护进程甚至不需要等他,除非这个问题是一个同步请求。并且即使另一个守护进程根本没有运行,它也能被产生。从第一个守护进程角度这里没有区别,因此依赖管理编程没用的或者至少是第二位的,所有这些都最佳地并行,可选择地按需加载。除此之外,这样也更健壮,因为套接字依然可用不管实际的守护进程可能暂时的不可用(可能因为崩溃)。事实上,你可以很容易地用这个写个能运行的守护进程,退出它(或者弄崩溃),再一次运行,再一次退出(依此类推),所有这些不需要客户端注意或者丢失任何请求。

现在是暂停以下的好时机,去把你的咖啡加满,要明白接下来的内容更精彩。

但是首先,让我们先清理一些东西:这种逻辑很新吗?不,当然不是。像这样工作的最突出的系统就是苹果的启动系统:在MacOS上套接字的监听满足所有的守护进程,由launchd来做。服务自身因此全部可以并行启动依赖不需要配置。这真实一个非常精巧的设计,这就是MacOS能够提供奇妙的启动时间的主要的原因。我强烈建议这个视频,launchd的创造者阐述他们做了什么。不幸的是这个想法从没有在苹果圈外实现。

这个思想实际上甚至比launchd更久远。在launchd之前这个令人尊敬的inetd像是这样工作:套接字被一个守护进程创建,这个进程通过在启动时将套接字描述传递过去将启动实际的服务守护进程。然而inetd的焦点不是本地服务,而是互联网服务(尽管后面的重新实施也支持AF——UNIX套接字)。它也不是一个用来并行启动的工具或者甚至对理顺隐含的依赖关系有用。 对于TCP套接字inetd主要用在一个地方:对于每一个进来的连接,一个新的守护进程实例被产生出来。那意味着对于每一个连接一个新的进程被产生和初始化,不过这不是高性能服务器的配方。然而,从inetd一开始他就也支持另一种模式:单一的一个守护进程在第一次连接时被产生,然后这个单一的实例将继续运行同时也会接受后来的连接(这个inetd.conf中wait/nowait选项的用途,不幸的是,也是一个尤其没有被好好用文档说明的选项.)每个连接守护进程的启动可能给inetd一个坏名声因为会使他变慢。但那不完全公平。

$ 并行总线服务

现代linux上的守护进程趋于通过D-Bus而不是主要的AF_UNIX套接字提供服务。现在问题是,对于这些服务,我们能够像传统套接字服务一样实行同样的并行启动逻辑吗?是的,我们可以。D-Bus已经有了所有正确的hooks,使用总线激活方式,一个服务能在它第一次连通时启动。总线激活也给了我们需要的为了同时启动D-Bus的生产者和消费者最少的同步请求:如果我们想要在启动CUPS的同时启动Avahi(注:CUPS使用Avahi来浏览mDNS/DNS-SD打印者),然后我们可以简单的同时运行他们,如果通过总线激活逻辑CUPS比Avahi更快,我们可以让D-Bus排在请求队列中直到Avahi能够稳固他的服务名称. 所以,总结一下:以套接字为基础的服务激活和总线基础的服务激活一起使我们并行启动所有守护进程,不需要任何更多的同步。激活同样允许我们对服务懒惰加载:如果一个服务很少使用,我们可以紧紧的在某人获取套接字或者总线名时再加载它,而不是在启动的时候加载。 如果这不是最好的方式,那我也不知道什么方式更好了! 并行文件系统工作 如果你看了当前发行版的启动进程串行图,同步点比只有守护进程启动还多:最显著的是文件系统相关的工作:挂载,检查,配额。现在,在启动时大量的时间用在闲置地等待直到所有在/etc/fstab中列出的在设备树出现的设备被检查,挂载,配额检查(如果可能的话)。只有在这些全部完成之后我们继续启动实际的设备。 我们能改进这些吗?结果证明我们可以做到。Harald Hoyer想出一个主意,用伟大的自动挂载解决它: 就像connect()的请求表明一个服务对另一个服务感兴趣那样,一个open()表明一个服务对一个文件或者文件系统有兴趣。所以,为了提高我们能够并行的量,只要他们要找的文件系统还没有挂载并且不可用,我们可以就让这些程序等待。我们建立一个自动文件系统挂载点,如果我们的文件系统完成正常启动时的检查和配额,我们就用真正的挂载替换掉它。尽管文件系统还没有准备好,它的连接将会被内核队列起来,相应的连接进程将会阻塞,但是仅仅只有那一个守护进程和他的连接被阻塞。这样我们就可以在所有文件系统完全可用前启动我们的守护进程–不会让他们丢失任何文件,并且可以最大程度地并行化。 并行文件系统工作和服务工作不理解/,尽管那是服务的二进制文件通常存储的地方。然而,对于像/home这样通常更大,甚至加密过的,可能在远程很少会被启动进程连接的文件系统来说,这样能显著改进启动时间。可能没有必要提到,像procfs或者sysfs这样的虚拟文件系统,绝不应该通过自动文件系统挂载。 如果有的读者可能发现把自动文件系统集成到初始化系统有点脆弱甚至怪异,可能在事物崩溃的边缘,我也不会感到惊讶。然而,大量把玩之后,我可以告诉你这感觉起来很正确。这里使用自动文件系统简单地意味着我们可以创建一个挂载点,而不必立即提供备份的文件系统。实际上它因此只延迟访问。如果应用试图访问一个自动文件系统,我们又花了很长时间用真实的文件系统替换它,它会进入到可中断的睡眠,意味着你可以安全的取消它,例如通过C-c。同样注意在任何一点,如果挂载点最后不应该是可挂载的(可能因为检查失败),我们可以告诉自动文件系统返回一个干净的错误代码(像是ENOENT)。所以,我猜测我想说的是即使把自动文件系统集成到初始化系统可能一开始看起来很冒险,我们的实验性的代码表明这种思想实际上工作起来出奇的好–如果它因为正确的理由并且使用了正确的方法。 同时要注意这些应该是直接自动文件系统挂载,意味着从应用的角度来看,传统的挂载点和基于自动文件系统的挂载点只有很少的不同点。 保持第一个用户PID苗条 我们从MacOS的启动逻辑中学到的另一事情是shell脚本是邪恶的。shell是快的shell也是慢的。它可以被快速的编写,执行却很慢。传统的SysV启动逻辑是围绕着shell脚本进行建模的.不论是/bin/bash还是任何其他shell(编写出来用于让shell脚本执行的更快)最后注定变慢。在我的系统上/etc/init.d的脚本至少召唤了grep77次。awk被调用92次,cut23次还有sed74次。每次这些命令(以及其他的)被调用,就生成一个进程,查询一些库,一些像i18n等的启动元素被安装之类的。之后除了少许字符串操作之外,这个进程又一次被终止。当然,那会是出奇的慢。除了shell不会有其他语言做那么慢。另外,shell脚本也非常脆弱,并且基于环境变量和类似的难于监管和控制的东西动态改变他们的行为。 所以让我们在启动进程中除掉shell脚本吧!在那之前我们需要弄清楚他们当前用来干什么:好吧,总的来说大部分时间,他们做的事相当无聊。大部分的脚本花在琐碎的安装和拆卸服务上。无论是单独执行,还是他们自己移动到守护进程,或者简单的在初始化系统中执行,都应该用c重写他们。 似乎我们将不能很快在系统启动中完全去掉shell脚本。用c重写他们需要时间,在一些情况根本没有意义,有时候shell脚本太方便了以至于不能没有。但是我们当然可以让他们不那么突出。 一个衡量shell脚本影响启动进程的指标是系统完全启动后你能启动的第一个进程的PID号。启动,登录,打开一个终端键入echo $$.在你的linux系统上试一下,然后和MacOS上的结果对照一下!(提示,是像这样的一些东西:Linux PID1823,MacOS PID 154,是在我们自己的测试系统上测量的。 跟踪进程 一个启动和维护服务的系统的部分中心应该是照看进程:他应该监视服务。如果他们关闭了要重启他们;如果他们崩溃了它应该收集有关他们的信息,把它留给管理员,把这些信息和崩溃转储系统,像是退出和登录系统像是syslog或者检查系统,交叉联合起来。 它也应该能够完全的关闭一个服务。那可能听起来很容易,但是比你想的要难。传统上在unix系统一个两次fork的进程能逃脱它父进程,并且祖辈进程不会明白新进程和他产生的那个有什么关系。举个例子:当前,一个行为不端的两次fork出的CGI脚本,当你关闭apache后没有被终止。此外,你甚至不能弄明白它和apache的关系,除非你通过名字和目的了解到。 所以我们如何跟踪进程才能不让他们逃出监管?甚至如果他们fork许许多多次我们也能像对待一个单元一样控制他们? 不同的人对待这个问题有不同的解决办法。这里我不会太深入,但是至少说说基于ptrace或者netlink连接器(一个内核接口,每当系统中任何进程fork或者退出时,它允许你得到一个netlink消息。)的方法,有一些人已经调查和实现了的,被批评为丑陋和不适合扩展的方法。 我们能做什么呢?因为有一阵子内核知道控制组(也称为cgroup)。他们主要是允许创建分层的进程组。这个分层结构直接暴露在一个虚拟文件系统,因此很容易访问。祖名基本上是文件系统的目录名。如果属于一个特殊cgroup的进程fork了,他的子进程会成为同样组里的一员。除非他被特许并能访问cgroup文件系统否则它不能逃脱这个组。最初,croups为了容器的目的被引进内核:某个内核子系统能强制限制一些组的资源,像是限制cpu或者内存使用。传统的资源限制(像通过setrlimit()实现的)(大部分)是每个进程。cgroup另一方面让你在整个进程组上实现强制限制。cgroup强制限制对立即容器使用案例之外的情况也很有用。比如你也可以用它限制apache和他的所有子进程可能用到的内存和cpu资源。然后,一个行为不端的CGI脚本再也不会通过简单的fork逃出你的strlimit()资源控制了。 除了容器和资源强制限制,cgroups对跟踪守护进程也很有用:cgroup隶属关系被子进程安全的继承了,他们不能逃脱。有一个通知系统可用以便于超级用户进程能被通知到一个cgroup空转。你可以通过读取/proc/#PID/cgroup来查找一个进程的cgroups。cgroups因此成为以监管为目的的跟踪进程的好的选择。

控制进程执行环境 一个好的监管员应该不仅监管和控制一个守护进程的启动,终止或者崩溃,而且要为他建立一个好的,精简的,安全的工作环境。 那意味着设置显著的进程参数像是setrlimit()资源限制,用户/组ID或者环境阻止,但是没有终止他们。linu内核给用户和管理员许多进程上的控制(当前,其中一些很少用到)。每个进程而言你可以设置CPU和IO调度程序控制,能力边界集,处理器亲和力或者有附加限制的cgroup环境等等。 例如,在系统交互上,带IOPRIO_CLASS_IDLE的ioprio_set()是个减少locate的updatedb的影响的的好方法。 在一些高等级控制上会很有用,像是基于只读绑定挂载建立只读文件系统覆盖。那样一个人可以运行一些守护进程因而所有(或者一些)文件系统显得对他们来说是只读的,因此在每个请求处返回EROFS。因此这可以用来封锁守护进程类似于一个邋遢人的selinux策略系统能做的事。(但是这当然不能代替selinux,请不要有坏想法) 最后登陆是执行服务的重要部分:理想情况下一个服务产生的输出的每一部分都应该被记录。一个初始化系统应该因此提供日志记录,它从开始产生的守护进程,和与syslog的标准输出、标准错误输出的连接,或者在某些情况下甚至是/dev/kmsg,它在很多情况下是syslog的很好的替代者(嵌入式的人,听好了!)尤其当内核日志缓存配置的异常滑稽的大的时候。

在upstart上 首先,让我申明事实上我喜欢upstart的代码,注释的很好,很容易弄懂。它确实是其他项目(包括我的)应该学习的。 那就是说,我不同意upstart的一般方法。但是首先,有关这个项目更多的一点: upstart不和sysvinit共享代码,upstart的功能是sysvinit的超集,并对著名的sysvinit脚本提供了一定程度的兼容性。它主要的特征是他的基于时间的方式:进程的启动和停止一定要和系统发生的“事件”联系起来,这里的事件可以是许多不同的事情,像是:一个网络接口变得可用或者一些其他的软件启动了。 upstart通过这些事件让服务串行:如果启动syslog的事件触发了,这会是启动D-Bus的指示,因为它现在可以利用Syslog。然后,当启动dbus的出发了,网络管理器启动,因为它现在可以使用D-Bus,等等。 有人说这样这种被管理员或开发者理解的实际的逻辑依赖关系树,被翻译和编码到事件和动作规则里:每个管理员/开发者明白的逻辑上的“a需要b”规则成为一个“当b启动后启动a”和“当b停止后停止a”。在某种程度上这确实是一种简化:尤其对于upstart的代码。然而我不得不争论的是这种简化实际上是有害的。首先逻辑上的依赖系统没有移除,写upstart文件的人现在必须手动的把依赖翻译成事件/行为规则(事实上,每个依赖需要两条规则)。所以,代替让计算机基于依赖计算出做什么,用户必须手动的把依赖翻译成事件/行为规则。同时,由于依赖信息从没有被编码的原因在运行时它是不可用的,事实上意味着一个试图弄清一些事,比如为什么当b启动了a启动,为什么发生了的管理员,并没有机会弄明白。 此外,事件逻辑掉转了所有依赖,从头转到脚。非但没有减少工作量(这是一个好的初始化系统应该集精力做的地方,就如这篇博客开头指出的那样),反而事实上加大了操作期间的工作量。或者换句话说,没有定一个清晰的目标并为了达成目标只做它应该做的事,而是它走出一步,完成之后,它把之后可能的路线都做了。 或者简单的说:用户启动D-Bus的事实绝不是网络管理器也应该被启动的指标(但这就是upstart会做的事)。正好相反:当用户请求网络管理器,这是D-Bus应该被启动的明确指示(当然也是大多数用户期望的那样,对吧?)。 一个好的初始化系统应该仅仅启动它需要的,按需求而言。既不偷懒也不事先并行。然而它不应该比必须的启动更多,尤其不是每个已经安装的能用到那个服务的。 最后,我看不到事件逻辑的真正的用处。依我看来,在upstart中暴露的大多数事件事实上不那么精确,而是有持续时间:一个服务开始,运行和结束。一个设备被插入,可用,又被拔出。一个挂载点经过正在挂载,完全挂载上,或者正在解挂。一个电源插头被插进,系统运行在交流电,电源插头被拔出。一个初始化系统或者进程管理者应该处理的只有一少部分事件是确实很准确,大多数是启动,条件和停止的元组。这个信息再一次的在upstart中不支持,因为它聚焦于单一事件,忽略了持久的依赖关系。 现在,我意识到我以上指出的某些问题在upstart最近的更改之后有些缓和,尤其是基于条件的语法像是在upstart规则文件中的启动(本地文件系统和网络启动设备IFACE=lo)。然而,在我看来这显得很是对一个核心设计的有缺陷的系统的尝试的修补。 除此之外upstart作为一个守护进程的监管者做的还不错,即使一些选择可能有疑问(见上面),确实有许多错过的机会(同样见上面)。 除了sysvinit,upstart,launchd之外还有其他的初始化系统。他们中大部分提供比upstart或sysvinit很少的实质性的东西。其他最有趣的参赛者是Solaris的SMF,它支持服务间的适当的依赖关系。然而,在很多时候它过度的复杂,不如说,大量使用XML和新技术有点学术化了。它和Solaris特别的特征,例如合同系统有密切关系。

$ 集大成者:systemd

    嗯,这是停顿一下的另一个好机会,因为在我以上有希望的阐明我认为一个好的PID1应该做的和当前大量使用的系统所做的,我们现在找到了问题的实质。所以,再去把你的咖啡杯加满,值得那样做的。
    你可能猜到了:我上面建议的作为一个理想的初始化系统的要求和特征现在已经可用了,在一个(还是体验的)叫做systemd的初始化系统,也就是我要宣告的。重复一次,这里是代码。这里是他的特征和背后原理的概括:
    systemd启动和监管整个系统(因此得名...)他实现了之上指出的所有以及一些更多的特征。他基于单元的概念。单元有一个名字和类型。由于他们的配置文件通常直接从文件系统加载,这些单元名实际上是文件名。比如:一个avahi.service单元从有相同名字的文件读取配置文件,当然是压缩了avahi守护进程的单元。有这么几种单元:
    1.服务:这些是最明显的一种单元:那些可以被启动,停止,重启,重载的守护进程。为了兼容sysv,我们不仅支持我们自己的服务配置文件,还可以读取传统的sysv初始化脚本,还有lsb头,如果存在的话。/etc/init.d 因此是有一个配置文件源。
    2.socket:这个单元在文件系统中或者在互联网中压缩了一个套接字,我们现在支持类型流、数据和连续的信息包的AF_INET,AF_INET6,AF_UNIX套接字,我们当然也接受传统的FIFOs作为传输。每个套接字单元有一个匹配服务单元,当第一个连接在套接字或FIFO中到来时启动。
    3.设备:这个单元压缩了linux设备树中的一个设备。如果一个设备通过udev规则被标记为此,它会被暴露为systemd中的一个设备单元。带有udev的属性集可以作为设置设备单元依赖的配置源。
    4.挂载:这个单元压缩了文件系统中的一个层级中的一个挂载点。systemd监管所有挂载点如何来去,当然也能被用来挂载和卸载挂载点。
    5.自动挂载点:这个单元压缩了文件系统中的一个层级中的一个自动挂载点。它简单的指向其它单元而不是自己实际做事,因此能够一起管理。对于这个的例子是:多用户目标,目标是一个担任传统的sysv系统中运行级别5的角色,或者蓝牙目标是蓝牙适配器一可用就被请求,它简单的引入蓝牙相关的服务,一些其它情况不必启动的服务:bluetoothd 和obexd之类的。
    7.快照:和目标类似,快照实际上不做任何事情,它们唯一目标是指向其它单元。快照可以用来保存/回滚初始化系统中的所有服务和单元的状态。基本上它们有两种潜在的用例:允许用户临时的进入一个特殊的状态,像是“紧急shell”,结束当前服务,提供一个返回之前状态的简单方式,把刚才临时推出的服务再请回来。轻松的支持系统的睡眠:到现在为止,依然有许多服务不能处理系统睡眠,通常更好的想法是在睡眠之前关机,之后再还原它们。
    所有单元之间都有依赖(包括积极的和消极的,例如‘需求’和‘冲突’):一个服务可以依赖另一个服务,意味着只要那个服务可用一个确定的服务就启动了。挂载对于它们来自的服务存在隐含的依赖。挂载还对之前的挂载有隐含的依赖关系。(比如一个挂载/home/lennart隐含地得到一个对于/home的依赖)等等。
    其它特征的一个简单列表:
    1对于每个产生的进程,你可以控制:环境,资源限制,工作和根目录,umask,OOM killer调整,nice水平,IO类和优先级,CPU策略和优先级,cpu关系,timer slack, 用户id,组id,附加组id,可读/可写/不可访问目录,共享的/私有的/附属的挂载标签,容量/界限集,安全位,cpu调度程序重置fork,私有/临时命名空间,对各种子系统的cgroup控制。同样,你可以轻松的连接服务的校准输入/标准输出/标准错误到syslog,/dev/kmsg,任意的TTY,如果为了输入连接到一个tty,systemd将会确认一个进程得到独占的访问,要么等待或者强制它。
    2每个执行的进程都得到它的cgroup(当前默认情况下,在debug子系统中,因为那个子系统不会被使用,比大多数基本进程组做的并不多),很容易配置systemd去把服务放到在外面已经配置过的cgroups中,比如通过libcgroups工具集。
    3本地配置文件使用一个紧密沿袭众所周知的.desktop文件的语法。对于分析者来说它是一个简单的语法,早已在众多软件框架中存在过。同时,这也允许我们依赖于存在的i18n工具来描述服务,和类似的。
    4.如上所述,我们提供sysv初始化系统的兼容能力,我们利用lsb和红帽chkconfig头如果它们可用。如果不可用我们就尽可能利用其它可用的信息,像是/etc/rc.d中的启动优先级。这些初始化脚本被简单的认为是配置的一个不同的来源,因此一个简单的升级路径到systemd服务是可行的。可选的我们可以为服务读取传统的pid来辨识一个守护进程的主pid,注意到我们利用lsb初始化脚本头的依赖信息,把这些翻译成本地的systemd依赖。边注:Upstart不能获取并利用这些信息。在一个主要的Upsart系统上启动,大部分的lsb,sysv初始化脚本将因此不被并行,然而一个相似的运行systemd的系统将会。事实上,对于Upstart所有sysv脚本一起使一个工作执行,他们并不单独的对待,再一次对比systemd,sysv初始化脚本是另一个配置源,并且都被单独的处理和控制,就像其它本地systemd服务一样。
    5.类似的,我们读取存在的/etc/fstab配置文件,并认为它是另一个配置源。使用=fstab选项命令,你甚至可以标记/etc/fstab入口成为systemd控制的自动挂载点。
    6.如果同样的单元在多个配置源被配置(比如/etc/systemd/system/avahi.service存在,/etc/init.d/avahi也存在),然后本地配置将会总是按先后顺序,传统格式将会被忽略,允许一个轻松的升级方式和包来暂时传送一个SysV 初始化脚本和一个systemd服务文件。
    7.我们支持一个简单的模板/实例机制。例如:并没有为六个gettys有六个配置文件,我们只有一个getty@service文件作为getty@tty2.service等的实例。接口部分甚至能够被依赖表达式继承,例如,很容易就能够编码一个dhcpcd@eth0.service服务pulls inavahi-autoipd@etth0.service,同时使eth0字符串成为通配符。
    8.对于socket激活我们支持对传统inetd模式的完全兼容,和一个非常简单的模式,它尽力模仿已启动的socket激活,被推荐应用于新服务。inetd模式只允许传递一个套接字给启动的守护进程,而本地模式支持传递任意数量的文件描述。我们同时支持一个实例一个连接,和一个实例所有连接模式。在前一种模式下,我们把cgroup命名为在连接参数之后将要启动的守护进程,并且利用前面提到过的为了这个的模板逻辑。举个例子:sshd.socket可能产生服务sshd@192.168.0.1-4711-192.168.0.2-22.service和一个sshd@service/192.168.0.1-4711-192.168.0.2-22的cgroup(比如IP地址和端口号用在实例名中。对于AF——UNIX套接字我们使用PID和连接客户端的用户id。这提供了一个对于管理员辩认各种守护进程的实例和各自控制他们的运行时的好方法。本地套接字传递模式在应用中非常容易实现:如果$LISTEN_FDS设置为它所包含的传递的套接字数量,那么守护进程将会从文件描述符3(一个写的很好的守护进程,能够同时用fstat()和getsockname()函数来标识那种它接收到超过一个情况下的那个套接字)开始,找到像.service文件列出的那样排序的它们。除此之外,我们还设置$LISTEN_PID为应该接收fds的守护进程的PID,因为环境变量被子进程自然而然的继承了,并且因此会沿着这条链误导进程。即使这样这个套接字的传递逻辑是很容易在进程中实现,我们将会提供一个BSD许可证的参考实现来展示怎么做到这样,我们已经把一些现存的守护进程移植到这个全新的模式下.

9.我们提供对/dev/initctl的一定程度的兼容性,这种兼容性事实上通过一个FIFO激活服务实现,它简单的把这些遗留下的请求翻译为D-Bus请求,事实上,这意味着来自Upstart和sysvinit的旧的shutdown,pweroff和类似的命令仍然能和systemd一起工作。 10.我们同时提供对utmp和wtmp的兼容性,可能甚至对不健壮的一定范围的兼容。鉴于utmp和wtmp的恶心程度. 11.systemd支持单元间的几种依赖关系,之前/之后可以用来修复单元激活的顺序。对于要求和需求来说完全是正交的,它表达了一种积极的或强制或可选的依赖需求。然后,有冲突表达了一个消极的依赖要求,最后,有三种更少使用的依赖类型, 12.systemd有一个很小的事务系统,意味着:如果要求一个单元启动或关闭,我们会把它和它所有的依赖添加到一个临时事务,然后,我们会核实这个事务是否一致(比如,无论通过之前/之后的所有单元的顺序是否是无循环的)。如果不是,systemd将会试着修复它,并且从可能移除循环的事务中移除不必需的工作。同时,systemd试着在那些可能停止一个运行中服务的事务中压制不必需的工作。不必需的工作是那些起初的请求没有直接包含,但是被需求类型的依赖引入的工作。最后,我们检查事务的工作是否与已经在队列中的工作有冲突,可选的是,事务中止,如果所有问题都解决并且事务对用所有早已杰出的工作合并的影响是是一致的和最小的,并加到运行队列中。事实上这意味着在执行一个请求操作前,我们将会核实它有意义,如果可能就修复它,并且只有在它真的不能工作时才放弃。 13.我们记录了每个我们产生和监控的进程的开始/退出时间,以及PID和退出状态。这个数据可以用于在abrtd,auditd和syslog中的带有数据的交联守护进程。想像一个对你来说会经常让守护进程崩溃的UI,并且允许你很容易地定位到syslog,abrt和auditd各自的UI,将会显示产生的和用于这个守护进程的数据,在一个特别的运行上。 14.我们支持在任何时候重新执行初始化进程本身。这个守护进程状态在重新执行前序列化,在之后被反序列化。这样我们提供了一个简单的方式来帮助初始化系统升级,以及从一个initrd守护进程向最终守护进程的移交。开放的套接字和autofs挂载点被适当的序列化,因此,它们始终保持连接,以一种客户端甚至不能注意到初始化系统重新执行本身的方式。同时,一大部分服务状态无论如何都被编码进cgroup虚拟文件系统的事实甚至将会允许我们去重新执行,而不必访问串行数据。这个重新执行的代码路径大部分事实上与初始化系统配置重新加载代码路径相同,这保证了重新执行(可能更少机会触发)获得和重新加载(可能更常见)相似的测试。 15.在我们已经记录下的用c组织的基本系统的一部分的启动进程中,我们开始移除shell脚本并直接把它移动到systemd中的工作。在其中是api文件系统的挂载(例如,像是/proc,/sys和/dev的虚拟文件系统)和主机名的设置。 16.服务状态通过D-Bus是反省的和可控制的。这还不是完整的,但已经是十分广泛的。 17.当我们想去强调基于socket和基于bus名字的激活时,我们因而就支持socket和服务的依赖,我们同时支持传统的中间服务依赖。我们支持多种一个服务能发送准备好信号多少的方式:通过fork和让开始的进程退出(例如,传统的daemonize()行为),以及通过监视bus直到一个配置过的服务名字出现。 18.每当一个进程被systemd产生时有一个交互模式会询问是否批准。你可以通过在内核命令行传递systemd.confirm_spawn=1来打开它。 19。使用systemd.default=内核命令行参数,你可以指定systemd在开机时应该启动的单元。通常你会指定一些像multri-user.target在这里,但是另一个选择甚至可能是一个简单服务而不是一个target,例如,我们推出一个开箱即用的emergency.service服务,和/init=/bin/bash有类似的用处,却有实际运行了初始化系统的优势,因此提供了从紧急shell启动整个系统的选项。 20.有一个最小化的UI允许你启动/stop/introspect服务。它远没有那么完整,但是作为一个调试工具却很有用。它是用Vala(yay!)编写,名字叫systemadm。 需要注意的是,systemd使用许多linux特定的特征,并且不限于POSIX。这样解除了许多无法提供的功能,被设计用来移植到其他系统的系统。

更新时间: 8/29/2020, 7:33:55 AM