安得倚天抽宝剑——搭建实验系统

2020年7月18日 | 由 白嘉庆整理 | 11800字 | 阅读大约需要24分钟 | 归档于 电子杂志 |

安得倚天抽宝剑——搭建实验系统

看完了前面那些文章,您可能对操作系统已经有了基本的把握;而且对Linux肯定也有了初步的了解,可是,如果你是一个程序员,你肯定还是会觉得惴惴不安:到底是这么回事吗?

求知和求实是程序员的天性,我们当然有义务满足自己的天性,所以,相关的实践必需在此展开。可是,我们到底要实践些什么呢?

不如从搭建Linux系统开始。

在搭建的时候,我们需要制作一个root文件系统:你很清楚了,所有必须的软件和数据都在上面,只要我们选定的范围合适,那么什么是操作系统最核心的组成部分在我们试验以后显然会一清二楚;我们需要制作一个内核镜像:没有内核,操作系统将会怎样?但是没有(不包含)用不到的内核功能,操作系统肯定会安然无恙。试验过后,你肯定会清楚内核到底要做些什么!内核和文件系统都有了,那不就OK了?稍等,怎么把两者绑定到一块呢?还有硬件启动的麻烦事,怎么才能让系统真正开始工作呢?

“安得倚天抽宝剑,将汝截为三段!”一段编译,一段创建,一段自己连…

好了,不开玩笑了,让我们开始吧。

搭建的基础?

首先我们必须做出一个基本决定,到底是从源代码开始搭建呢?还是从现有系统上裁减出一个系统呢?如果是从源代码开始搭建,那么我们需要对所有的软件包进行下载、编译、链接和安装,这中间步骤非常繁琐,在这里我们不希望选取这种方式。说实话,其实这方面有非常不错的Howto文档LinuxFromScrach(有翻译好的中文文档),你完全可以按照该文档进行搭建。

但是,我们的考虑是用最小的代价--消耗时间最短、相关硬件需求最少--来完成操作,而且整个过程必须保证安全可靠,即使初学者出现一些误操作,也不应该影响原来安装好的系统。所以,我们决定从现有的系统上抽出我们所需的原料。

分三步建立系统

非常遗憾,搭建Linux操作系统的人基本上都是diyer,所以不存在什么标准流程。但是大体流程都大同小异,无非是:首先编译内核——将内核源代码编译成一个可执行的镜像文件,当然,此时可能还会需要编译一些模块,这要取决于你是如何配置内核的。

有了编译好的内核还远远不够,因为你还没有能容纳系统程序和应用程序的文件系统,所以接下来需要创建一个root文件系统。文件系统要包含什么?第二步会有详细的说明。

内核与文件系统都有了,现在是万事具备,只欠东风了。只要将内核和文件系统邦定到一起,让机器启动后载入我们编译的内核,进而安装root文件系统,然后执行其中的初始化程序,我们的工作就大功告成了。说来轻松,可这个收尾动作难度系数还是蛮高的,初学者往往都在这里要栽跟头--你必须对系统启动过程了如指掌才能左右逢源。

搭建一个操作系统是我们深入了解Linux系统的一个非常好的途径。当然,由于操作系统这门学科本身涵盖面非常广,而实际的操作系统软件又庞大不堪。所以,一开始从任何层面着手实践肯定都会有盲点,有不能理解的地方,其实不用着急,随着杂志的逐渐展开,希望您和我们一起探讨,共同解开这些疑惑。

前面提到过,我们希望搭建的过程安全便捷,但不是每个朋友都能找到空硬盘或者磁盘(看看你的机器,也许连软驱都没有)来做新系统的。而且为了防止初学的朋友不小心误操作,新开分区等危险操作也应该尽力避免。所以,我们决定在系统启动时使用RAM盘来存放根文件系统。

提醒:这种实现其实在嵌入式系统中常常用到,许多嵌入系统并没有硬盘,只有容量很小的ROM disk,里面存放压缩过的数据;而且运行中根本就不需要向磁盘存储任何数据。

现在开始,我们一同做个这样的试验小系统,你要付出些代价是:时间 + 无数次击健。

编译内核

