从上电到switchroot


让我们从启动开始,看看FC6都做了些什么。

众所周知,和所有别的发行版本一样,FC6是由grab引导的,grab通常被安装在主引导扇区,也就是说,如果你在主板的bios中设置了从硬盘启动,那么主板自检以后所执行的第一部分代码就是grub,grub将在其安装时候指定的位置寻找 menu.lst这个文件,并且根据这个文件的配置,加载相应的内核,启动linux。这里值得我们注意的是,由于grub的这种机制,即使是格式化你觉得已经完全不再使用的硬盘分区,也可能造成灾难性的后果,假设我们把grub安装在了 mbr,并且将配置文件放置在hda2,hda1安装了一套windows操作系统,通过grub实现多重引导,但是现在我们想放弃hda2的linux 系统,或者想把它换成另外一套linux发行版,我们可能会选择格式化hda2,虽然grub被安装在mbr,但是hda2的被格式化仍然会破坏其配置文件所在的目录,grub将无法正常启动,你也就无法正常引导位于hda1的windows系统了,因为grub会提示错误,不给你选择系统的机会。这种情况在实际的双系统使用过程中,可能经常会遇到。 遇到这种问题,常见的修复方法是使用软盘启动windows,使用fdisk /mbr命令使用windows系统提供的mbr覆盖gurb的mbr代码,或者使用其他方式启动linux(软盘,U盘或者光盘),重新安装grub。虽然天不会塌下来,但是相信也会让你很不爽了,所以要小心。

内核是所有linux的核心,grub在成功的读取了配置文件以后,将会找到 kernel所在的位置,加载内核并且把电脑的控制权交给kernel程序,在FC对应的grub的menu.lst文件中,我们通常可以看到类似这样的语句:root (hd0,2) 这句话告诉我们,从现在开始,根路径将被设置为第一个硬盘的第3个分区,然后是kernel /boot/vmlinuz-2.6.18-1.2798.fc6 ro root=LABEL=/ rhgb quiet,这句话告诉我们,从根分区的boot目录的vmlinuz-2.6.18-1.2798.fc6这个文件中读取内核,执行内核的时候使用 "ro root=LABEL=/ rhgb quiet"这样的参数,内核的执行参数可以控制内核的行为,比如ro参数告诉内核,以只读方式挂载根分区,而quiet则告诉内核,启动的时候不要打印任何信息。这些参数不光影响内核的执行,大多数的发行版也使用这些参数控制启动完毕以后后续的动作。这些参数可以在任何时候从/proc/cmdline 这个文件中获得。

现在,grub找到了内核(hd0,2) /boot/vmlinuz-2.6.18-1.2798.fc6,它将整个电脑的控制权交给了这个程序,内核开始进行各种初始化的动作,你可以将 quiet参数去掉,以便看看内核都做了哪些事情,也可以在系统启动成功以后,使用dmesg这个命令查看内核启动的时候,都打印了哪些东西,总的来说,内核做的都是一些和硬件打交道的事情,比如初始化内存,检测并初始化硬件等,在内核启动的最后,它将寻找init程序并将电脑的控制权交给这个程序。

有越来越多的新硬件需要linux的支持,如果把所有的硬件检测工作都放在内核中完成,内核会变得无比巨大,这不光是没有效率的,事实上也是不可能和不允许的,因此,如果你清楚的知道你的电脑都拥有哪些硬件,并且在未来不会添加新的硬件,你可以只将那些你需要的硬件编译到内核中去,然后直接启动你的 linux系统(事实上,早期的Gentoo系统要求每个安装者在安装的时候编译自己的内核),但是对于FC6这样的发行版来说,为了让全球大多数的PC 都可以顺利使用它,它使用模块的方式编译了尽可能多的硬件支持,并且在启动的时候在grub的配置文件中指定了initrd参数。

