您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 信息化管理 > SYSTEMD学习笔记
SYSTEMD学习笔记Systemd是RHEL7里用来管理系统和进程的一个服务管理器,在RHEL7中它将替代以前版本中的sysV和BSD风格风格的initu程序来完成系统初始化的工作,而且也可以用来管理服务,调整运行级别,日志跟踪等功能。开发者是LennartPoettering,目前是红帽的雇员。systemd的开发目标是为linux提供更优秀的管理框架以解决系统服务之间的依存关系,并可以实现服务并行启动和降低系统开销的效果。PID1从架构上讲,传统的init进程是由内核启动,因此是所有其他进程的父进程,因此要比其他进程能够做更多事情,比如在启动过程中加载服务进程。然而传统的sysinit系统不符合一个有效的、快速的init系统标准,主要包括2点:1.尽可能启动更少进程以节约资源;2.尽可能将更多进程并行启动以加速启动的速度;对于1意味着除非是必须,否则就不要启动。举例来说系统内部有些服务是必须要启动的,例如syslog和DBus。而有的服务,例如蓝牙服务仅在蓝牙适配器被插入时才被需要;打印服务CUPS也仅是在打印机连接或程序要打印时才需要;甚至sshd也不是必须的,为了管理员可能几个月才会连接一次的ssh却不得不一直运行sshd进程进行监听。对于2意味着不应该像sysvinit一样串行执行启动进程,而应该并行启动以最大化利用CPU和IO带宽的效率。此外现代操作系统应具备高度的动态特性,包括:1.不同的程序启动、停止;2.不同的硬件被添加、移除;软硬件动态变化因此这就要求服务在有需要的时侯动态的启动。大多数当前的系统在尽可能并行化启动过程时仍对一些守护进程的调用保持同步:比如Avahi需要DBus,故仅在DBus启动、且DBus信号准备完成后Avahi才被启动。与之相同的是libvirtd和X11需要HAL(虽然之后HAL会被移除),在HAL启动前,libvirtd和X11不会启动。以上的同步过程导致了系统启动过程的串行化。那么是否可以摆脱同步与串行化所带来的缺点呢?答案是肯定的!并行的socket服务首先要理解daemon对彼此的真正依赖是什么,为什么它们在启动时被延迟了。对于传统的Unixdaemon,答案是:它们在所需要的服务所提供的socket准备好连接之前一直处于等待状态。常见的是AF_UNIX,但也可能是AF_INET。比如,需要DBus服务的客户程序将在/var/run/dbus/system_bus_socket可以被连接之前等待,syslog的客户程序将在/dev/log上等待,CUPS的客户端等待的是/var/run/cups/cups.sock,而NFS挂接是等待/var/run/rpcbind.sock诸如此类,而这是它们等待的唯一需求。这意味着只要这一条件满足,客户程序即无需等待,不论实际的服务是否已经启动。以上就是等待的真正原因。如果能尽早的创建客户程序所必须的socket就可以让客户程序处于等待状态,而不是在服务程序完全启动后再启动客户程序,我们就能加快启动进程,进一步并行化进程启动。在Unix系统上实现起来非常简单:在真正需要启动守护服务之前先创建它的监听socket,然后在exec()的过程中通过socket传递给守护进程。通过这种方式我们就可以在initsystem中一次性创建所有的daemon所需的所有socket,接下来就可以启动所有的daemon。即便某个服务的启动因为依赖于另一个服务而没有完全启动也没有关系:接下来会发生的是连接请求会在服务端排队,造成客户进程在这次请求中被阻塞;但仅有这个客户进程的请求被阻塞在这一个请求中。最终,如果我们一次性创建好所服务之间通讯所需要的socket,那么在系统中实现并行启动就没有必要去配置服务之间的依赖关系。针对上面的方案举例来说:如果同时启动了syslog和多个syslog的客户进程,由于syslog尚未启动准备好处理请求,这时客户进程开始发送份额请求会被写入/dev/log的Socket缓存中。除非缓存填满,否则客户进程无需任何等待即可继续完成其启动过程。当syslog启动完全后,就会处理队列中的所有请求并执行。另一个例子:同时启动DBus与其客户进程,当同步请求发出但没有接受到预想的回应,客户进程将阻塞。但是当DBus启动完成后处理请求,客户进程继续。内核的socket缓存将帮助我们实现最大的并行化。同步工作是由内核自动完成的,不需要任何来自用户空间的额外管理。如果在daemon启动前所有必须的socket都已经可用,依赖关系管理就变得多余了(至少变得次要了)。daemonA需要另一个daemonB,A会简单的发请求连接到B。若B已经启动,则A的请求会成功。若B已经启动但是启动过程未完成,除非A发出的是一个同步请求,则无需等待。甚至即使B根本没有运行时再重启B也为时不晚,对于A而言二者没有任何分别。借此可达成最佳的并行化和随意的按需(on-demand)加载。更棒的是,因为socket即使在相应服务是暂时不可用(如crash)时也可用,所以客户进程不会丢失任何的请求(request入buffer,待重启服务后处理)。这不是全新的设计逻辑,按这种方式工作的最杰出的系统是苹果的launchd系统。在MacOS上,侦听请求的socket被lanuchd从daemon中分离出来,服务可以并行启动而且无需配置彼此间的依存关系。这是一个独具匠心的设计,也是MacOS可以提供梦幻版启动速度的原因。这一设计理念甚至出现在lanuchd之前,古老的inetd就是这样工作的:socket被inetd集中创建,当有请求访问特点的socket时,inetd会启动相应的服务进程。然而inetd关注的焦点当然不是本地的服务,而是网络服务(虽然后来重新设计后也支持AF_UNIXsocket,它也不是一个并行启动或者正确隐藏进程依赖关系的工具。对于TCPsocket,inetd主要用于这样一种方式,当每一个连接接入的时候一个新的daemon都会催生出来。这意味着每一个连接都会催生和初始化一个daemon,这不是一个高效服务器的解决方案。然而,从一开始inetd还支持另外一种工作模式,当一个daemon为了首次连接被催生出来后,这个实例会接受随后的连接请求(这就是inetd.conf里的wait/nowait选项,遗憾的是这是一个非常糟糕的参数)。按请求启动daemon的方式可能给inetd带来了速度慢的不好名声,但这是不公平的。并行总线服务Linux系统上现代的daemon服务都倾向于通过D-BUS而不是朴素的AF_UNIXsocket来启动服务。那么我们是否可以像传统socket服务一样将并行引导逻辑应用于这类服务呢?答案是肯定的,D-BUS已经提供了所有必须的hooks(钩子):使用总线可以让服务在被第一次访问的时候启动。总线激活方式也同时给了我们用于同时启动基于D-BUS服务的服务端和客户程序所需要的最小的、每个请求的同步机制:如果我们想同时启动Avahi和CUPS服务(CUPS使用Avahia的mDNS/DNS-SD来扫描打印机),如果CUPS服务比Avahi服务通过D-BUS逻辑激活启动的速度快,我们可以把CUPS服务的请求放入缓存队列中直到Avahi服务进行处理。所以,总结一下:基于socket的服务激活和基于D-BUS的服务激活结合在一起就可以一次并行的启动所有的daemon,而无需要任何进一步的同步工作。激活机制也可以让我们进行延迟加载服务:如果一个服务很少使用,我们可以让这个服务的socket或者总线名在第一被访问的时候再启动这个服务而不是在引导的时候启动。并行文件系统任务如果看一下现在Linux发行版的启动过程,文件系统的同步点大于daemon启动时候的同步点:最突出的是文件有相关的工作:mount、fsck、配额。开机是大量的时间都花在怠速等待上直到所有列在/ect/fstab中的设备在设备树中显示启动、而且已经被fsck、mount和做过quota检查(如果enable了quota)。只有上述完全结束才能继续并启动实际的服务。如何改进?HaraldHoyer想到了使用古老的autofs:就像connect()调用表示有一个服务对另外一个服务感兴趣一样,open()(或者类似调用)调用表示一个服务对特定文件或者文件系统感兴趣。为了改进我们可以实现的并行度,我们可以让这些app只在它们需要的文件系统没有被mount或者可读之前处于等待状态:配置autofs的mount点,当文件系统因为正常启动而成fsck和quota检查工作后再切换到真正的mount。当文件系统可用之前,对其的访问将会被内核缓存到队列中,该访问会被阻塞,但仅仅是这个进程在这一时刻被阻塞而已。通过这个方法就可以在所有文件系统可用之前启动我们的daemon,在不丢失文件的同时最大化并行度。并行化文件系统任务并不适用于/文件系统,毕竟那是所有服务的二进制文件存储的地方。但是,对于像/home这样的文件系统来说,通常很大,而且可能是加密的,而且可能是远程文件系统或者很少被开机引导的daemon所访问,并行化作业可以显著的提高开机时间,对于像/proc、/sys这样的虚拟文件系统应该永远不要考虑通过autofs来mount。大家可能会认为将autofs整合进init系统会会很脆弱甚至有点怪异,但是这么做是非常正确的。采用autofs意味着只需要提供一个mount点而不是去提供hack整个文件系统的方法。实际上,它只是造成了访问延时。如果一个应用试图访问一个autofs文件系统,但与之对应的真实文件系统花了很长时间没有mount,那么它将在可中断的sleep中挂起,可以安全的取消(例如通过C-c)。另外请注意,如果mount点因为fsck失败等原因而最终没有挂载成功,我们可以告诉autofs返回一个干净的错误代码(如ENOENT)。所以,将autofs整合到init系统中可能看起来是一次冒险,但是实验代码却证明效果出奇的好——因为它是基于正确的理由和正确的方法。确保第一用户的PID尽量小另一个从Mac-OS的引导逻辑中我们可以学到的是shell脚本的不足。shell很快而shell又很慢。shell快在修改起来很容易,而慢在执行的效率很低下。传统的sysvinit引导逻辑是通过shell脚本来构建模型的。不管它是/bin/bashe或者任何其他的让shell脚本执行的更快的shell。无论如何,执行效率最终还是很慢。在笔者的系统中/eyc/init.d/目录下的脚本调用grep超过77次,awk92次、cut23次以及sed74次。每次这些命令被调用的时候就会有进程被产生出来以及相关的类库被搜索,一些初始化操作,像i18n设置这类的东西还会产生更多的调用和搜索,然后在做了一些很琐碎的字符串操作之后就终止了。所以,这种速度难以想象的慢,除了shell没有哪种语言会这样做事情。更重要的是,shell脚本本身也非常脆弱,诸如改变环境变量之类的东西就可以显著的改变其行为,因此很难去监控和控制shell脚本的行为。因此,在搞清楚这么做的意义何在之前,让我们从引导过程中去掉shell脚本:总体来说,大多数情况下,shell脚本所做的工作是非常无聊的。大多数脚本的工作是花在琐碎的服务设置和卸载上,无论是单独执行还是迁移到daemon内部或者由系统的init系统来完成这些工作都应该由C语言来重写。看起来我们还无法在整个系统启动的过程中摆脱shell脚本,用C语言重写这些代码需要花费a使劲,在某些情况下也不是真的有意义,而且shell脚本太方便很难摆脱,但是我们可以让它们不想现在这样突出。用于测量shell脚本对启动过程的影响是查看在系统完全启动第一个进程的PID号。开机、登陆,然后打开一个终端,输入echo$$。在Linux系统上输入上述命令并将结果与在MacOS上的结果做对比(提示:结果可能是这样的,在Linux上是1823。而在MacOS上是154,这是在笔者自己系统上的结果)。进程跟踪一个
本文标题:SYSTEMD学习笔记
链接地址:https://www.777doc.com/doc-2861966 .html