第一步要做的工作是挑选一个合适版本的内核源代码包,然后编译它。不要以为编译内核很神秘,其实它和编译普通程序差不多,内核源代码其实就是“一大堆”程序,它也是通过编译一个个的文件,然后将它们链接成一个可执行镜像文件。这个镜像就是你在/boot目录下看到的vmlinuz-*。(如果你细心的话,一定能发现在该目录下还有一个叫vmlinux的文件。其实这两个文件是一回事,前面那个不过是经过压缩的罢了。)

正如第一部分所提到过的,Linux内核具有很强的伸缩性,在编译内核时,你很容易就能发现这一点:很多功能是可选的,如果系统需要它,就可以将其编译到内核。不过加入太多的功能会使内核迅速膨胀起来。一种替代的方式是将某些功能编译成模块,放在文件系统里,等你真正需要它时,再把它载入到内存中供内核使用。

虽然是个试验系统,但我们希望它支持尽可能多的硬件和实现尽可能多的功能。所以我们配置内核编译选项时,除了支持最基本的ext2文件系统,PCI接口,自动装载模块机制以外,又把ext3,JFS,即插即用,网络,SCSI,USB等比较常用的部分加入到内核中(我们的系统不大,所以,为了方便起见把它们都直接编译到了内核。不过也留下网卡驱动以模块形式编译,好让大家印证一下模块自动载入机制)。此外,为了能在虚拟内存中建立root文件系统,内核还必须支持Ramdisk 和initrd。

内核网络设备选项里包含大量网卡驱动程序,你必须知道自己的网卡类型才能正确选择,一般情况下都将网卡驱动编译为模块,在系统启动后载入。我们的试验系统运行在vmware下,而vmware虚拟网卡驱动为pcnet32,因此只包含这个驱动模块。

编译步骤

巧妇难为无米之炊,首先得去下载一份内核源代码。就算一直是饭来张口,也别指望我能给你提供内核源码,自己到www.kernel.org网站上下载一个想要版本的内核源代码吧。如果是gz结尾的压缩源文件,就使用tar xvzf linux-2.4.18.tar.gz解开,如果是bz2结尾的,就用tar xvjf linux-2.4.18.tar.bz2解开。

内核版本编号可是有点讲究的,简单的说,偶数为稳定版本,奇数为开发版本。我们选用的是2.4.18版,一是因为它属于稳定版,再就是因为我以前下载过它,现在机器里就有,不想再换了。

进入解开后的内核源代码目录(标准系统默认情况下在目录/usr/src/linux下存放该系统的内核源代码,如果你自己解压源码包,那么如果不指明具体的目录,默认解压到当前目录的linux目录下),执行命令make menuconfig,进行内核编译选项的配置,选择需要的功能,以模块形式编译或直接编译到内核。配置信息默认情况下保存在隐含文件.config中,你也可以选择将其保存到指定的文件中,比如可以把信息记录在MiniSys.config中,在以后重新配置内核时可以方便地导入指定的配置文件(即使是老手,配置内核有时也会出现问题,导致编译失败。我们建议初学者从默认情况开始,逐步加减内核编译选项,小步更改,多次练习,逐步掌握内核配置本领)。

make menucofig提供给你一个终端图形界面的配置菜单,其中列出了内核所能提供的全部功能,如果你在选项前打上*标号,那么该选项被编译到内核中,如果打上M标号则被编译为模块,对于你不清楚的选项的意义,可以使用?查看它的帮助文档。除了用make meunconfig外,如果你有复古情结的话,可以试试使用make config,它完成同样的功能,不过你得有足够得耐心去忍受洪水一般涌过的命令行信息,同时不厌其烦对功能项进行取舍(选)。如果你在X环境下(桌面环境),不妨使用make xconfig配置,相比前两种方法,它更容易看清楚一些,眼神不好的网友推荐使用。

保存内核配置后,就执行 make dep ,该操作检查代码之间的相互依赖关系,比如引用的头文件是不是都被包含了。

make* clean*,该操作用来** 清除以前编译内核时遗留下来的生成物。采取这个步骤是习惯问题,现在新版本的内核已经不是必需的了。

make* bzImage*make zImage** ***,***编译内核——bzimage和zImage两种内核之间最大的差别是对于内核体积大小的限制。zImage内核需要放在实模式1MB的内存之内,所以其体积受到了限制。而bzImage的内核没有1MB内存限制,因此通常用它。