initrd 参数指定一个小的文件系统,这个文件系统虽然很小,但是比起内核来可以大很多,如果指定了initrd参数,内核在进行完自己的任务之后,将会运行 initrd这个小文件系统中的init程序,由这个程序完成进一步的系统初始化动作,加载更多的硬件支持以便找到真正的根文件系统。在menu.lst 文件中,这是通过initrd /boot/initrd-2.6.18-1.2798.fc6.img这一行来完成的,扩展名img通常预示着这是一个小的系统镜像文件。

使用file命令,我们可以看到/boot/initrd-2.6.18-1.2798.fc6.img是一个使用gzip压缩过的文件,解压缩以后再使用 file命令,看到这是一个cpio文件,再解压缩这个文件,我们就可以看到initrd文件系统了,这个系统中的文件不多,在根目录中包含一个init 文件,这就是内核初始化完毕以后要运行的文件,这是一个脚本文件,它使用nash解释执行,nash是专门为initrd定制的脚本解释器,它的功能小而专业,内建了很多initrd很需要的命令,我们在FC6启动的时候看到的"Red Hat nash version xxx starting "这句话,就是这个时候打印出来的。

我们来具体看看FC6的initrd做了哪些事情,首先为了让包含在initrd镜像中的那些程序顺利执行,它需要完备当前的文件系统,包括挂载proc 和sys文件系统(这些是内核支持的系统目录,需要将其挂载到用户区),创建/dev目录,并且在 /dev目录中创建系统初始化所需的那些设备,最典型的设备比如console,有了这个设备,echo命令才能把信息显示到终端上,这个阶段FC6的 initrd中的init程序创建的设备达到数十个之多。然后启动hotplug支持热插拔,这里的hotplug是nash内建支持的命令之一,然后使用内建的mkblkdevs命令根据/sys/block目录下的文件信息创建/dev目录中对应的设备文件,然后加载usb和ext3相关的模块,在这个过程中可能又有新的设备被发现,因此需要使用mkblkdevs命令再次更新设备目录,在准备好了/dev设备目录以后,init程序开始调用内建的 mkrootdev命令来创建/dev/root这个设备作为后续操作的根分区,这个命令的大致逻辑是:如果内核命令行中指定了root参数,则使用其指定的那个参数作为root设备,如果指定的为LABEL,则检查所有的块设备并且寻找卷标为指定值的设备作为root设备,如果没有指定root参数,则使用/proc/sys/kernel/real-root-dev指定的设备,这个命令除了将创建/dev目录中相应的root设备文件以外,还将更新 fstab文件,将当前找到的root文件的mount参数写入/etc/fstab文件,这样在接下来的命令中,可以直接使用mount命令加载根分区,成功加载完根分区以后,init使用nash内建的setuproot命令,将所有的sys,proc,dev等这些已经挂载在initrd文件系统中的目录重新转移至新的根分区,然后使用nash内建的switchroot命令(内核2.6以上的版本)将当前文件系统切换至新的根分区,并且执行新的根分区的init命令,这样.initrd也完成了自己的使命,剩下的事情就是真实的根分区中的init程序的工作了。

rc.sysinit过程


在上一篇文章中,我们介绍了从上电到切换到真正的root之前,FC6都做了哪些事情,接下来,我们将开始介绍从切换到真正的Root到图形界面的用户登录,FC6都做了哪些事情。在切换到真正的Root以后,FC6将电脑的控制权交给真正的init程序,通常使用的都是标准的SysVinit程序,这个程序读取配置文件/ets/inittab,然后按照其中的配置执行指定的任务。研究这个文件,就可以了解从切换到新Root,到提示用户登录,FC6都做了哪些事情。

