CentOS系统启动流程:
POST-->BootSequence-->Bootloader-->kernel+initramfs(initrd)-->rootfs-->/sbin/init
innit程序:
CentOS5:SysVinit
CetnOS6:Upstart
CentOS7:Systemd
Systemd新特性:
系统SysVinit和LSBinitscripts兼容
系统引导时实现服务并行启动;采用socket/D-Busactivation等技术启动服务;为了减少系统启动时间,systemd的目标是:尽可能启动更少的进程;尽可能将更多的进程并行启动;
按需激活进程;Systemd可以提供按需启动的能力,只有在某个服务被真正请求的时候才启动它。当该服务结束,systemd可以关闭它,等待下次需要时再次启动它。
能够对系统进行快照和恢复;
启动挂载点和自动挂载点的管理:
Systemd自助管理系统上的挂载点,以便能够在系统启动时自动挂载它们。且兼容/etc/fstab文件;
实现事务依赖关系管理:
systemd维护一个"事务一致性"的概念,保证所有相关的服务都可以正常启动而不会出现互相依赖,以至于死锁的情况。
基于内生依赖关系定义服务控制逻辑;
system利用Linux内核的特性即CGroup来完成进程跟踪的任务。当停止服务时,通过查询CGroup,systemd可以确保找到所有的相关进程,从而干净地停止服务;
日志服务:systemd自带日志服务journald,该日志服务的设计初衷是克服现有的syslog服务的缺点。
System的基本概念
单元的概念:
系统初始化需要做的事情非常多。需要启动后台服务,比如启动SSHD服务;需要做配置工作,比如挂载文件系统。这个过程中的每一步都被systemd抽象为一个配置单元,即unit。可以认为一个服务是一个配置单元;一个挂载点是一个配置单元;一个交换分区的配置是一个配置单元;等等。systemd将配置单元归纳为以下一些不同的类型。然而,systemd正在快速发展,新功能不断增加。所以配置单元类型可能在不久的将来继续增加。
service:代表一个后台服务进程,比如mysqld。这是常用的一类;
socket:此类配置单元封装系统和互联网中的一个套接字。当下,systemd支持流式、数据包和连续包的AF_INET、AF_INET6、AF_UNIXsocket。每一个套接字配置单元都有一个相应的服务配置单元。相应的服务在第一个"连接"进入套接字时就会启动(例如:nscd.socket在有新连接后便启动nscd.service)。
device:此类配置单元封装一个存在于Linux设备树中的设备。每一个使用udev规则标记的设备都将会在systemd中作为一个设备配置单元出现。
mount:此类配置单元封装文件系统结构层次中的一个挂载点。Systemd将对这个挂载点进行监控和管理。比如可以在启动时自动将其挂载;可以在某些条件下自动卸载。Systemd会将/etc/fstab中的条目都转换为挂载点,并在开机时处理。
automount:此类配置单元封装系统结构层次中的一个自挂载点。每一个自挂载配置单元对应一个挂载配置单元,当该自动挂载点被访问时,systemd执行挂载点中定义的挂载行为。
swap:和挂载配置单元类似,交换配置单元用来管理交换分区。用户可以用交换配置单元来定义系统中的交换分区,可以让这些交换分区在启动时被激活。
target:此类配置单元为其他配置单元进行逻辑分组。它们本身实际上并不做什么,只是引用其他配置单元而已。这样便可以对配置单元做一个统一的控制。这样就可以实现大家都已经非常熟悉的运行级别概念。比如想让系统进入图形化模式,需要运行许多服务和配置命令,这些操作都由一个个的配置单元表示,将所有这些配置单元组合为一个目标(target),就表示需要将这些配置单元全部执行一遍以便进入目标所代表的系统运行状态。(例如:multi-user.target相当于在传统使用SysV的系统中运行级别3)
timer:定时器配置单元用来定时触发用户定义的操作,这类配置单元取代了atd、crond等传统的定时服务。
snapshot:与target配置单元相似,快照是一组配置单元。它保存了系统当前的运行状态。
依赖关系:
虽然systemd将大量的启动工作解除了依赖,使得它们可以并发启动。但还是存在有些任务,它们之间存在天生的依赖,不能用"套接字激活"(socketactivation)、D-Busactivation和autofs三大方法来解除依赖(三大方法详情见后续描述)。比如:挂载必须等待挂载点在文件系统中被创建;挂载也必须等待相应的物理设备就绪。为了解决这类依赖问题,systemd的配置单元之间可以彼此定义依赖关系。
Systemd用配置单元定义文件中的关键字来描述配置单元之间的依赖关系。比如:unitA依赖unitB,可以在unitB的定义中用"requireA"来表示。这样systemd就会保证先启动A再启动B。
Systemd事务:
Systemd能保证事务完整性。Systemd的事务概念和数据库中的有所不同,主要是为了保证多个依赖的配置单元之间没有环形引用。存在循环依赖,那么systemd将无法启动任意一个服务。此时systemd将会尝试解决这个问题,因为配置单元之间的依赖关系有两种:required是强依赖;want则是弱依赖,systemd将去掉wants关键字指定的依赖看看是否能打破循环。如果无法修复,systemd会报错。
Systemd能够自动检测和修复这类配置错误,极大地减轻了管理员的排错负担。
Target和运行级别:
systemd用目标(target)替代了运行级别的概念,提供了更大的灵活性,如您可以继承一个已有的目标,并添加其它服务,来创建自己的目标。下表列举了systemd下的目标和常见runlevel的对应关系:
Systemd的并发启动原理
如前所述,在Systemd中,所有的服务都并发启动,比如Avahi、D-Bus、livirtd、X11、HAL可以同时启动。乍一看,这似乎有点儿问题,比如Avahi需要syslog的服务,Avahi和syslog同时启动,假设Avahi的启动比较快,所以syslog还没有准备好,可是Avahi又需要记录日志,这岂不是会出现问题?
Systemd的开发人员仔细研究了服务之间相互依赖的本质问题,发现所谓依赖可以分为三个具体的类型,而每一个类型实际上都可以通过相应的技术解除依赖关系。
并发启动原理之一:解决socket依赖
绝大多数的服务依赖是套接字依赖。比如服务A通过一个套接字端口S1提供自己的服务,其他的服务如果需要服务A,则需要连接S1。因此如果服务A尚未启动,S1就不存在,其他的服务就会得到启动错误。所以传统地,人们需要先启动服务A,等待它进入就绪状态,再启动其他需要它的服务。Systemd认为,只要我们预先把S1建立好,那么其他所有的服务就可以同时启动而无需等待服务A来创建S1了。如果服务A尚未启动,那么其他进程向S1发送的服务请求实际上会被Linux操作系统缓存,其他进程会在这个请求的地方等待。一旦服务A启动就绪,就可以立即处理缓存的请求,一切都开始正常运行。
那么服务如何使用由init进程创建的套接字呢?
Linux操作系统有一个特性,当进程调用fork或者exec创建子进程之后,所有在父进程中被打开的文件句柄(filedescriptor)都被子进程所继承。套接字也是一种文件句柄,进程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支持所谓"busactivation"功能。如果服务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的使用有一个大概的理解。具体的细节内容太多,即无法在一篇短文内写全。还需要读者自己去进一步查阅systemd的文档。
Unit文件的编写
开发人员开发了一个新的服务程序,比如httpd,就需要为其编写一个配置单元文件以便该服务可以被systemd管理,类似UpStart的工作配置文件。在该文件中定义服务启动的命令行语法,以及和其他服务的依赖关系等。
此外我们之前已经了解到,systemd的功能繁多,不仅用来管理服务,还可以管理挂载点,定义定时任务等。这些工作都是由编辑相应的配置单元文件完成的。我在这里给出几个配置单元文件的例子。
下面是SSH服务的配置单元文件,服务配置单元文件以.service为文件名后缀。
[root@kalaguiyinsystem]#cat/usr/lib/systemd/system/sshd.service
[Unit]
Description=OpenSSHserverdaemon
After=network.targetsshd-keygen.service
Wants=sshd-keygen.service
#[unit]部分,描述信息
[Service]
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd-D$OPTIONS
ExecReload=/bin/kill-HUP$MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s
#[service]定义,ExecStartPre定义启动服务之前应该运行的命令;
#ExecStart定义启动服务的具体命令行语法。
[Install]
WantedBy=multi-user.target
#[install]部分:WangtedBy表明这个服务是在多用户模式下所需要的。
那我们就来看下multi-user.target吧:
[root@kalaguiyinsystem]#catmulti-user.target
[Unit]
Description=Multi-UserSystem
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.servicerescue.target
After=basic.targetrescue.servicerescue.target
AllowIsolate=yes
#Requires定义表明multi-user.target启动的时候basic.target也必须被启动;另外basic.target停止的时#候,multi-user.target也必须停止。如果您接着查看basic.target文件,会发现它又指定了sysinit.target
#其他的单元必须随之启动。同样sysinit.target也会包含其他的单元。采用这样的层层链接的结构,最终所#有需要支持多用户模式的组件服务都会被初始化启动好。
[Install]
Alias=default.target
#Alias定义,即定义本单元的别名,这样在运行systemctl的时候就可以使用这个别名来引用本单元。
此外在/etc/systemd/system目录下还可以看到诸如*.wants的目录,放在该目录下的配置单元文件等同于在[Unit]小节中的wants关键字,即本单元启动时,还需要启动这些单元。比如您可以简单地把您自己写的foo.service文件放入multi-user.target.wants目录下,这样每次都会被默认启动了。
[root@kalaguiyinsystem]#pwd
/etc/systemd/system
[root@kalaguiyinsystem]#ls
basic.target.wantsdisplay-manager.service
bluetooth.target.wantsgetty.target.wants
dbus-org.bluez.servicegraphical.target.wants
printer.target.wantssockets.target.wants
spice-vdagentd.target.wantsdefault.targetsysinit.target.wantsdefault.target.wants
再让我们来看看sys-kernel-debug.mout文件,这个文件定义了一个文件挂载点:
[root@kalaguiyinsystem]#cat
sys-kernel-debug.mount
[Unit]
Description=DebugFileSystem
Documentation=https://www.kernel.org/doc/Documentation/filesystems/debugfs.txt
Documentation=http://www.freedesktop.org/wiki/Software/systemd/APIFileSystems
DefaultDependencies=no
ConditionPathExists=/sys/kernel/debug
Before=sysinit.target
[Mount]
What=debugfs
Where=/sys/kernel/debug
Type=debugfs
这个配置单元文件定义了一个挂载点。挂载配置单元文件有一个[Mount]配置小节,里面配置了What,Where和Type三个数据项。这都是挂载命令所必须的,例子中的配置等同于下面这个挂载命令:
mount–tdebugfs/sys/kernel/debugdebugfs
Systemd系统管理:
systemd的主要命令行工具是systemctl。
多数管理员应该都已经非常熟悉系统服务和init系统的管理,比如service、chkconfig以及telinit命令的使用。systemd也完成同样的管理任务,只是命令工具systemctl的语法有所不同而已。
启动服务
systemctlstarthttpd.service如图1:
停止服务
systemctlstophttpd.service如图2:
重启服务
systemctlrestarthttpd.service如图3:
重载服务
systemctlreloadhttpd.service
条件式重启
systemctlcondrestarthttpd.service
状态查看
systemctlstatushttpd.service
列出可以启动或停止的服务列表。
systemctllist-unit-files–type=service
设置服务为开机启动
chkconfighttpdon
systemctlenablehttpd.service
取消服务开机启动;
systemctldisablehttpd.service
检查一个服务在当前环境下被配置为启用还是禁用。
systemctlis-enabledhttpd.service;echo$?
输出在各个运行级别下服务的启用和禁用情况
systemctllist-unit-files–type=service
列出某服务在哪些运行级别下启用和禁用。
ls/etc/lib/systemd/system/*.wants/httpd.service
改变用户运行级别:
systemctlisolatemulti-user.target
multi-user.target==第3运行级别
graphical.target==第5运行级别
runlevel3.target符号链接,指向multi-user.target
runlevel5.target符号链接,指向graphical.target
改变默认运行级别:
[root@kalaguiyinsystem]#systemctlset-defaultmulti-user.target
rm'/etc/systemd/system/default.target'
ln-s'/usr/lib/systemd/system/multi-user.target''/etc/systemd/system/default.target'
上述操作的实质是删除/usr/lib/systemd/system/default.target,然后将目标级别的target文件链接至/etc/systemd/system/default.target文件;
systemd已经不仅仅是一个初始化系统了:
systemd还负责系统其他的管理配置,比如配置网络,Locale管理,管理系统内核模块加载等。
Systemd出色地替代了sysvinit的所有功能,但它并未就此自满。因为init进程是系统所有进程的父进程这样的特殊性,systemd非常适合提供曾经由其他服务提供的功能,比如定时任务(以前由crond完成);会话管理(以前由ConsoleKit/PolKit等管理)。仅仅从本文皮毛一样的介绍来看,Systemd已经管得很多了,可它还在不断发展。它将逐渐成为一个多功能的系统环境,能够处理非常多的系统管理任务,有人甚至将它看作一个操作系统。这非常有助于标准化Linux的管理!