Upstart简介
假如您使用的 Linux 发行版是 Ubuntu,很可能会发现在您的计算机上找不到/etc/inittab 文件了,这是因为 Ubuntu 使用了一种被称为 upstart 的新型 init 系统。
开发Upstart的缘由
大约在 2006 年或者更早的时候, Ubuntu 开发人员试图将 Linux 安装在笔记本电脑上。在这期间技术人员发现经典的 sysvinit 存在一些问题:它不适合笔记本环境。这促使程序员 Scott James Remnant 着手开发 upstart。
当 Linux 内核进入 2.6 时代时,内核功能有了很多新的更新。新特性使得 Linux 不仅是一款优秀的服务器操作系统,也可以被用于桌面系统,甚至嵌入式设备。桌面系统或便携式设备的一个特点是经常重启,而且要频繁地使用硬件热插拔技术。在现代计算机系统中,硬件繁多、接口有限,人们并非将所有设备都始终连接在计算机上,比如 U 盘平时并不连接电脑,使用时才插入 USB 插口。因此,当系统上电启动时,一些外设可能并没有连接。而是在启动后当需要的时候才连接这些设备。在 2.6 内核支持下,一旦新外设连接到系统,内核便可以自动实时地发现它们,并初始化这些设备,进而使用它们。这为便携式设备用户提供了很大的灵活性。
可是这些特性为 sysvinit 带来了一些挑战。当系统初始化时,需要被初始化的设备并没有连接到系统上;比如打印机。为了管理打印任务,系统需要启动 CUPS 等服务,而如果打印机没有接入系统的情况下,启动这些服务就是一种浪费。Sysvinit 没有办法处理这类需求,它必须一次性把所有可能用到的服务都启动起来,即使打印机并没有连接到系统,CUPS 服务也必须启动。
还有网络共享盘的挂载问题。在/etc/fstab 中,可以指定系统自动挂载一个网络盘,比如 NFS,或者 iSCSI 设备。在本文的第一部分 sysvinit 的简介中可以看到,sysvinit 分析/etc/fstab 挂载文件系统这个步骤是在网络启动之前。可是如果网络没有启动,NFS 或者 iSCSI 都不可访问,当然也无法进行挂载操作。Sysvinit 采用 netdev 的方式来解决这个问题,即/etc/fstab 发现 netdev 属性挂载点的时候,不尝试挂载它,在网络初始化并使能之后,还有一个专门的 netfs 服务来挂载所有这些网络盘。这是一个不得已的补救方法,给管理员带来不便。部分新手管理员甚至从来也没有听说过 netdev 选项,因此经常成为系统管理的一个陷阱。
针对以上种种情况,Ubuntu 开发人员在评估了当时的几个可选 init 系统之后,决定重新设计和开发一个全新的 init 系统,即 UpStart。UpStart 基于事件机制,比如 U 盘插入 USB 接口后,udev 得到内核通知,发现该设备,这就是一个新的事件。UpStart 在感知到该事件之后触发相应的等待任务,比如处理/etc/fstab 中存在的挂载点。采用这种事件驱动的模式,upstart 完美地解决了即插即用设备带来的新问题。
此外,采用事件驱动机制也带来了一些其它有益的变化,比如加快了系统启动时间。sysvinit 运行时是同步阻塞的。一个脚本运行的时候,后续脚本必须等待。这意味着所有的初始化步骤都是串行执行的,而实际上很多服务彼此并不相关,完全可以并行启动,从而减小系统的启动时间。在 Linux 大量应用于服务器的时代,系统启动时间也许还不那么重要;然而对于桌面系统和便携式设备,启动时间的长短对用户体验影响很大。此外云计算等新的 Server 端技术也往往需要单个设备可以更加快速地启动。
UpStart 满足了这些需求,目前不仅桌面系统 Ubuntu 采用了 UpStart,甚至企业级服务器级的 RHEL 也默认采用 UpStart 来替换 sysvinit 作为 init 系统。
Upstart的特点
UpStart解决了之前提到的sysvinit的缺点。采用时间驱动模型,UpStart可以:
- 更快地启动系统
- 当新硬件被发现时动态启动服务
- 硬件被拔出时动态停止服务
这些特点使得 UpStart 可以很好地应用在桌面或者便携式系统中,处理这些系统中的动态硬件插拔特性。
Upstart概念和术语
Upstart 的基本概念和设计清晰明确。UpStart 主要的概念是 job 和 event。Job 就是一个工作单元,用来完成一件工作,比如启动一个后台服务,或者运行一个配置命令。每个 Job 都等待一个或多个事件,一旦事件发生,upstart 就触发该 job 完成相应的工作。
Job
Job 就是一个工作的单元,一个任务或者一个服务。可以理解为 sysvinit 中的一个服务脚本。有三种类型的工作:
- task job;
- service job;
- abstract job;
task job 代表在一定时间内会执行完毕的任务,比如删除一个文件;
service job 代表后台服务进程,比如 apache httpd。这里进程一般不会退出,一旦开始运行就成为一个后台精灵进程,由 init 进程管理,如果这类进程退出,由 init 进程重新启动,它们只能由 init 进程发送信号停止。它们的停止一般也是由于所依赖的停止事件而触发的,不过 upstart 也提供命令行工具,让管理人员手动停止某个服务;
Abstract job 仅由 upstart 内部使用,仅对理解 upstart 内部机理有所帮助。我们不用关心它。
除了以上的分类之外,还有另一种工作(Job)分类方法。Upstart 不仅可以用来为整个系统的初始化服务,也可以为每个用户会话(session)的初始化服务。系统的初始化任务就叫做 system job,比如挂载文件系统的任务就是一个 system job;用户会话的初始化服务就叫做 session job。
Job生命周期
Upstart 为每个工作都维护一个生命周期。一般来说,工作有开始,运行和结束这几种状态。为了更精细地描述工作的变化,Upstart 还引入了一些其它的状态。比如开始就有开始之前(pre-start),即将开始(starting)和已经开始了(started)几种不同的状态,这样可以更加精确地描述工作的当前状态。
工作从某种初始状态开始,逐渐变化,或许要经历其它几种不同的状态,最终进入另外一种状态,形成一个状态机。在这个过程中,当工作的状态即将发生变化的时候,init 进程会发出相应的事件(event)。
Upstart中Job的可能状态
状态名 | 含义 |
---|---|
Waiting | 初始状态 |
Starting | Job即将开始 |
pre-start | 执行pre-start段,即任务开始前应该完成的工作 |
Spawned | 准备执行script或者exec段 |
post-start | 执行post-start动作 |
Running | interim state set after post-start section processed denoting job is running (But it may have no associated PID!) |
pre-stop | 执行pre-stop段 |
Stopping | interim state set after pre-stop section processed |
Killed | 任务即将被停止 |
post-stop | 执行post-stop段 |
展示了 Job 的状态机
其中有四个状态会引起 init 进程发送相应的事件,表明该工作的相应变化:
- Starting
- Started
- Stopping
- Stopped
事件 Event
顾名思义,Event 就是一个事件。事件在 upstart 中以通知消息的形式具体存在。一旦某个事件发生了,Upstart 就向整个系统发送一个消息。没有任何手段阻止事件消息被 upstart 的其它部分知晓,也就是说,事件一旦发生,整个 upstart 系统中所有工作和其它的事件都会得到通知。
Event 可以分为三类: signal,methods 或者 hooks。
- Signals:
- Signal 事件是非阻塞的,异步的。发送一个信号之后控制权立即返回。
- Methods:
- Methods事件是阻塞的、同步的
- Hooks:
- Hooks 事件是阻塞的,同步的。它介于 Signals 和 Methods 之间,调用发出 Hooks 事件的进程必须等待事件完成才可以得到控制权,但不检查事件是否成功
事件是个非常抽象的概念,下面我罗列出一些常见的事件,希望可以帮助您进一步了解事件的含义:
- 系统上电启动,init 进程会发送”start”事件
- 根文件系统可写时,相应 job 会发送文件系统就绪的事件
- 一个块设备被发现并初始化完成,发送相应的事件
- 某个文件系统被挂载,发送相应的事件
- 类似 atd 和 cron,可以在某个时间点,或者周期的时间点发送事件
- 另外一个 job 开始或结束时,发送相应的事件
- 一个磁盘文件被修改时,可以发出相应的事件
- 一个网络设备被发现时,可以发出相应的事件
- 缺省路由被添加或删除时,可以发出相应的事件
不同的 Linux 发行版对 upstart 有不同的定制和实现,实现和支持的事件也有所不同,可以用man 7 upstart-events
来查看事件列表。
Upstart在机器启动时的执行过程
在机器做完那些加载内核,挂载跟目录等工作后,操作系统会调用 /sbin/init 来接管后续的服务启动过程。
init 启动后马上发出第一个事件 startup
hostname,mountall 会被 startup 事件触发(在 /etc/init 目录下 grep startup * 就可以看到了!),也就是一启动就开始分别设置 hostname 和 挂载硬盘。
在 mountall.conf 里可以看到它主要运行了 mountall 程序,这个程序会读取 /lib/init/fstab 和 /etc/fstab 里的配置,按顺序挂载。其中 /lib/init/fstab 里的都是像 /proc /sys 这样的虚拟磁盘。
mountall 会发出很多可以触发 Upstart 的事件:
emits virtual-filesystems emits local-filesystems emits remote-filesystems emits all-swaps emits filesystem emits mounting emits mounted
每挂载好一个磁盘都会发出一个 mounted 事件,当挂载完 /lib/init/fstab 里的虚拟磁盘后会发出 virtual-filesystems 事件,mountall 继续处理 /etc/fstab 里的配置,期间会发出 remote-filesystems, all-swaps等事件,全部处理完后会发送 local-filesystems 和 filesystem 事件。
也就是说,当 Upstart 接收到 filesystem 事件时,配置文件中的磁盘已经挂载好了。
mountall 发出的很多事件,有些是以阻塞的方式发出的,也就是在 mountall 过程中有些别的任务已经被触发并且可能已经完成了。
udev.conf 在接收到 virtual-filesystem 时出发,这是一个管理 /dev 下的设备描述的程序。
- udev 导致 upstart-udev-bridge 任务执行,这个任务会设置好 127.0.0.0 这个网络回路地址。
- udev 和 filesystem 事件还是开始网络设置的前提。
在设置网络之前,ufw 的配置也会准备就绪。
网络和文件系统都准备好后出发 rc-sysinit.conf 里的脚本执行,里面其实就是打开 telinit 程序并传入一个2作为参数,这个程序发出 runlevel 事件。
- rc.conf 会被 runlevel 事件触发,它会调用 /etc/init.d/rc 2 , 这是兼容传统 init 的关键步骤。
- 其他 job 也会被 runlevel 事件触发,例如 cron、ssh、irqbalance 等。
Upstart小结
可以看到,UpStart 的设计比 SysVInit 更加先进。多数 Linux 发行版上已经不再使用 SysVInit,一部分发行版采用了 UpStart,比如 Ubuntu;而另外一些比如 Fedora,采用了一种被称为 systemd 的 init 系统。Systemd 出现的比 UpStart 更晚,但发展迅速,虽然 UpStart 也还在积极开发并被越来越多地应用,但 systemd 似乎发展更快,我将在下一篇文章中再介绍 systemd。