首先,这个配置文件指定运行文件/etc/rc.d/rc.sysinit,这是个使用bash的执行的脚本文件,它首先检测一些基本系统的挂载情况,然后从/etc/sysconfig/network文件中读取网络配置,检查SELinux(安全性增强Linux)的状态,然后开始设置终端字体(使用 /sbin/setsysfont命令,这个命令将读取/etc/sysconfig/i18n配置文件,然后开始打印 "Welcome to Fedora ..."的字样,其中Fedora是从配置文件/etc/redhat-release中读取的。然后开始提示按"I"键将进入交互启动模式,在这种模式,你可以选择是否启动某个特定的服务。然后,使用/sbin/hwclock这个程序从BIOS中读取系统时间,其间使用了配置文件 /etc/sysconfig/clock,然后杀死所有的nash(我们在initrd中使用的shell)进程,启动udev(动态设备管理进程,通过监视sysfs按照规则动态创建/dev目录中的设备,已经逐渐取代了hotplug和coldplug).

然后rc.sysinit程序检查/etc/sysconfig/modules/下的所有的脚本,如果找到可以执行的脚本,就执行它,这里的脚本通常用来定义一些用户级别的模块加载。我想如果你希望额外加载一些比如遥控器之类的模块,你可以在这里增加脚本。

然后,FC6准备进入图形界面继续init过程,进入初始化需要满足的条件包括内核命令行参数中包含rhgb参数并且不包含early-login参数, BOOTUP="color",GRAPHICAL="yes"(这些变量在/etc/sysconfig/init定义)并且 /usr/bin/rhgb是可执行的程序。如果所有这些条件都满足,那么现在将执行rhgb程序. rhgb程序的作用是在启动的时候建立一个临时的仅使用loopback网络的X窗口服务器,然后在这个窗口上显示启动进度,init程序的其他部分可以通过rhgb-client程序向这个进度窗口发送消息,rhgb-client使用到的update参数是在rhgb的代码中写死的,总共有20个步骤,从最开始的"RCclock"到最后的"loginscreen"。

现在我们已经进入了FC6启动的图形界面部分。解下来首先做的事情是在运行期配置内核参数,读取的配置文件是/etc/sysctl.conf,然后是加载键盘配置,你在安装FC6的时候可能被问到这个选项,配置文件是/etc/sysconfig/keyboard,然后是使用hostname命令设置主机名,接下来如果/proc/acpi目录存在则尝试加载所有位于/lib/modules/$unamer/kernel/drivers/acpi /中的模块。然后尝试设置RAID(磁盘阵列) 加载相应模块并按需进行磁盘加密, 根据配置文件/etc/sysconfig/autofsck,/etc/sysconfig/readonly-root以及命令行参数中指定的属性决定是否对磁盘进行检查以及判断检查的结果。根据内核命令行参数决定是否进行磁盘限额检查。

重新将根分区挂载为读写模式,删除在磁盘检查过程中产生的临时文件并更新/etc/mtab文件,挂载fstab中所有非网络分区的分区并且打开磁盘限额配置。

按需要进行本机配置:如果存在/.unconfigured文件,调用/usr/bin/system-config-keyboard配置键盘,调用 /usr/bin/passwd root配置超级用户密码,调用/usr/sbin/netconfig配置网络,调用/usr/sbin/timeconfig配置时区,调用 /usr/sbin/authconfig --nostart配置网络登录,调用/usr/sbin/ntsysv配置默认的运行级别。清空包括/var/lock/,/var/run/, /tmp等在内的临时目录并创建新的临时文件,初始化串口,按照内核命令行参数的指示加载scsi相关模块,进行网络配置等(netprofile=), 创建/.autofsck文件,这个文件应该在关机的时候被删除,如果没有被删除说明没有正常关机,将会执行磁盘检测。

现在,检查用户是否按下了"I"键来决定是否运行交互模式的启动。至此,rc.sysinit完成了。


在VIA 的主页上,我找到了它们提供了用于linux的显卡驱动程序,不过预编译的驱动程序只提供Fedora Core以及Suse等超大发行版的版本,对于ubuntu这个新生事物,网站上还没有提供它对应的预编译的驱动。为了不占用太多在家里的时间,也因为我对ubuntu还没有建立起深厚的感情。 我毫不犹豫的格掉了小电脑上的ubuntu,将其换为FC5。

Fedora Core是Redhat 9的后续版本,我在多个linux发行版中间转了一圈以后,又回到了原来的地方。

我无法不喜欢Fedora Core,正是因为我在同一台机器上使用过多个linux发行版,使得我有机会对它们进行一些相对公平的比较。对于gentoo,ubuntu和FC5, 我都没有进行太多的太专业的优化,ubuntu和FC5都是二进制的发行版本,可以定制的东西不多,对于Gentoo,我也只是简单的使用了i386的体系结构,ACCEPT_KEYWORDS="~x85"而已,不过它们的表现确差别很大,简单的说,在FC5和Gentoo之间我很难比较哪个的性能更优一些,但就Gnome菜单的响应速度来说,ubuntu比较那两个显然要差很多。在FC5上通过yum进行了EPIA的升级以后,mplayer播放 rmvb和avi文件没有出现一次哪怕些许的停顿,整体表现非常流畅,并且升级后的FC5还很好的支持我的16:9的液晶电视,成功的设置出了 1280x720的标准分辨率,如果我在Gentoo上折腾这些,显然是要花很多的时间。

我们应该让那些专业人士把它们擅长的东西做的更专业,而不是让每个人都成为专业人士。

我想FedoraCore很好的满足了这个原则,因此,我认为,FedoraCore将会是最好的也是最有前途的linux发行版。在我写这些文字的同时, FC7的Test1可能已经发布了,据称FC7将整合Core仓库和Extra仓库,这意为着更为大量的软件将被FC测试优化以后以光盘的形式发布了,对于用户来说,这是好事。

虽然把专业的东西做的更专业是专业人士的事情,但是,作为用户,我们可能不必知道它是怎么作出来的,但是至少我们应该知道,它正在做什么。这可以让我们更好的使用它。在我满意了客厅的那个小电脑之后,我打算在卧室也安装了一模一样的另外一台电脑,这样我就可以躺在床上用遥控器看电影了,既然这两个小电脑的硬件是一模一样的,我打算直接把已经装好的那一台的根分区直接传输(tar)到另外一台的已经存在的根分区,本想这会是很简单的过程,但是在传输完成以后,却无法启动,提示根分区找不到之类的错误,我看到FC5的grub配置文件中并没有常见的root=/dev/hda2之类的描述,取而代之的是 root= LABEL=/,前者意为着指定第一个硬盘的第二个分区为根分区,后者应该怎么理解呢?我看不懂这是什么意思,于是直接将其更改为root=/dev /hdc4(这是我另外一台电脑上的根分区),这下,出现了"Welcome to"的启动字样,不过还是无法启动,提示找不到"LABEL=/".

当然,如果我们知道它在做什么,类似的这些简单的小问题都可以迎刃而解了,这是我要写下接下来这些文字的原因。


从启动级别到登录


根据inittab的指示,在启动完rc.sysinit之后,init程序将进入相应的运行级别,并运行这个级别的脚本。 默认的运行级别也是在inittab中指定的,一般设置为3或者5,两者的区别在于是否默认进入图形模式(启动XWindow)。

启动脚本是通过/etc/rc.d/rc这个程序运行的,它做的事情也不算很复杂,首先它将根据你是否在前面的rc.sysinit的时候摁下"I"键来决定是交互启动模式还是非交互启动模式并且进行相应的输出,然后,依次运行位于相应启动级别目录(/etc/rc.d/rc启动级别.d/)中的脚本,运行的次序是,首先按照名称顺序运行那些K打头的脚本,然后按照名称顺序运行那些S打头的脚本。如果是交互启动模式,它将在运行每个S打头的脚本之前,询问你是否运行这个脚本。

由于脚本运行的顺序是按照字母顺序,你就可以理解为什么在每个脚本之前要被加上一个两位的数字,这只是为了在排列脚本执行顺序的时候显的更直观,另外,所有的启动脚本文件都存在在/etc/rc.d/init.d目录中,位于不同启动级别下的脚本是指向 /etc/rc.d/init.d目录中相应脚本的符号链接。启动脚本符号链接中的数字是怎么来的呢,它是由你指定的,如果你要增加自己的启动脚本到相应的启动级别中去,这个数字当然应该由你指定,因为只有你才知道这个脚本应该以什么样的优先级启动。但是对于那些已经存在的启动脚本,作为FC6发行的一部分,它们的优先级是在脚本中最前面的注释行中的chkconfig这一行指定的,在这一行中,你可以看到类似# chkconfig: 35 99 95的字样,它的含义是:这个脚本应该增加到运行级别3和运行级别5中,启动的优先级是99,关闭的优先级是95,当然,这些数字是由那些FC6的开发者测试过没有问题,所以才写在这里的。一个二进制的程序/sbin/chkconfig将会读取这一行,并且在将服务增加到启动级别中去的时候自动生成文件名。

文件名中的第一个字符S和K代表了什么含义呢?它代表了你在services控制面中选择了打开这个服务还是关闭这个服务,如果你在那里打开了这个服务,则以S作为前导符,否则为K,结合我们前面介绍的启动过程,你就可以知道,在启动的时候,FC6会首先保证那些K打头的脚本是关闭的(通过以stop参数调用这个脚本),然后才会逐个启动那些S打头的脚本(通过以start参数调用这个脚本)。

对于每个启动脚本文件,如果想知道启动了时候都做了些什么,可以查看相应脚本中的start()函数,比如对于avahi-dnsconfd这个脚本,我们可以看到,它只是运行了/usr/sbin/avahi-dnsconfd -D这个命令。

除了位于系统控制面板中对各个services的简单描述以外,你还可以在 http://www.mjmwired.net/resources/mjm-services-fc5.html 找到对各个Fedora Core服务功能的描述以便决定是启动还是关闭某个服务。

在所有需要启动的服务都启动完毕以后,rc程序通过rhgb-client程序通知rhgb图形界面退出,rhgb的使命就完成了。

接下来,init程序在tty1-tty6启动mingetty程序,从现在开始,你可以通过Ctrl-Alt+F1..F6在各个不同的tty之间进行切换了。

然后,如果当前启动级别为5,init程序通过调用/etc/X11/prefdm程序,启动一个图形界面的登录屏幕,让用户登录。这个程序将会读取位于 /etc/sysconfig/desktop中的配置文件,如果没有指定任何配置文件,prefdm运行的顺序依次为gdm,kdm和xdm.

后面的启动部分就属于Gnome,Kde或者其它相应的窗口管理器了。

gdm


上次我们说到,inittab启动到最后,使用/etc/X11/prefdm这个脚本来选择一个DM(Display Manager)来启动图形界面.这个脚本根据/etc/sysconfig/desktop中的配置来选择是该运行gdm,kdm还是xdm,默认将会使用gdm。

无论是gdm,xdm还是kdm,所做的事情都是类似的,及启动一个X窗口,基于这个X窗口提供一个图形化的用户登录界面,以便在实际进入X窗口系统之前,对用户进行验证,并且提供用户选择自己希望的语言,窗口管理器等的机会。除此之外,dm程序一般还支持别的一些操作,比如提供直接关机的选项以及根据配置决定是否打开XDMCP服务的端口等。

XDMCP服务是X窗口显示管理器控制协议的缩写,它允许用户在远程电脑上运行X窗口服务,然后通过XDMCP协议使用本地的XDM登录,登录以后的后续操作将使用远程的X窗口作为显示系统。一个很简单的例子,首先使用gdmsetup程序(管理菜单的登录屏幕)打开XDMCP的支持(远程选项卡更改为与本地相同),然后打开一个终端窗口,运行Xnest :1 -query 127.0.0.1命令(Xnest并不是默认安装的命令),你将在一个新开的窗口中看到和你的登录屏幕一模一样的登录屏幕,你可以登录其它用户,进行所有和本地用户一样的操作。显然如果你是在另外一台电脑上,只需要把相应的ip地址改掉就可以了。

并不一定非要使用Xnest程序,你甚至可以在远程的Win32系统上进行基于XDMCP的远程登录,这首先需要你在你的windows系统上运行一个X 窗口系统,有很多种类似的实现,包括X-win32和cygwin在内的各种免费和收费版本都是一个不错的选择,事实上,一台强劲的服务器可以通过这种方法可以将N台落魄的486PC转变成可以运行高级科学运算的X终端。

说到远程X终端,除了上面提到的方法,你还可以使用内置于gnome之中的vino程序,这个程序可以基于本地的X窗口打开一个兼容于vnc的服务,你可以使用各种类型的vncviewer来连接这个服务并进行远程操作(参见首选项菜单中的远程桌面),这种实现方式下,远程显示的屏幕和本地屏幕是完全相同的。或者你也可以使用单独的vncserver,这种使用方式和XDMCP的使用方法类似,只是登录的用户和使用的窗口管理器都是由vncserver指定好的。

gdm的配置定义在/etc/gdm/custom.conf中,对于其预定义配置的默认值你还可以查询/usr/share/gdm/defaults.conf文件,它们都采用了类似windows下ini文件的文件格式。

在用户选择了语言和窗口管理器以后,DM根据用户的选择设置相应的locale变量,然后运行和那个窗口管理器对应的命令。通常在语言选择菜单中,我们只能看到区域的选择,比如中国大陆,中国香港这样的选项,但是中国大陆的选项可以使用GBK的编码方式,也可以使用UTF8的编码方式,选择的区域是怎样和编码方式对应的呢?所有gdm相关的配置文件保存在/etc/gdm这个目录中,在这个目录中有一个 locale.alias文件,这个文件列出了系统支持的语言以及各个语言对应的编码方式,你可以通过更改这个文件,以便在选择中国大陆的语言时,将编码由默认的UTF8更改为GBK,当然,适应UTF8的编码方式也是一个不错的选择,如果你不需要经常的和windows环境打交道,你应该保留这个默认的设置。

除了选择语言以外,gdm还允许你选择要登录的会话. 系统内建的几个会话包括安全模式终端,安全模式gnome以及上一次的成功登录等,其它的会话则是从配置文件中读取的,gdm将会在多个目录中寻找设定的会话,包括/etc/X11/sessions/,/usr/share/gdm/BuiltInSessions/, /usr/share/xsessions/等,路径可以通过daemon/SessionDesktopDir配置项进行更改,gdm在这些目录中寻找扩展名为desktop的文件,比如默认会话对应的文件是 /usr/share/gdm/BuiltInSessions/default.desktop,而gnome会话对应的文件为 /usr/share/xsessions/gnome.desktop.这些配置文件定义了在不同的语言中这个会话要显示的名称。

当用户选择了一个对话,输入了正确的用户名和密码以后,gdm执行命令的顺序依次是,首先它将执行位于 daemon/PreSessionScriptDir 配置项路径下(默认为/etc/gdm/PreSession/)的所有脚本文件,来执行启动会话前的一些任务,比如更改X窗口的默认背景之类,然后它将调用位于daemon/PostLoginScriptDir配置的目录中(默认为/etc/gdm/PostLogin)的脚本,执行一些在刚刚登录以后需要运行的命令,然后它将以前面提到的desktop文件中定义的exec参数的值作为参数,调用daemon/BaseXsession配置项指定的脚本(默认为 /etc/gdm/Xsession),比如如果你选择的是默认会话,那么执行的命令将会是/etc/gdm/Xsession default,如果你查看这个文件你将发现,在这种情况下,它将首先检查是否存在主目录的.xsession文件,如果存在就执行它,否则检查是否存在主目录下的.Xclients文件,如果存在则执行它,否则就将执行/etc/X11/xinit/Xclients文件,这个文件根据 /etc/sysconfig/desktop配置文件中的设置选择相应的命令执行,默认为执行gnome-session.

而配置在 daemon/PostSessionScriptDir配置项(默认值为/etc/gdm/PostSession/)所设定的目录中的脚本将在会话结束以后运行,这意味着无论出于什么原因,gnome程序已经完全退出了,也许是你选择了注销命令,也许是X窗口崩溃了,如果你有这方面的需要,可以将相应的脚本放在对应的目录中。

最后还有一点要说的是,gdm通常是由/etc/X11/prefdm脚本启动的,这个脚本中将会重复启动自己,因此,如果你想在运行级别5的时候完全退出X窗口,你应该将prefdm这个程序杀死,而不是简单的杀死X窗口或者是gdm。