编译过程漫长而且乏味(至少在我的vmware虚拟机中非常慢),尤其讨厌的是,要是你选择功能不当,那编译时就会出错,终止编译过程。这时你不得不从头开始,修改选项,重新编译,好不麻烦。所以每次配置都不要追求勇猛精进,最好参考我们的建议,逐步添加功能,保存配置信息,以防不测。

注意,内核编译完了,还要编译模块。即使您在配置内核时没有使用任何模块,最好也不要跳过此步骤,在编译完内核后立刻编译并安装模块是个好习惯。当然,如果您确实没有配置什么模块的话,这部分工作很快就会结束。

make* modules**,***编译内核模块,凡是在配置内核时标记为M的部分都将被编译为模块。

make* modules_install* ,将编译好的内核模块安装到正确的位置上,一般会放在*/lib/modules/<****内核版本号* ***>/******这个目录下。当然,***如果你想改变模块的安装目录,可以修改Makefile文件,通过设置INSTALL_MOD_PATH来改变模块的安装目录。

等蝗虫一样的字符风暴在屏幕上停歇后(当然不是满屏的出错信息!),你真的拥有新内核了。它藏在内核源码目录下的子目录arch/i386/boot下,叫作bzImage或zImage(根据你的make操作而定 ),模块也被安装到了相应的位置上。

创建root文件系统

组建root文件系统更简单:第一步,格式化容纳root文件系统的宿主设备(按照所需的文件系统格式来格式化设备);第二步,拷贝所需文件到root文件系统中。

As easy as ABC,轻松吧?我们先来一同拷贝文件,等拷完了再谈格式化宿主设备的问题。别忘了我们可是完全空手套白狼呀!因此宿主设备的制作和格式化过程稍显麻烦,我们后面单独介绍它。

交叉编译:更标准的方法前面已经提到了,是下载各种工具包源代码,在本地交叉编译后进行安装。但是这样做所需的操作相当复杂,还非常容易出错误。我们采用拷贝现有系统文件的方法来构造文件系统,不但方便,而且效果一样好。但前提是要创建的系统体系结构——处理器——和我们原来的系统一致,如果你想在0x86系统上创建在ARM机上用的root文件系统,那么最好是去下载源代码包重新交叉编译来生成可执行文件吧。

创建root文件系统基本要求

Linux文件系统的详细结构本期杂志中另有介绍,这里就不多讲了。我们这里着手搭建的root文件系统,不必包含太多内容,仅仅提供最基本的目录结构、系统命令就可以了。并且配置文件也应该尽量修改得简洁明了。

root文件系统必须包含的内容如下:

n 包含/dev 、/proc、/bin 、/sbin 、/etc 、/lib 、/usr 、/root 等目录。

n 包含一组基本命令,如ls这样的文件管理命令,insmod这样的系统管理命令。

n 支持上述命令的运行库函数和系统登陆认证的相关库函数。

n 包括编译内核生成的模块。

n 必须的设备文件。

n 一些必要的配置文件和服务管理脚本。

我们要做的只不过是按部就班地生成和拷贝以上内容,唯一的要求就是心细。

创建root文件系统的内容

我们先来建立一个将包含根文件系统内容的新目录“rootfs”(mkdir /rootfs),然后开始在其中生成(拷贝)根文件系统需要地所有目录和文件。

第一步 当然是在rootfs目录下建立文件系统根目录下必需的子目录:

#cd rootfs

#mkdir dev, proc, bin, etc, lib, usr,sbin, root, usr/bin, usr/sbin, usr/lib

第二步 向rootfs中拷贝你需要的命令,比如ls 命令。

你得先确定它在系统中的位置(使用命令whereis ls**,发现它在/bin/ls目录下),然后将该命令拷贝到rootfs下相同的目录结构下(cp /bin/ls /rootfs/bin/ls)。仅仅拷贝命令文件并不够,还必须拷贝该命令所用到的动态链接库。如何查找命令用到了那些动态链接库呢?很简单,利用命令ldd** /bin/ls 可以完成这中工作:

比如在我的系统上,该操作输出为:

libtermcap.so.2 => /lib/libtermcap.so.2 (0x4001f000)

libacl.so.1 => /lib/libacl.so.1 (0x40023000)

libc.so.6 => /lib/libc.so.6 (0x40029000)

libattr.so.1 => /lib/libattr.so.1 (0x40149000)

/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

名字中有.so的那些文件就是用到的动态链接库。找到这些动态链接库后,就将它们拷贝到rootfs下与原来位置对应的地方,一般应该在rootfs目录下的lib或lib/i386中。(用户所用到的命令多集中在/bin和/sbin下;此外,一些脚本会用到一些出现在/usr/bin和/usr/sbin下的命令和工具。如果你要使用这些脚本,不用说,这些命令工具和它们要用的库一个都不能少)。

但是先别忙,这些文件名可并非我们实际想要的,它们只是实际库文件的一个符号链接而已,系统之所以要使用符号链接,是为了便于库文件升级换代过程中不直接影响到使用它的应用程序(应用程序中用到的库路径指向的是库的连接文件,连接文件名称轻易不会改变)。因此我们如果单拷贝符号链接是没有意义,必须将符号链接指向的实际库文件一同拷贝到workdir/lib录下面去(使用命令ls ??Cl <库连接名称> 就可查看实际动态共享库文件)。

/lib目录中还有一个重要组成部分,modules目录。它里面包含了内核编译产生的模块,不同版本内核对应的模块存放在以版本号命名的文件中。你可别忘了拷贝我们新内核模块到我们的rootfs/lib/modules下。

在很久很久以前,拥有/lib下的这些库就足够用了,但现在的Linux系统相比过去多了许多新要求,尤其是系统从网络安全性考虑,增加了许多验证手段,因此你还必须拥有这些与安全验证相关的附加库。这些库虽然不会在命令中直接使用,但却间接地要被系统的安全机制调用。多数情况下,安全机制使用的具体库会在配置文件中定义,安全框架通过查看配置文件,选择调用具体是何种验证库(有些配置文件后面会提及)。安全框架方面的话题,我们就不多介绍了,有兴趣的话可以查查 pam 和 nss等的用法, 它们都是系统登陆时必须使用的与安全相关的库。在这里,我们就不管它三七二十一,将所有在/lib/security/下和pam相关的库,以及在/lib下与nss相关的所有libnss*库,都拷贝到rootfs/lib下的/security和/lib中(如果没有这些验证库,你就无法登陆,系统不断重复地给你登陆提示,但却不给你输入密码的机会)。方法很笨,但却比较省事。

在Linux系统中,那些会被应用程序频繁使用的库函数,多数都不会以静态方式编译连接到应用程序中,而通常会采取动态链接的方式,进行集中存储管理。这样如果多个程序都用到某个共享库,那么该库文件只会被调入内存一次,驻留在内存供所有应用程序调用。因此利用共享库能显著地节约内存空间,缩减执行文件的体积。当然,天下没有免费的午餐,虽然共享库相比静态库来说灵活得多,但却需要进行路径搜索,而且调入时也更耗时。

链接文件是Unix风格操作系统提供的一个特色之一,其中具体又可分为软链接和硬链接。软链接又称为符号链接,这种文件唯一的内容就是包含实际文件系统的路径。硬链接则是和被链接文件共享索引节点的(索引节点概念如果还不清楚,我们会在介绍文件系统的那一期杂志中详细介绍的)。因此,即使实际文件已经被删除或转移,符号链接仍然存在,当然,它不再有效了。相反,删除硬链对应的文件则会使索引节点计数减少,而不会破坏硬链接文件。

第三步 该建立设备文件了,这点很重要,但相当容易。Linux继承了Unix的一个优良特性,将设备抽象成特殊文件来使用和管理,想使用系统的外设,比如软硬盘,时钟,系统终端甚至内存,都可以通过设备文件来进行访问。因此我们要建立系统可能会用到的所有设备对应的设备文件。至于你具体需要哪些设备文件不能一概而论,你可以打开/dev/目录看看,当然,里面的文件肯定会多得让你眼花缭乱。

不过也别怕,因为多数都没什么用处。对于我们要建立的实验系统来说,用到的设备文件就屈指可数:

console ——控制台设备;

tty* ——由控制台管理的虚拟终端(我们用ctrl-[1-7]切换的就是这个设备);

sda1 ——SCSI接口设备 (因为我的Linux是运行在vware虚拟机上,而vmware虚拟机使用的存储设备是虚拟的SCSI硬盘,所以需要这个文件。它的用法和使用标准IDE硬盘没什么两样);

ram ——内存虚拟盘设备(以后我们的系统就运行在内存虚拟盘中);

null ——空设备(是一个非常有用的字符设备文件,送入这个设备的所有东西都被忽略。如果将任何程序的输出结果重定向到/dev/null,则看不到任何输出信息,因此脚本中常用它来消除本该显示在屏幕上的信息);

zero——零设备 (读取这个设备,只会得到空的内容,所以有时为了获得高压缩率,需要对某空间用全零添充时往往就会用到它);

initrd ——这是一个特殊的字符设备,它被用来从用户空间向系统内核发送切换运行级别的信息,属于一个虚拟字符设备(比如改变运行级别的init 1-6命令,都是通过该设备传递给内核的)。关于虚拟字符设备作为用户向内核发命令的利器作用你可看看《Linux内核空间与用户空间信息交互方法

必须的设备文件,大概就是这么多了,明确了你需要所有设备文件,就可以利用mknod命令挨个建立这些需要的设备文件。如果采取这种方式,那么注意mknod是需要参数的。你可以通过命令ls **??****Cla /dev/**设备名来查看设备是属于块设备还是字符设备,查看设备的主从设备号。如果你觉得这样太烦,就用拷贝命令直接从标准系统的/dev/目录下拷贝这些文件吧,不过要配合参数-R使用,否则,你拷过来的就是设备的所有文件,即设备中的整个内容,而不仅仅是一个设备文件了。拷贝设备内容就如同把自己往自己衣服口袋里塞,你是永远塞不进去的。

Linux系统将设备分为块设备和字符设备,块设备可以随机访问(用符号B描述),字符设备只能按顺序访问(用符号C描述)。另外,一个驱动程序可能会控制多个设备,所以有主设备号和从设备号之分。主设备号对应驱动程序,从设备号用来区分具体设备。

第四步 该建立系统运行需要的配置文件和脚本了。我们还是从简出发,拷贝标准系统现有的文件,然后针对需要进行修改。下面是具体需要的配置文件和脚本文件:

我们的试验系统必须满足多任务、多用户的需求,因此要有用户登陆和用户分组的能力,所以我们需要首先要拷贝/etc下的passwd和group这两个文件,如果系统通过shadow保护用户密码,那么shadow文件也要随同拷过来。

系统启动后最先执行的就是init程序,它需要的配置文件可不算少。首当其冲的便是inittab文件,它规定了系统运行的许多基本属性。接下来init程序会去执行inittab中引用的rc.sysinit脚本,进行系统引导后期的初始化工作,其中又会使用到fstab配置文件,它包含了系统启动后需要安装的文件系统以及安装到的目录位置;对于我们的试验系统来说只有两项:一个是将/dev/ram作为root文件系统安装到/下,另一个是将proc文件系统安装到/proc目录下。

init执行完rc.sysinit后,依照inittab中定义的运行级别进入对应的/etc/rc?.d/(1就是/etc/rc1.d/,当然,最常见的是3级,/etc/rc3.d/)目录下执行其中S开头的系统服务脚本。这里面的细节就不多说了,可以使用man init获得相关资料。现在要做的就是把/etc/下initab,rc.sysinit和rc.d目录的所有东西拷贝到新root文件系统的对应位置上。

我们的实验系统的运行级别为3,只需要启动网络服务,因此可以把除了S?Network和S?Local外的S脚本都统统删除。(当然你也可以改变系统默认的启动流程,让它执行你自己定义的初始化脚本,要做的只是去inittab中修改“sysinit:XXX”中的脚本名称)。

执行了上述初始化,启动了相关服务后,系统会执行rc.local文件,这里可以放一些你自己希望在开机时执行的命令,我们这里放一句“ ok you are welcome !!!”作为你进入系统前的问候(由于我们系统初始化工作没有什么太多操作,所以启动过程很快,系统中的虚拟终端设备(tty?)往往还来不及初始化完全,所以可能出现系统起来后提示(”Id xxx respawning too fast:disabled for 5 minutes”)一类的话 ,为了避免这个错误,我们在local中还让系统睡眠了20秒)。

另外,登陆程序login往往要使用pam验证模块认证用户合法性,所以pam的配置文件也要拷贝到新系统。很多系统还会用到NSS(名称服务开关,在前面已经提到它要用到的库文件。这个服务来帮助客户机器或应用程序获得网络信息,可从本地或从网络某处取得——从DNS或NIS等。诸如getXbyY()等函数都往往会用到这种服务,用户登陆时login很可能就要使用,这取决于你的libc的版本),所以nss的配置文件/etc/nsswitch.conf也需要拷贝,至于该脚本的内容等细节问题,可通过man nsswitch.conf来了解。

你还要拷贝terminfo/termcap(新旧版本有别)文件,设置TERM终端环境变量要用到该文件。

最后别忘记拷贝模块配置文件modules.conf,它包含了模块相关的信息,我们的实验系统中的modules.conf文件中仅仅给pcnet32.o 起了个别名而已。说得我口渴啦,不说了,有什么疑问自己去找资料吧。

差不多完了,如果你还没被累死,就去把/root/目录下的那些.开头的用于bash配置的隐藏文件也拷到新系统的root目录下,这些都是bash的环境参数(如果没有,关系也不大,就是不大方便而已)。

结束动作:ldconfig ??Cr rootfs(试验文件系统目录) 建立库文件路径缓存 ,从此我们新root文件系统上的命令再使用动态链接库时就不必指定库的目录了,因为它们的路径都被缓存了。(ldconfig 要用到动态库配置文件ld.so.conf,它其中指定了需要用的库路径。比如你的程序用到kerberos库,那么就需要在ld.so.conf里面包含/usr/lkerberos/lib路径)

内核和root文件系统绑定

别混淆了,刚刚我们所作所为只不过是集合文件系统应该包含的文件内容。实际的根文件系统我们还压根没开始做呢。

制作文件系统就需要先在容纳文件系统的宿主盘上以指定的文件系统规格进行格式化操作。如果你手头没有实际设备,Linux提供给你另外两种变通方法 : ramdisk和loop设备(回环设备)来“冒充”物理文件系统。利用loop设备可以将一个文件虚拟成文件系统进行安装使用,而ramdisk则是将内存模拟一个块设备来存放文件系统。

使用ramdisk或loop设备相比直接使用真正的磁盘分区操作要方便一些,也安全一些,不会损坏磁盘的物理结构。因此在做试验的时候,往往会使用上面两种虚拟技术创建文件系统,然后再将得到的文件系统整个转移到真正的物理宿主设备上去。

我们的试验选择的是ramdisk。在它上面制作文件系统(进行格式化),然后按照我们前面那一小节所讲的顺序,创建root文件系统到其上去。然后将整个文件系统(要比其中文件内容的大小大一点,因为其中还包含文件系统本身格式的一些信息)统统转移到一个文件中去(利用dd命令,因为 dd 命令允许二进制方式的读写,所以特别适合在原始物理设备上进行输入/输出,适合制作整个文件系统的镜像),该文件被称为文件系统镜像。虽然Linux对文件后缀没有要求,但这里我们还是习惯以img命名它。

具体做法大该如此:

dd if=/dev/zero of=/dev/ram bs=1k count=20000

mke2fs **??**Cm0 /dev/ram 20000

mount /dev/ram /mnt/ram

cp **??**Cav /rootfs/* /mnt/ram

运行df看看**/dev/ram的大小(注意是1k-blocks****一栏中的数值)假定为***ramsize*

