假如您使用的 Linux 发行版是 Ubuntu,极可能会发如今您的计算机上找不到/etc/inittab 文件了,这是由于 Ubuntu 使用了一种被称为 upstart 的新型 init 系统。 html
大 约在 2006 年或者更早的时候, Ubuntu 开发人员试图将 Linux 安装在笔记本电脑上。在这期间技术人员发现经典的 sysvinit 存在一些问题:它不适合笔记本环境。这促使程序员 Scott James Remnant 着手开发 upstart。 linux
当 Linux 内核进入 2.6 时代时,内核功能有了不少新的更新。新特性使得 Linux 不只是一款优秀的服务器操做系统,也能够被用于桌面系统,甚至嵌入式设备。桌面系统或便携式设备的一个特色是常常重启,并且要频繁地使用硬件热插拔技术。 在现代计算机系统中,硬件繁多、接口有限,人们并不是将全部设备都始终链接在计算机上,好比 U 盘平时并不链接电脑,使用时才插入 USB 插口。所以,当系统上电启动时,一些外设可能并无链接。而是在启动后当须要的时候才链接这些设备。在 2.6 内核支持下,一旦新外设链接到系统,内核即可以自动实时地发现它们,并初始化这些设备,进而使用它们。这为便携式设备用户提供了很大的灵活性。 程序员
可 是这些特性为 sysvinit 带来了一些挑战。当系统初始化时,须要被初始化的设备并无链接到系统上;好比打印机。为了管理打印任务,系统须要启动 CUPS 等服务,而若是打印机没有接入系统的状况下,启动这些服务就是一种浪费。Sysvinit 没有办法处理这类需求,它必须一次性把全部可能用到的服务都启动起来,即便打印机并无链接到系统,CUPS 服务也必须启动。 shell
还有网络共 享盘的挂载问题。在/etc/fstab 中,能够指定系统自动挂载一个网络盘,好比 NFS,或者 iSCSI 设备。在本文的第一部分 sysvinit 的简介中能够看到,sysvinit 分析/etc/fstab 挂载文件系统这个步骤是在网络启动以前。但是若是网络没有启动,NFS 或者 iSCSI 都不可访问,固然也没法进行挂载操做。Sysvinit 采用 netdev 的方式来解决这个问题,即/etc/fstab 发现 netdev 属性挂载点的时候,不尝试挂载它,在网络初始化并使能以后,还有一个专门的 netfs 服务来挂载全部这些网络盘。这是一个不得已的补救方法,给管理员带来不便。部分新手管理员甚至历来也没有据说过 netdev 选项,所以常常成为系统管理的一个陷阱。 apache
针对以上种种状况,Ubuntu 开发人员在评估了当时的几个可选 init 系统以后,决定从新设计和开发一个全新的 init 系统,即 UpStart。UpStart 基于事件机制,好比 U 盘插入 USB 接口后,udev 获得内核通知,发现该设备,这就是一个新的事件。UpStart 在感知到该事件以后触发相应的等待任务,好比处理/etc/fstab 中存在的挂载点。采用这种事件驱动的模式,upstart 完美地解决了即插即用设备带来的新问题。 编程
此外,采用事件驱动机制也带来了一些其它有益的变化,好比加快了系统启动时间。sysvinit 运行时是同步阻塞的。一个脚本运行的时候,后续脚本必须等待。这意味着全部的初始化步骤都是串行执行的,而实际上不少服务彼此并不相关,彻底能够并行启 动,从而减少系统的启动时间。在 Linux 大量应用于服务器的时代,系统启动时间也许还不那么重要;然而对于桌面系统和便携式设备,启动时间的长短对用户体验影响很大。此外云计算等新的 Server 端技术也每每须要单个设备能够更加快速地启动。 ubuntu
UpStart 知足了这些需求,目前不只桌面系统 Ubuntu 采用了 UpStart,甚至企业级服务器级的 RHEL 也默认采用 UpStart 来替换 sysvinit 做为 init 系统。 bash
UpStart 解决了以前提到的 sysvinit 的缺点。采用事件驱动模型,UpStart 能够: 服务器
这些特色使得 UpStart 能够很好地应用在桌面或者便携式系统中,处理这些系统中的动态硬件插拔特性。 网络
Upstart 的基本概念和设计清晰明确。UpStart 主要的概念是 job 和 event。Job 就是一个工做单元,用来完成一件工做,好比启动一个后台服务,或者运行一个配置命令。每一个 Job 都等待一个或多个事件,一旦事件发生,upstart 就触发该 job 完成相应的工做。
Job 就是一个工做的单元,一个任务或者一个服务。能够理解为 sysvinit 中的一个服务脚本。有三种类型的工做:
task job 表明在必定时间内会执行完毕的任务,好比删除一个文件;
service job 表明后台服务进程,好比 apache httpd。这里进程通常不会退出,一旦开始运行就成为一个后台精灵进程,由 init 进程管理,若是这类进程退出,由 init 进程从新启动,它们只能由 init 进程发送信号中止。它们的中止通常也是因为所依赖的中止事件而触发的,不过 upstart 也提供命令行工具,让管理人员手动中止某个服务;
Abstract job 仅由 upstart 内部使用,仅对理解 upstart 内部机理有所帮助。咱们不用关心它。
除 了以上的分类以外,还有另外一种工做(Job)分类方法。Upstart 不只能够用来为整个系统的初始化服务,也能够为每一个用户会话(session)的初始化服务。系统的初始化任务就叫作 system job,好比挂载文件系统的任务就是一个 system job;用户会话的初始化服务就叫作 session job。
Upstart 为每一个工做都维护一个生命周期。通常来讲,工做有开始,运行和结束这几种状态。为了更精细地描述工做的变化,Upstart 还引入了一些其它的状态。好比开始就有开始以前(pre-start),即将开始(starting)和已经开始了(started)几种不一样的状态,这 样能够更加精确地描述工做的当前状态。
工做从某种初始状态开始,逐渐变化,或许要经历其它几种不一样的状态,最终进入另一种状态,造成一个状态机。在这个过程当中,当工做的状态即将发生变化的时候,init 进程会发出相应的事件(event)。
状态名 | 含义 |
---|---|
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 段 |
图 1 展现了 Job 的状态机。
其中有四个状态会引发 init 进程发送相应的事件,代表该工做的相应变化:
而其它的状态变化不会发出事件。那么咱们接下来就来看看事件的详细含义吧。
顾 名思义,Event 就是一个事件。事件在 upstart 中以通知消息的形式具体存在。一旦某个事件发生了,Upstart 就向整个系统发送一个消息。没有任何手段阻止事件消息被 upstart 的其它部分知晓,也就是说,事件一旦发生,整个 upstart 系统中全部工做和其它的事件都会获得通知。
Event 能够分为三类: signal,methods 或者 hooks。
Signals
Signal 事件是非阻塞的,异步的。发送一个信号以后控制权当即返回。
Methods
Methods 事件是阻塞的,同步的。
Hooks
Hooks 事件是阻塞的,同步的。它介于 Signals 和 Methods 之间,调用发出 Hooks 事件的进程必须等待事件完成才能够获得控制权,但不检查事件是否成功。
事件是个很是抽象的概念,下面我罗列出一些常见的事件,但愿能够帮助您进一步了解事件的含义:
不一样的 Linux 发行版对 upstart 有不一样的定制和实现,实现和支持的事件也有所不一样,能够用man 7 upstart-events来查看事件列表。
Upstart 就是由事件触发工做运行的一个系统,每个程序的运行都由其依赖的事件发生而触发的。
系 统初始化的过程是在工做和事件的相互协做下完成的,能够大体描述以下:系统初始化时,init 进程开始运行,init 进程自身会发出不一样的事件,这些最初的事件会触发一些工做运行。每一个工做运行过程当中会释放不一样的事件,这些事件又将触发新的工做运行。如此反复,直到整个 系统正常运行起来。
究竟哪些事件会触发某个工做的运行?这是由工做配置文件定义的。
任 何一个工做都是由一个工做配置文件(Job Configuration File)定义的。这个文件是一个文本文件,包含一个或者多个小节(stanza)。每一个小节是一个完整的定义模块,定义了工做的一个方面,好比 author 小节定义了工做的做者。工做配置文件存放在/etc/init 下面,是以.conf 做为文件后缀的文件。
#This is a simple demo of Job Configure file #This line is comment, start with # #Stanza 1, The author author “Liu Ming” #Stanza 2, Description description “This job only has author and description, so no use, just a demo”
上面的例子不会产生任何做用,一个真正的工做配置文件会包含不少小节,其中比较重要的小节有如下几个:
"expect" Stanza
Upstart 除了负责系统的启动过程以外,和 SysVinit 同样,Upstart 还提供一系列的管理工具。当系统启动以后,管理员可能还须要进行维护和调整,好比启动或者中止某项系统服务。或者将系统切换到其它的工做状态,好比改变运 行级别。本文后续将详细介绍 Upstart 的管理工具的使用。
为了启动,中止,重启和查询某个系统服务。Upstart 须要跟踪该服务所对应的进程。好比 httpd 服务的进程 PID 为 1000。当用户须要查询 httpd 服务是否正常运行时,Upstart 就能够利用 ps 命令查询进程 1000,假如它还在正常运行,则代表服务正常。当用户须要中止 httpd 服务时,Upstart 就使用 kill 命令终止该进程。为此,Upstart 必须跟踪服务进程的进程号。
部分服务进程为了将本身变成后台精灵进程(daemon), 会采用两次派生(fork)的技术,另一些服务则不会这样作。假如一个服务派生了两次,那么 UpStart 必须采用第二个派生出来的进程号做为服务的 PID。可是,UpStart 自己没法判断服务进程是否会派生两次,为此在定义该服务的工做配置文件中必须写明 expect 小节,告诉 UpStart 进程是否会派生两次。
Expect 有两种,"expect fork"表示进程只会 fork 一次;"expect daemonize"表示进程会 fork 两次。
"exec" Stanza 和"script" Stanza
一个 UpStart 工做必定须要作些什么,多是运行一条 shell 命令,或者运行一段脚本。用"exec"关键字配置工做须要运行的命令;用"script"关键字定义须要运行的脚本。
清单 2 显示了 exec 和 script 的用法:
# mountall.conf description “Mount filesystems on boot” start on startup stop on starting rcS ... script . /etc/default/rcS [ -f /forcefsck ] && force_fsck=”--force-fsck” [ “$FSCKFIX”=”yes” ] && fsck_fix=”--fsck-fix” ... exec mountall –daemon $force_fsck $fsck_fix end script ...
这是 mountall 的例子,该工做在系统启动时运行,负责挂载全部的文件系统。该工做须要执行复杂的脚本,由"script"关键字定义;在脚本中,使用了 exec 来执行 mountall 命令。
"start on" Stanza 和"stop on" Stanza
"start on"定义了触发工做的全部事件。"start on"的语法很简单,以下所示:
start on EVENT [[KEY=]VALUE]... [and|or...]
EVENT 表示事件的名字,能够在 start on 中指定多个事件,表示该工做的开始须要依赖多个事件发生。多个事件之间能够用 and 或者 or 组合,"表示所有都必须发生"或者"其中之一发生便可"等不一样的依赖条件。除了事件发生以外,工做的启动还能够依赖特定的条件,所以在 start on 的 EVENT 以后,能够用 KEY=VALUE 来表示额外的条件,通常是某个环境变量(KEY)和特定值(VALUE)进行比较。若是只有一个变量,或者变量的顺序已知,则 KEY 能够省略。
"stop on"和"start on"很是相似,只不过是定义工做在什么状况下须要中止。
代码清单 3 是"start on"和"stop on"的一个例子。
#dbus.conf description “D-Bus system message bus” start on local-filesystems stop on deconfiguring-networking …
D-Bus 是一个系统消息服务,上面的配置文件代表当系统发出 local-filesystems 事件时启动 D-Bus;当系统发出 deconfiguring-networking 事件时,中止 D-Bus 服务。
UpStart 还能够用于管理用户会话的初始化。在我写这篇文章的今天,多数 Linux 发行版尚未使用 UpStart 管理会话。只有在 Ubuntu Raring 版本中,使用 UpStart 管理用户会话的初始化过程。
首先让咱们了解一下 Session 的概念。Session 就是一个用户会话,即用户从远程或者本地登入系统开始工做,直到用户退出。这整个过程就构成一个会话。
每一个用户的使用习惯和使用方法都不相同,所以用户每每须要为本身的会话作一个定制,好比添加特定的命令别名,启动特殊的应用程序或者服务,等等。这些工做都属于对特定会话的初始化操做,所以能够被称为 Session Init。
用 户使用 Linux 能够有两种模式:字符模式和图形界面。在字符模式下,会话初始化相对简单。用户登陆后只能启动一个 Shell,经过 shell 命令使用系统。各类 shell 程序都支持一个自动运行的启动脚本,好比~/.bashrc。用户在这些脚本中加入须要运行的定制化命令。字符会话需求简单,所以这种现有的机制工做的很 好。
在图形界面下,事情就变得复杂一些。用户登陆后看到的并非一个 shell 提示符,而是一个桌面。一个完整的桌面环境由不少组件组成。
一 个桌面环境包括 window manager,panel 以及其它一些定义在/usr/share/gnome-session/sessions/下面的基本组件;此外还有一些辅助的应用程序,共同帮助构成一 个完整的方便的桌面,好比 system monitors,panel applets,NetworkManager,Bluetooth,printers 等。当用户登陆以后,这些组件都须要被初始化,这个过程比字符界面要复杂的多。目前启动各类图形组件和应用的工做由 gnome-session 完成。过程以下:
以 Ubuntu 为例,当用户登陆 Ubuntu 图形界面后,显示管理器(Display Manager)lightDM 启动 Xsession。Xsession 接着启动 gnome-session,gnome-session 负责其它的初始化工做,而后就开始了一个 desktop session。
init |- lightdm | |- Xorg | |- lightdm ---session-child | |- gnome-session --session=ubuntu | |- compiz | |- gwibber | |- nautilus | |- nm-applet | : | : | |- dbus-daemon --session | : :
这个过程有一些缺点(和 sysVInit 相似)。一些应用和组件其实并不须要在会话初始化过程当中启动,更好的选择是在须要它们的时候才启动。好比 update-notifier 服务,该服务不停地监测几个文件系统路径,一旦这些路径上发现能够更新的软件包,就提醒用户。这些文件系统路径包括新插入的 DVD 盘等。Update-notifier 由 gnome-session 启动并一直运行着,在多数状况下,用户并不会插入新的 DVD,此时 update-notifier 服务一直在后台运行并消耗系统资源。更好的模式是当用户插入 DVD 的时候再运行 update-notifier。这样能够加快启动时间,减少系统运行过程当中的内存等系统资源的开销。对于移动,嵌入式等设备等这还意味着省电。除了 Update-notifier 服务以外,还有其它一些相似的服务。好比 Network Manager,一天以内用户不多切换网络设备,因此大部分时间 Network Manager 服务仅仅是在浪费系统资源;再好比 backup manager 等其它常驻内存,后台不间断运行却不多真正被使用的服务。
用 UpStart 的基于事件的按需启动的模式就能够很好地解决这些问题,好比用户插入网线的时候才启动 Network Manager,由于用户插入网线代表须要使用网络,这能够被称为按需启动。
下图描述了采用 UpStart 以后的会话初始化过程。
init |- lightdm | |- Xorg | |- lightdm ---session-child | |- session-init # <-- upstart running as normal user | |- dbus-daemon --session | |- gnome-session --session=ubuntu | |- compiz | |- gwibber | |- nautilus | |- nm-applet | : | : : :
有两种人员须要了解 Upstart 的使用。第一类是系统开发人员,好比 MySQL 的开发人员。它们须要了解如何编写工做配置文件,以便用 UpStart 来管理服务。好比启动,中止 MySQL 服务。
另一种状况是系统管理员,它们须要掌握 Upstart 的管理命令以便配置和管理系统的初始化,管理系统服务。
系 统开发人员不只须要掌握工做配置文件的写法,还须要了解一些针对服务进程编程上的要求。本文仅列出了少数工做配置文件的语法。要全面掌握工做配置文件的写 法,须要详细阅读 Upstart 的手册。这里让咱们来分析一下如何用 Upstart 来实现传统的运行级别,进而了解如何灵活使用工做配置文件。
Upstart 系统中的运行级别
Upstart 的运做彻底是基于工做和事件的。工做的状态变化和运行会引发事件,进而触发其它工做和事件。
而 传统的 Linux 系统初始化是基于运行级别的,即 SysVInit。由于历史的缘由,Linux 上的多数软件仍是采用传统的 SysVInit 脚本启动方式,并无为 UpStart 开发新的启动脚本,所以即使在 Debian 和 Ubuntu 系统上,仍是必须模拟老的 SysVInit 的运行级别模式,以便和多数现有软件兼容。
虽然 Upstart 自己并无运行级别的概念,但彻底能够用 UpStart 的工做模拟出来。让咱们完整地考察一下 UpStart 机制下的系统启动过程。
系统启动过程
下图描述了 UpStart 的启动过程。
系统上电后运行 GRUB 载入内核。内核执行硬件初始化和内核自身初始化。在内核初始化的最后,内核将启动 pid 为 1 的 init 进程,即 UpStart 进程。
Upstart 进程在执行了一些自身的初始化工做后,当即发出"startup"事件。上图中用红色方框加红色箭头表示事件,能够在左上方看到"startup"事件。
所 有依赖于"startup"事件的工做被触发,其中最重要的是 mountall。mountall 任务负责挂载系统中须要使用的文件系统,完成相应工做后,mountall 任务会发出如下事件:local-filesystem,virtual-filesystem,all-swaps,
其中 virtual-filesystem 事件触发 udev 任务开始工做。任务 udev 触发 upstart-udev-bridge 的工做。Upstart-udev-bridge 会发出 net-device-up IFACE=lo 事件,表示本地回环 IP 网络已经准备就绪。同时,任务 mountall 继续执行,最终会发出 filesystem 事件。
此时,任务 rc-sysinit 会被触发,由于 rc-sysinit 的 start on 条件以下:
start on filesystem and net-device-up IFACE=lo
任务 rc-sysinit 调用 telinit。Telinit 任务会发出 runlevel 事件,触发执行/etc/init/rc.conf。
rc.conf 执行/etc/rc$.d/目录下的全部脚本,和 SysVInit 很是相似,读者能够参考本文第一部分的描述。
程序开发时须要注意的事项
做为程序开发人员,在编写系统服务时,须要了解 UpStart 的一些特殊要求。只有符合这些要求的软件才能够被 UpStart 管理。
规则一,派生次数需声明。
很 多 Linux 后台服务都经过派生两次的技巧将本身变成后台服务程序。若是您编写的服务也采用了这个技术,就必须经过文档或其它的某种方式明确地让 UpStart 的维护人员知道这一点,这将影响 UpStart 的 expect stanza,咱们在前面已经详细介绍过这个 stanza 的含义。
规则二,派生后便可用。
后台程序在完成第二次派生的时候,必须保证服务已经可用。由于 UpStart 经过派生计数来决定服务是否处于就绪状态。
规则三,遵照 SIGHUP 的要求。
UpStart 会给精灵进程发送 SIGHUP 信号,此时,UpStart 但愿该精灵进程作如下这些响应工做:
•完成全部必要的从新初始化工做,好比从新读取配置文件。这是由于 UpStart 的命令"initctl reload"被设计为可让服务在不重启的状况下更新配置。
•精灵进程必须继续使用现有的 PID,即收到 SIGHUP 时不能调用 fork。若是服务必须在这里调用 fork,则等同于派生两次,参考上面的规则一的处理。这个规则保证了 UpStart 能够继续使用 PID 管理本服务。
规则四,收到 SIGTEM 即 shutdown。
•当收到 SIGTERM 信号后,UpStart 但愿精灵进程进程当即干净地退出,释放全部资源。若是一个进程在收到 SIGTERM 信号后不退出,Upstart 将对其发送 SIGKILL 信号。
做为系统管理员,一个重要的职责就是管理系统服务。好比系统服务的监控,启动,中止和配置。UpStart 提供了一系列的命令来完成这些工做。其中的核心是initctl,这是一个带子命令风格的命令行工具。
好比能够用 initctl list 来查看全部工做的概况:
$initctl list alsa-mixer-save stop/waiting avahi-daemon start/running, process 690 mountall-net stop/waiting rc stop/waiting rsyslog start/running, process 482 screen-cleanup stop/waiting tty4 start/running, process 859 udev start/running, process 334 upstart-udev-bridge start/running, process 304 ureadahead-other stop/waiting
这是在 Ubuntu10.10 系统上的输出,其它的 Linux 发行版上的输出会有所不一样。第一列是工做名,好比 rsyslog。第二列是工做的目标;第三列是工做的状态。
此 外还能够用 initctl stop 中止一个正在运行的工做;用 initctl start 开始一个工做;还能够用 initctl status 来查看一个工做的状态;initctl restart 重启一个工做;initctl reload 可让一个正在运行的服务从新载入配置文件。这些命令和传统的 service 命令十分类似。
Service 命令 | UpStart initctl 命令 |
---|---|
service start | initctl start |
service stop | initctl stop |
service restart | initctl restart |
service reload | initctl reload |
不少状况下管理员并不喜欢子命令风格,由于须要手动键入的字符太多。UpStart 还提供了一些快捷命令来简化 initctl,实际上这些命令只是在内部调用相应的 initctl 命令。好比 reload,restart,start,stop 等等。启动一个服务能够简单地调用
start <job>
这和执行 initctl start <job>是同样的效果。
一些命令是为了兼容其它系统(主要是 sysvinit),好比显示 runlevel 用/sbin/runlevel 命令:
$runlevel N 2
这个输出说明当前系统的运行级别为 2。并且系统没有以前的运行级别,也就是说在系统上电启动进入预约运行级别以后没有再修改过运行级别。
那么如何修改系统上电以后的默认运行级别呢?
在 Upstart 系统中,须要修改/etc/init/rc-sysinti.conf 中的 DEFAULT_RUNLEVEL 这个参数,以便修改默认启动运行级别。这一点和 sysvinit 的习惯有所不一样,你们须要格外留意。
还有一些随 UpStart 发布的小工具,用来帮助开发 UpStart 或者诊断 UpStart 的问题。好比 init-checkconf 和 upstart-monitor
还可使用 initctl 的 emit 命令从命令行发送一个事件。
#initctl emit <event>
这通常是用于 UpStart 自己的排错。
可 以看到,UpStart 的设计比 SysVInit 更加先进。多数 Linux 发行版上已经再也不使用 SysVInit,一部分发行版采用了 UpStart,好比 Ubuntu;而另一些好比 Fedora,采用了一种被称为 systemd 的 init 系统。Systemd 出现的比 UpStart 更晚,但发展迅速,虽然 UpStart 也还在积极开发并被愈来愈多地应用,但 systemd 彷佛发展更快,我将在下一篇文章中再介绍 systemd。