查看: 1567|回复: 0
打印 上一主题 下一主题

安得倚天抽宝剑——搭建自己的Linux实验系统(二)

[复制链接]
跳转到指定楼层
沙发
发表于 2015-3-22 20:11:07 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
创建root文件系统基本要求

我们这里着手搭建的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目录下的liblib/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下的passwdgroup这两个文件,如果系统通过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/initabrc.sysinitrc.d目录的所有东西拷贝到新root文件系统的对应位置上。

我们的实验系统的运行级别为3,只需要启动网络服务,因此可以把除了S?NetworkS?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(名称服务开关,在前面已经提到它要用到的库文件。这个服务来帮助客户机器或应用程序获得网络信息,可从本地或从网络某处取得——DNSNIS等。诸如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提供给你另外两种变通方法 : ramdiskloop设备(回环设备)来“冒充”物理文件系统。利用loop设备可以将一个文件虚拟成文件系统进行安装使用,而ramdisk则是将内存模拟一个块设备来存放文件系统。

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

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

   具体做法大该如此:

          dd if=/dev/zero f=/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 f=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打开电源后,先执行ROMBIOS中的代码,该程序负责将启动设备(软盘、硬盘、光盘)的第一个扇区(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方式安装它才可以。

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

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

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

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

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

所有工作到此可以说已经基本完成,然后就是修改系统启动配置文件,加入试验系统的启动选项了。你要在lilo.conf中加入如下内容

boot =实验系统内核

label = ramlinux

initrd = 刚做的initrd压缩镜像

root = /dev/ram

append = “ramdisk_size = 20000”

最后,执行lilo ??Cr /rootfs 。重新启动机器,成败立时可见。

下载搭建脚本和实验系统

虽然搭建系统技术简单,但是过程很繁琐,搞不好会丢三落四,错误百出。为了节约大家的体力,我们编写了几个小脚本帮助搭建系统。利用这几个脚本大家可以轻松地自动建立实验系统。

我们的制作脚本可分为下面几个部分:

mkrootfs.sh  收集制作root文件系统所需的所有材料到指定目录。

mkinitrdfs.sh 收集制作initrd镜像所需的所有材料到指定目录。

setup.sh   制作root文件系统镜像和initrd镜像,改写lilo配置文件添加ramlinux启动选项。

连同脚本一同提供给你的还有mybootmyetc myroot目录。boot里含有编译好的内核(注意内核是与系统硬件相关的,如果你的系统与我们提供的有所不同,那你还是自己在本机上编译试验系统内核吧!不过可以使用我们提供的内核配置文件MinSys.config来选择内核功能,编译完成把内核拷贝到myboot下就可以了——或修改mkimage.sh脚本,在最后面的地方修改lilo.conf部分,将”boot=×××”中的XXX用你自己编译的内核代替)、模块和内核配置文件MiniSys.configetc下包含了供试验系统使用的、已经修改好的配置文件和服务脚本。Boot下是两个bash的配置文件--全部脚本和必要配置文件打包为work.tar.gz

此外,我们也将按上述方法裁减出来的root文件系统(rootfs.tar.gz)root文件系统镜像ramlinux.img.gz放在网上以供下载,同时也把initrd的内容(initrdfs.tar.gz)和镜像(initrd.img.gz)放在网上。

如果你要添加或删除文件系统中的某些文件,应该展开roofs.tar.gz,然后在rootfs里面修改,不要把文件系统镜像文件(img文件)loop方式安装后进行修改,因为我们曾经用zero设备填充过文件系统,所以如果新添加或删除某些内容,可能会破坏里面的一些数据对齐,系统反映给你的就可能会有类似于“bus error”等一类莫名奇妙的错误。

如果你按要求解开了rootfs.tar.gzinitrdfs.tar.gz,那么执行setup.sh即可生成对应的镜像文件ramlinux.img.gzinitrd.img.gz,并会在lilo中添加好对应于试验系统的选项。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 加入中科因仑

本版积分规则

快速回复 返回顶部 返回列表