umount /dev/ram

dd if=/dev/ram of=ramlinux.img bs=1k count=*ramsize*

gzip **??**C9v ramlinux.img

第一步是给/dev/ram设备(ramdisk)清出20M的全零空间,然后格式化/dev/ram设备,也就是将文件系统的格式信息写入/dev/ram设备中。

接下来,安装/dev/ram设备到/mnt目录下(安装后才能向里面拷贝东西),再把你创建的文件系统内容全部拷进来。完成了这步,你才可以说真正有了一个文件系统(文件系统格式信息+文件系统内容)

接下来的工作是把这个文件系统如何保留下来。我们没有物理设备,因此把要它保留在一个镜像文件中。先加载/dev/ram,再把设备内容(包含文件系统格式和内容)统统转移到名为ramlinux.img的镜像文件中(你可别指望用cp命令,它无法拷贝文件系统自身信息)。

最后,为了节约系统空间,压缩镜像文件(压缩后名字为ramlinux.img.gz),开始使用/dev/zero清零/ram设备的目的就是为了提高压缩率,因为压缩算法利用统计规律替换字符,所以统一为零会大大提高镜像文件的压缩率。

一般标准系统中ramdisk默认大小为4098字节,你建立的ramdisk大小不能超过这个限制。但我们搭建的系统已经超过了4096字节,所以必须扩大ramdisk的大小限制。最简单的方法是在lilo启动时给ramdisk指定大小,我们实现的系统中大概用到20M大的空间,所以在Lilo.conf中应该加入“append = “ramdisk_size=25000”这一行,系统启动时就会自动更改ramdisk默认大小为25M了。.

系统引导

PC打开电源后,先执行ROM中BIOS中的代码,该程序负责将启动设备(软盘、硬盘、光盘)的第一个扇区(0扇区)的第一个磁道的数据载入内存。接着BIOS执行该扇区中的代码(将内核从启动设备中逐步导入到内存)。所以扇区中要么直接存放操作系统内核,要么存放启动装载程序,比如Lilo等,由启动装载程序负责找到内核,装载内核到系统,然后才能执行内核。

内核被载入内存后的动作上面已经初步介绍了,我们这里要强调的是,内核初始化以后紧接着就要安装根文件系统,那么根文件系统的位置如何确定呢?(这个位置被叫做“ramdisk size”)

我们必须指定root文件系统的宿主设备,可以利用命令rdev filename devicename 设置或在编译内核前在内核源代码目录下的 makefile中修改相关参数。由此可知该信息是被记录在内核中的。

除了root文件的宿主设备需要告诉内核外,还需要指出root文件系统在宿主设备上的具体位置,这还需要利用命令rdev 来实现。具体位置信息也被记录在内核之中。(rdev命令很丰富,回忆我们前面谈到的改变ram盘大小的任务都可以通过rdev来修改)

可能很多朋友奇怪自己根本没用过这个命令,这么多年还不照样把系统升级了无数次。的确我们不大使用该命令,因为我们有更酷的工具lilo(当然grub好像现在更流行了),在lilo.conf中的配置如root=* 这些选项其实就是告诉lilo将上述信息写入内核中。

确定了根文件系统位置,将其安装到根目录下,然后找到其中init程序,开始执行系统初始化工作。

initrd镜像

大家应该对 lilo.conf下的initrd=initrd.img.*选项有所印象吧。你知道initrd.img是干嘛用的吗?

这个文件实际上就是个文件系统镜像,有兴趣的话你可以将它安装起来,看看里面包含的内容:它确确实实是个微缩的文件系统。该文件使用gzip压缩的,所以先要解压才能安装:

mv initrd.img initrd.img.gz

gunzip initrd.img.gz;

mount **??**Co loop initrd.img /mnt

这个文件里目录结构和我们标准的文件系统几乎完全一样的,但是由于initrd.img是系统启动后在Ram盘里运行的,所以它一般是系统启动时必需的命令和库的最小集合。(关于initrd,我们本期杂志中已经有相关介绍了,希望大家参考。)

标准系统中lilo.conf里看到的inirtd.img文件没有gz后缀,但它的的确确是个gzip压缩过的文件系统镜像,要想看它的内容,你必须先解压它,再以loop方式安装它才可以。

不过内核自己就具备解压能力,因此压缩的文件系统内核可以直接使用。当然解压后的文件系统内核识别更没问题。

因为我们已经将SCSI和EXT3等用到的模块都直接编译进了内核,所以基本上不必使用initrd的常规功能:进行启动时的模块载入。我们的initrd并不做寻常工作,它另有妙用:我们制作的根文件系统镜像是放在原有的标准系统中的root文件系统下的,为了使它摆脱实际物理设备在ram盘上运行,就只能靠initrd机制了。系统启动期间将物理盘上所容纳的试验系统的root文件系统镜像载入ram盘中,然后就可以进入试验系统开始运行了。

这个工作我们仍然要利用linuxrc脚本来完成,具体流程是:

mount原来的根文件系统(在vmware中标准系统的根文件系统在sda1上),将试验文件系统镜像解压传送到/dev/ram中(zcat 压缩镜像文件 >/dev/ram),最后umount 原来标准系统的root文件系统。从此系统进入我们的实验文件系统开始运行。

initrd.img.gz也属于压缩的文件系统镜像,它的制作方法和制作root文件系统大同小异。先拷贝需要的文件,再编辑脚本(linuxrc),然后制作文件系统镜像,并压缩它。详细过程不再