5 根文件系统
创建根文件系统比编译内核要复杂的多,也更难理解。这里的关键是掌握init
rd(初始化RAM盘)的使用方法。
5.1 根文件系统
这里我们将要创建的根文件系统与通常Linux主机的根文件系统类似,只是它应
该仅仅包括系统运行所必须的应用程序、库和相关文件的最小集合。根文件系统的
尺寸大小是一个重要的指标。
5.2 文件系统的内容
5.2.1 应用程序(applications)
/bin,/sbin,/usr/bin,/usr/sbin
应用程序大致可以分为3部分,第一是操作系统正常运行所需的基本工具软件,
比如bash,cp,rm等;第二是提供某项服务的服务器软件,比如httpd,telnetd,
proftpd等;第三是我们所开发的应用程序。其实后面讲的配置文件等也可以粗略按
照这个原则来分类。
到底需要复制哪些软件,弹性是比较大的,很多软件都是可要可不要,可以实
际情况灵活选择。这个列表可能很长,并且变化也比较大,因此不在这里列出。
为了进一步减小所创建的根文件系统的尺寸,可以考虑使用下列工具包软件来
替代某些标准的工具:
O BusyBox(http://www.busybox.net)
O TinyLogin(http://tinylogin.busybox.net)
O Embutils(http://www.fefe.de/embutils/)
其详细使用方法请参考相关资料,此处不再赘述。LIPS的实现目前没有使用这
些软件包。
5.2.2 设备文件(device files)
/dev
设备文件也可以称作设备节点(device node)。设备文件非常重要,缺少某些
有些设备文件可能导致系统不能正常运行甚至不能引导。有些设备文件是必须的,
而更多的是根据具体目标系统的硬件配置来进行取舍。
比如硬盘的设备文件,在完整的系统中一般有hda,hdb,……,hdt,即最多支
持20个IDE硬盘,每个硬盘有hdX1,hdX2,……,hdX32,(其中X表示a-t),即支
持32个分区,另外还有表示SCSI硬盘的节点。根据实际情况,如果只需要支持少量
的硬盘、少量的分区,这些节点可以被大大简化。如果目标系统中没有的设备,其
对应的设备文件也可以省掉。
设备文件
描述
/dev/console
系统控制台设备,非常重要。
/dev/fd0
第一个软驱
/dev/hda
/dev/hda[1-8]
IDE硬盘及分区
/dev/initctl
实际上是一个FIFO设备,跟init有关(切换运行级别时用于新init与原init通信)
/dev/initrd
Initial RAM disk
/dev/input
(目录)Input core(包括游戏杆、鼠标等)
/dev/kmem
内核虚拟内存
/dev/loop[0-7]
Loopback设备
/dev/mem
访问物理内存
/dev/null
NULL设备
/dev/psaux
PS/2鼠标
/dev/ptmx
UNIX98 PTY master
/dev/pts
(目录)UNIX98 PTY slaves
/dev/ptyp[0-7]
伪终端主设备(远程登录使用)
/dev/ram[0-7]
/dev/ramdisk
/dev/ram
RAM Disk设备。至少/dev/ram0是应用initrd机制所必须的。
/dev/ramdisk链接到/dev/ram0,是为了兼容老版本内核而保留的。
/dev/ram链接到/dev/ram1。
/dev/random
随机数发生器
/dev/sda
/dev/sda[1-8]
SCSI磁盘及分区设备
/dev/shm
共享内存设备
/dev/systty
指向系统tty设备的符号链接,一般是tty0。
/dev/tty
当前TTY设备
/dev/tty[0-7]
虚控制台(Virtual console)
/dev/ttyp[0-7]
伪终端从设备
/dev/ttyS0
/dev/ttyS1
串口(COM1和COM2)
/dev/urandom
速度更快、安全性较差的随机数发生器
/dev/zero
零设备,只能读0出来
设备节点的主设备号(Major)、次设备号(Minor)的文档是内核源代码中的
/Documentation/device.txt,如果有疑问可以查看这个文件[8]。
5.2.3 脚本和配置文件(scripts and configuration files)
/etc
/etc/rc.d目录下的启动脚本是系统的重要部分。必须对启动脚本做相应的修改
以简化系统的启动过程。
系统和各种应用程序用到的几乎所有的配置文件都位于/etc目录,是裁减Linu
x最麻烦的部分,最容易出问题。配置文件的选择需要综合很多方面的信息,需要对
系统有比较全面、深入的了解,并结合经验才能做出正确的判断。
配置文件
描述
/etc/default
(目录)某个命令(比如useradd)的缺省设置(man useradd(8))
/etc/ld.so.cache
由ldconfig命令根据/etc/ld.so.conf文件产生
/etc/ld.so.conf
库文件路径配置文件,ldconfig命令根据该配置文件生成/etc/ld.so.cache
/etc/localtime
本地时间、时区设置
/etc/login.defs
全局缺省设置
/etc/fstab
文件系统列表(man fstab(5))
/etc/group
组文件(man group(5))
/etc/hosts
列出主机名和IP地址(man hosts(5))
/etc/init.d
符号链接到/etc/rc.d/init.d
/etc/initlog.conf
Initlog日志配置文件(man initlog(8))
/etc/inittab
Init配置文件(man inittab(5))
/etc/ioctl.save
该文件包含了用于单用户模式的串口和终端参数,因为这些参数是由getty设置的,
而在单用户模式时没有运行getty,所以用该文件保存参数。单用户模式对系统安全
是个威胁,我们应该禁止使用单用户模式,因此这个文件实际上并没有必要复制过
来。
/etc/issue
登录信息和标识文件(man issue(5))
/etc/modules.conf
模块的配置文件(man modules.conf(5))
/etc/mtab
已经挂载的文件系统列表(man mount(8))
/etc/nsswitch.conf
Name Service Switch的配置文件(配置名称服务数据源和查询的顺序)(man nss
witch.conf(5))
/etc/pam.d
放置PAM配置文件的目录(有关PAM请参考5.5节)
/etc/passwd
用户口令文件(man passwd(5))
/etc/profile
系统环境变量和登录配置文件
/etc/rc.d
放置启动脚本的目录
/etc/services
列出可用的网络服务及其端口(man services(5))
/etc/termcap
终端(terminal)功能数据库(man termcap(5))
还有那些跟特定应用程序相关的配置文件,比如apache服务器需要的/etc/htt
pd/conf/httpd.conf 等,此处不再一一列出。
5.2.4 库文件(libraries)
/lib,/usr/lib,/usr/share
库文件也是系统运行所必需的。到底需要哪些库文件,是根据所复制的可执行
程序用 ldd 工具来确定的。比如,要知道/bin/bash需要哪些库文件,使用如下命
令:
[root@lips xmdong]# ldd /bin/bash
libtermcap.so.2 => /lib/libtermcap.so.2 (0x40020000)
libdl.so.2 => /lib/libdl.so.2 (0x40024000)
libc.so.6 => /lib/tls/libc.so.6 (0x42000000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
对复制到新的根文件系统的每个可执行程序,都要使用这种方法来确定其所需
要的库,然后把这些库文件也复制过来。
需要注意的是,有些库文件使用上述方法是找不出来的,但是却是系统必须的
。比如:
库文件
描述
/lib/libnss_files*
这个库是跟/etc/nsswitch.conf有关的,必不可少,否则系统不能正常使用。(ma
n nsswitch.conf(5))
/lib/security/pam_unix.so
这个库跟PAM有关,是安全、认证方面的,必不可少,否则系统无法登录。
5.2.5 必要的目录
/home,/mnt,/proc,/root,
/var,/var/log,/var/run,/var/lock/subsys
5.3 初始化RAM盘(initrd)
(参考资料[5])
初始化RAM盘(initrd)提供了引导器加载RAM盘的能力。这个RAM盘可以被挂载
(mount)成根文件系统,并执行其上的程序。然后,可以从另一个设备挂载一个新
的根文件系统,而原来的根文件系统(也就是initrd)会被移到一个目录里并卸载
。
initrd技术主要设计用来让系统启动过程可以分两个阶段进行,首先让内核以
一组最小的、被编译进内核里的驱动程序来启动,然后从initrd中加载其他的模块
。
5.3.1 操作步骤
使用initrd时,系统典型的引导步骤如下:
1) 引导器加载内核和初始化RAM盘;
2) 内核把initrd转到一个一般的RAM盘中,并且释放原来被initrd占用的RAM;
3) initrd以可读写模式被挂载到根目录;
4) 执行/linuxrc(linuxrc可以是任何可执行文件,包括shell脚本在内;它具有u
id 0即超级用户的权限,基本上可以做init程序中能够做的任何事情);
5) 在linuxrc中挂载真正的根文件系统;
6) linuxrc使用pivot_root系统调用,把真正使用的根文件系统挂载到根目录;
7) 在根文件系统上执行通常的引导过程(比如执行/sbin/init);
8) initrd文件系统被删除。
注意,改变根目录并不包括卸载旧的根文件系统,因此有可能在转变过程中仍
有进程在initrd上运行。另外,挂载在initrd目录下的根文件系统仍然是可用的。
5.3.2 引导选项
initrd技术增加了下列引导选项:
initrd=<path>
装入指定的文件作为初始化RAM盘。当使用 LILO 当引导器时,你可以用/etc/
lilo.conf 文件中 INITRD 这个配置参数,来指定初始化RAM盘文件。
noinitrd
initrd的数据仍会保留,但不会装入到一个RAM盘里,真正使用的根文件系统将
会被挂载。initrd的数据能夠从 /dev/initrd 这个设备中被读出来。注意,initr
d的数据可以是任何结构,并不一定必须是一个文件系统的映像,这个选项主要用来
进行debug。
注意:/dev/initrd 是一个只读并且只能使用一次的设备,最后一个程序一旦
关闭它, 所有数据将被释放,而且设备也不能再被打开。
root=/dev/ram0
initrd 先被挂载成根目录,接着进行正常的启动过程(这时RAM盘仍被挂载成
根)。
5.3.3 安装
首先:我们要在正常的根文件系统中创建一个容纳initrd文件系统的目录,例
如:
# mkdir /initrd
对目录名称并没有特别的限制,在pivot_root(2)的man手册页中有更详细的说
明。
如果根文件系统是在引导程序时被建立的(例如,你在制作安装软盘),在建
立根文件系统的同时应该创建 /initrd 目录。
在某些情况下initrd虽然未被挂载,只要有下列设备存在,它的內容仍是可被
访问的。(注意:这个设备无法在 devfs 下使用)
# mknod /dev/initrd b 1 250
# chmod 400 /dev/initrd
第二:支持初始化RAM盘的能力,及所有执行格式和文件系统模块,都必须直接
编译进内核,不能采用可加载模块的方式。
第三:必须制作一个RAM盘映像文件。大概的步骤是,在一个块设备上创建一个
文件系统,把需要的文件复制进去,然后把这个块设备的内容输出成一个initrd文
件。目前至少有3种设备适合作为这种块设备:
O 软盘(能拿到任何地方试验,但速度太慢);
O RAM盘(最快,但浪费内存);
O Loopback设备(比较合适的解决方案)。
下面,我们将讨论使用loopback设备创建initrd文件的方法。
1)
确认lookback设备已经配置到内核里。
2)
创建一个适当大小的空白文件系统,例如:
# dd if=/dev/zero of=initrd bs=300k count=1
# mke2fs -F -m0 initrd
3)
把这个文件系统挂载进来,例如:
# mount -t ext2 -o loop initrd /mnt
4)
创建控制台设备。
# mkdir /mnt/dev
# mknod /mnt/dev/console c 5 1
5)
复制所有可能在initrd环境中用到的文件到/mnt目录中。(别忘了/linuxrc文
件)
6)
initrd执行环境的测试需要反复进行修正,为了避免不断的重新启动,可以使
用下列命令:
# chroot /mnt /linuxrc
当然这样启用initrd还是有限制的,就是不能干扰正常系统的执行状态(比如
重新配置网络接口等等。如果在一个用pivot_root指令转换根目录的执行环境中,
就可以做这些事情了)。
7)
卸载这个文件系统:
# umount /mnt
8)
这时,初始化RAM盘系统就在“initrd”这个文件中了。可以把它压缩小一点:
# gzip -9 initrd
最后,必须启动内核并载入initrd系统。需要设置下列引导参数:
root=/dev/ram0 init=/linuxrc rw
(只有需要写入initrd文件系统时才需要附加rw参数)。
使用 LOADLIN 当引导器时,可以执行:
LOADLIN C:/LINUX/BZIMAGE initrd=C:/LINUX/INITRD.GZ root=/dev/ram0 i
nit=/linuxrc rw
使用 LILO当引导器时,可以在 /etc/lilo.conf 中如下设置:
image = /boot/bzImage
initrd = /boot/initrd.gz
append = "root=/dev/ram0 init=/linuxrc rw"
如果使用其他引导器,请参考相关文档。
5.3.4 改变根目录设备
在执行完重要任务的尾声,linuxrc一般会执行改变根目录设备的动作,并让所
有程序在真实的根文件系统中展开。这个过程包括下列步骤:
O 挂载新的根文件系统
O 把initrd自己转到这个根文件系统中
O 结束所有旧的对initrd根文件系统的存取动作
O 卸载initrd文件系统并释放RAM盘内存
挂载新的根文件系统很简单:只需要把它挂载到当前根文件系统中的一个目录
下即可。例如:
# mkdir /new-root
# mount -o ro /dev/hda1 /new-root
最终改变根文件系统由pivot_root()系统调用或者pivot_root工具来完成(参
考手册页pivot_root(8))。pivot_root可以把当前根文件系统转移到新根的一个目
录中,然后把指定的目录当作新的根。在调用pivot_root之前,必须先为旧根准备
目录,例如:
# cd /new-root
# mkdir initrd
# pivot_root . initrd
现在,linuxrc仍然可以访问旧的根文件系统。执行下列指令将完全结束与旧根
的联系:
# exec chroot . what-follows <dev/console >dev/console 2>&1
其中what-follows是新根下面的一个程序,比如/sbin/init。(注意,这时如
果没有/dev/console,系统不能启动)。同时,为了保持不同版本之间的兼容性,
需要特别注意下列事项:
O 在调用pivot_root之前,当前目录应该是新的根目录;
O 使用“.”(当前目录)作为pivot_root的第一个参数(新根),为旧根指定目录
的第二个参数也要用相对路径;
O chroot这个命令应该在新、旧根系统当中都能使用;
O 然后调用chroot转移根文件系统;
O 在exec命令中使用象 dev/console 这样的相对路径。
特别提示:让initrd文件系统的目录结构与新根文件系统的结构一致,有利于
转换过程的顺利。
这时,initrd可以卸载,RAM盘也可以释放:
# umount /initrd
# blockdev --flushbufs /dev/ram0
注意:如果linuxrc或者它调用的其他程序的执行因为某种原因中断,则会启用
旧的change_root机制。
5.3.5 使用场合
实现initrd的主要动机是允许系统安装时模块化的内核配置。这个过程大致如
下:
1) 系统可以用一个最小配置的内核从软盘或者其他存储媒体启动,然后载入initr
d系统;
2) /linuxrc来决定需要什么来进一步挂载真实的根文件系统(如设备类型、驱动程
序等)或者支持发行版媒体(如CD-ROM,网络,磁带等);
3) /linuxrc载入必须的内核模块;
4) /linuxrc建立并安装根文件系统;
5) /linuxrc调用pivot_root改变根文件系统,并通过chroot一个程序(继续安装过
程);
6) 安装引导器;
7) 引导器被配置成装入initrd以及相关的模块,建立起系统环境;
8) 现在,系统可以引导,并执行其他的安装任务。
Initrd的关键作用是能够多重配置一个正常操作的系统,而不需要用一个庞大
的内核,或者重新编译、连接内核。在制作Linux发行版(光盘等)、系统恢复盘等
方面有广泛的应用。
5.3.6 淘汰的根转换机制(change_root)
可以通过改写/proc/sys/kernel/real-root-dev这个文件的数字值来改变真实
的根设备。例如:
# echo 0x301 >/proc/sys/kernel/real-root-dev
但是这个机制已经被淘汰,虽然目前的内核仍然支持,不能保证以后的内核会
支持。所以不要使用。
5.4 系统初始化(init)
(参考资料[10])
5.4.1 init的任务
UNIX的init指的不是一个程序、而是一类程序。Init一般是指系统引导时执行
的第一个进程,也是唯一的进程。当内核完成计算机硬件的设置之后,就把控制权
交给init。内核只产生init这一个进程,而系统中其他所有进程都是由init负责产
生(spawn),主要包括各种系统服务进程,比如控制台的登录会话(getty)。主
要任务包括:
O 产生其他进程;
O 重新启动已经退出的进程;
O 负责清理系统中的“僵尸”进程(init是所有其他进程的祖先);
O 处理系统关机(stop所有进程,unmount文件系统);
内核并不关心拿什么来作为系统的init,可以是下列几种选择之一:
O SysVinit (作者:Miquel van Smoorenburg),或者
O simpleinit (作者:Peter Orbaek),或者
O 一个shell脚本,或者
O 嵌入式系统中你的应用程序。
不过路径名字必须是/sbin/init,/etc/init,或/bin/init(因为已经编译到
内核里面了)。如果这几个路径都找不到,系统就完蛋了。为了增加灵活性,内核
提供了命令行选项可以指定init路径:“init=”。
5.4.2 SysVinit
/etc/inittab
/etc/rc.d
大多数Linux发行版使用的init是SysVinit,也就是System V UNIX的实现。其
主要思想是规定了不同的“运行级别(runlevel)”。通过配置文件/etc/inittab
,定义了系统引导时做什么,进入或者切换到一个运行级别时做什么。配置文件每
一行的语法为:
id:runlevel:action:command
细节请参考手册页inittab(5)。
整个过程中用到的脚本都放在/etc/rc.d目录。
5.4.3 两种风格:Slackware vs. Debian
关于配置文件/etc/inittab和脚本/etc/rc.d的实现和组织主要有两种不同的风
格,其有代表性的发行版分别为Slackware和Debian(Redhat同Debian)。这两种风
格之间有几个明显的区别,可以比较容易的识别。
例如在/etc/inittab中,定义进入运行级别0时运行的脚本分别为/etc/rc.d/r
c.0和/etc/init.d/rc 0(在Redhat中/etc/init.d是指向/etc/rc.d/init.d的一个
符号链接,注意这里0是脚本rc的命令行参数)。因此,Slackware风格的/etc/rc.
d中应该是一系列相对独立的脚本,对应于配置文件中每个动作的定义(我没有见过
Slackware哦)。
在我们所熟悉的Redhat中,/etc/rc.d的组织要复杂的多,每个运行级别对应一
个子目录/etc/rc.d/rcX.d(X表示运行级别0~6),下面放的是一系列形如SXXfoo
和KXXbar(S表示Start某个服务,K表示Kill某个服务,XX是两位数字,决定了该脚
本执行的顺序)的符号链接,指向/etc/rc.d/init.d中的脚本,每个脚本对应一项
服务程序。
另外,还有两个重要脚本值得一提:/etc/rc.d/rc.sysinit是系统引导时首先
要执行的(完成系统初始化的各项工作),而/etc/rc.d/rc.local在最后执行(类
似DOS的autoexec.bat)。
读一下/etc/rc.d/rc.sysinit,/etc/rc.d/rc和/etc/rc.d/init.d/中的某个脚
本,就会对Redhat的启动过程和风格有比较清晰的了解。
据说Slackware风格比Debian的速度要快一些。可能是后者的组织结构比较复杂
的缘故吧。
5.5 PAM
(参考资料[11],http://www.kernel.org/pub/linux/libs/pam/,手册页pam
(8))
5.5.1 什么是PAM
PAM(Pluggable Authentication Modules)是为了解决计算机系统中用户认证
的问题而引入的一种实现方案。PAM的目标为:
O 将认证功能从应用中独立出来,单独进行模块化设计、实现和维护(而不是象以
前那样,将认证功能的代码跟应用程序编译在一块);
O 为这些认证模块建立标准API,以便各应用程序能方便的使用它们提供的各种功能
;
O 认证机制对其上层用户(包括应用程序和最终用户)是透明的。
PAM机制由SUN设计并首先在Solaris 2.3上部分实现,后来逐渐在其他UNIX平台
上实现,包括Linux(其实现称为Linux-PAM)。
5.5.2 PAM的结构
PAM采用分层的体系结构。最下面是模块层,负责实现具体的认证功能,包括帐
户管理(account)、口令鉴别(auth)、口令管理(password)和会话管理(ses
sion)4个模块。
应用接口层位于模块层之上,调用下层提供的服务,并向上(应用程序)隐藏
PAM实现的细节。
5.5.3 配置文件
/etc/pam.conf
/etc/pam.d (如果存在这个目录,则忽略/etc/pam.conf)
配置文件是应用接口层的另一个重要组件。其作用主要是为应用选定具体的鉴
别模块,模块间的组合以及规定模块的行为。
/etc/pam.conf的语法为每行5项(在Linux-PAM的手册页pam(8)中有更详细的描
述):
service type control module-path module-arguments
Service是服务的名字,比如login,su等。注意,/etc/pam.d目录下面有许多
配置文件,分别对应某项系统服务,文件名即等于service,因此每行只有4项。
Type为account,auth,password,session之一,即要使用的认证模块。每个
应用可以使用多个认证模块,或者说可以将模块“堆叠”使用,但是每一行只能定
义一个,因此一项服务可以有多行配置。
Control规定了如何处理模块认证失败或成功,可以是requisite、required、
sufficient或optional。
Module-path是PAM库文件的文件名。缺省路径是/lib/security/。
Module-arguments是可以传给模块的参数。
5.5.4 other
特别需要指出的是,有一个特殊的服务名字——other。如果没有明确指明应用
于某项服务的规则,就用other的定义来处理。因此,我们可以想到,系统中PAM的
最简配置为,只有/etc/pam.d/other这一个配置文件,即可处理所有的认证请求。
下面是我们用过的一个other配置文件的例子:
[xmdong@lips pam.d]$ cat other
#%PAM-1.0
auth required /lib/security/pam_unix.so
account required /lib/security/pam_unix.so
password required /lib/security/pam_unix.so
session required /lib/security/pam_unix.so
当然,这样处理对系统的安全性来说是不利的,最好还是给每项服务定义单独
的配置文件。这不过是一种简化问题的临时方案。
5.6 再论initrd
在前面的5.3节中,对initrd机制已经有了比较详细的讨论。准确的说,该部分
内容来自linux-2.4/Documentation/initrd.txt 文件。但是在实践中,我们发现其
中描述的有些方法并不适用。是新版本内核的变化造成的(内核实现与文档不同步
),还是2.4版内核的bug,不得而知。在我查证的过程中,看到网上有不少相关的
问题和讨论。遗憾的是至今我还不能确认到底是什么原因。
后来我又专门读了2.2版内核中所附带的 initrd.txt 文件。我感觉实际的情况
似乎更接近于这个老文档的描述。我还看到一个有关的patch文件,从该文件中可以
明确看到新的initrd.txt文档的改变。下面是该文件的开始部分(行首的-表示老文
档有而新文档中已经删除,+表示新文档中增加的内容)。
Using the initial RAM disk (initrd)
===================================
-Written 1996 by Werner Almesberger <almesber@lrc.epfl.ch> and
- Hans Lermen <lermen@elserv.ffm.fgan.de>
+Written 1996,2000 by Werner Almesberger <werner.almesberger@epfl.ch
> and
+ Hans Lermen <lermen@fgan.de>
-initrd adds the capability to load a RAM disk by the boot loader. T
his
-RAM disk can then be mounted as the root file system and programs c
an be
-run from it. Afterwards, a new root file system can be mounted from
a
-different device. The previous root (from initrd) is then either mo
ved
-to the directory /initrd or it is unmounted.
+initrd provides the capability to load a RAM disk by the boot loade
r.
+This RAM disk can then be mounted as the root file system and progr
ams
+can be run from it. Afterwards, a new root file system can be mount
ed
+from a different device. The previous root (from initrd) is then mo
ved
+to a directory and can be subsequently unmounted.
下面我试图将我遇到的问题和能够确认的东西记录一下。(后面我们分别称新
、老文档为initrd.txt-2000和initrd.txt-1996)
5.6.1 根到底在哪里
在配置使用initrd之前,首先你要清楚,系统正常运行时,根文件系统挂载在
什么设备上(也就是说,root device是什么——root在哪里?)。
我们知道,使用initrd机制的话,系统启动时先以RAM Disk(/dev/ram0)作为
根,然后通过某种方法将根转换到“真实的根(real-root-dev)”(之所以说rea
l,是与前面的RAM里临时的根比较而言)。一种常见的情形是启动时用initrd,然
后切换到一个硬盘分区,比如/dev/hda1。
事实上,这个根转换不是必须的。我们完全可以就把/dev/ram0作为我们真实的
根设备。这时,系统启动之后所有的操作都在RAM Disk中进行。当然这样做也有一
个明显的缺点,即无法保存数据。比如对系统配置进行的修改(象IP地址),重新
启动之后就丢掉了。如果要保存数据,就要想别的办法。
LiPS目前的做法就是这一种(没有根转换,initrd就是真实的根)。为了在重
新启动之后保持配置文件的修改,我们将目录/etc链接到了其他非易失存储器(例
如,一个硬盘分区,或者USB盘的分区)。
指定根设备是通过内核参数“root=”来完成的。以GRUB引导器的配置为例:
kernel /boot/bzImage ro ramdisk_size=65536 root=/dev/ram0
initrd /boot/initrd.img
其中 ramdisk_size=65536 说明初始RAM盘的大小为64MB,root=/dev/ram0 说
明根的位置。下面第二个例子来自一台在hda1安装了Redhat 9的机子:
kernel /boot/vmlinuz-2.4.20-8 ro root=/dev/hda1
initrd /boot/initrd-2.4.20-8.img
很明显,这里 root=/dev/hda1 指定了真实的根,其启动过程包含根的转换。
但是这里没有指定 ramdisk_size,因为initrd的缺省大小是4MB,而initrd-2.4.2
0-8.img比较小,所以用缺省值即可。
(我们多次提到root, root filesystem, root device, 自己体会吧。)
5.6.2 linuxrc
在initrd机制的设计中,初始RAM盘只是作为一个过渡性质的根设备,由/linu
xrc来完成操作环境准备(比如加载一些不常用的、没有编译进内核的驱动程序模块
)和根转换工作。
Linuxrc的执行是自动的,即内核挂载initrd盘作为根,如果根下面有这个文件
(也就是/linuxrc),就执行它。(如果没有呢?当然什么也不做。所以linuxrc也
并不是必不可少的。)
接下来说说linuxrc的内容。下面的例子是从initrd-2.4.20-8.img中释放出来
的:
[root@lips initrd]# cat linuxrc
#!/bin/nash
echo "Loading jbd.o module"
insmod /lib/jbd.o
echo "Loading ext3.o module"
insmod /lib/ext3.o
echo Mounting /proc filesystem
mount -t proc /proc /proc
echo Creating block devices
mkdevices /dev
echo Creating root device
mkrootdev /dev/root
echo 0x0100 > /proc/sys/kernel/real-root-dev
echo Mounting root filesystem
mount -o defaults --ro -t ext3 /dev/root /sysroot
pivot_root /sysroot /sysroot/initrd
umount /initrd/proc
该脚本中首先加载了支持ext3文件系统所需的模块,然后挂载真实的根文件系
统,并进行转换。特别需要注意的是,linuxrc脚本一般是用nash解释执行的,而不
是通常的bash。有关nash的详情请看手册页nash(8)。简单说,nash内置实现了多数
在执行linuxrc这个阶段可能用到的命令,比如mount, pivot_root, umount;而如
果使用bash,象mount等都是“外部命令”,就需要复制/bin/mount到initrd映像文
件中。实际上,在initrd-2.4.20-8.img的/bin中只有insmod和nash两个程序(mod
probe符号链接到insmod):
[root@lips initrd]# ls bin/
insmod modprobe nash
当我按照5.3.4节描述的方法测试linuxrc的时候发现,我的脚本头两句执行就
报错。
mount –o ro /dev/hda11 /sysroot
cd /sysroot
这里我的真实的根设备是/dev/hda11,/sysroot是initrd映像中存在的一个目
录。这两句完全是按照该文档的要求写的。后来经过多次试验才发现问题之所在。
第一,nash实现的mount命令语法与我们常用的/bin/mount并不完全兼容;第二,n
ash根本没有实现cd命令。
因此,可以推断initrd.txt-2000中讲linuxrc时所举例子不是用的nash。实际
上在该文档中根本没有指明用什么shell。再回头仔细看才发现,所有例句都是在r
oot的shell命令提示符“#”后面的,那么它用的是bash之类了。但是令人迷惑的是
,同样在该小节中稍后出现如下例句: # exec chroot . what-follows <dev
/console >dev/console 2>&1
为什么用exec执行chroot呢?chroot可以在命令行直接用啊。文档中讲到,wh
at-follows可以是/sbin/init。我用nash试了下面这个句子(nash中用exec执行外
部命令):
exec chroot . /sbin/init <dev/console >dev/console 2>&1
还是不行。错误信息:
Usage: init 0123456SsQqAaBbCcUu
开始我还以为后面那一串是个“magic number”,后来想以Usage开头应该是讲
使用这个命令的语法。看init(8)手册页,果然要求给一个参数,0~6当然是运行级
别啦,试了一个init 3也还是不行。尤其搞不明白后面那些个console什么意思。
5.6.3 根转换机制:新的?旧的?
前面我们多次提到了根的转换。在initrd.txt的两个版本中,分别描述了两种
根转换机制,其中2000版中称1996版的内容已经“过时”,不推荐使用。然而事实
上要麻烦的多,事情并不总是按照预期(如文档所述)运行。
为了弄清楚这个问题,我进行了一系列测试。环境是这样的:
O 使用initrd机制,已经准备好了根文件系统映像文件initrd.img,准备好了内核
bzImage;
O initrd.img的所有内容释放到一个硬盘分区/dev/hda11,ext2文件系统;
O 在/dev/hda11上面增加一个/boot目录,存放bzImage和initrd.img文件;
O 以/dev/ram0作为根启动,然后以某种机制转换到“真实的根”/dev/hda11。
系统的引导器是GRUB,安装在硬盘MBR,grub.conf位于/dev/hda1(也就是做测
试的实际的根文件系统)。按照上述环境配置,在grub.conf中增加如下内容:
title lips (testing ......)
root (hd0,10)
kernel /boot/bzImage ro root=/dev/hda11 ramdisk_size=65536
initrd /boot/initrd.img
下面我们分别使用不同的/linuxrc进行了测试,以确认到底哪种机制是可行的
。
第一,如果没有/linuxrc会怎么样呢?
结果是系统能够成功启动到以/dev/hda11作为根,并且把initrd(也就是旧根
文件系统)的内容移到了/initrd目录。这是符合initrd.txt-1996文档中第一段的
描述的。所以只要你使用了initrd,指定了新的根,并且准备好了/initrd目录(已
经在/dev/hda11创建了/initrd),内核可以自动完成根的转换工作。终端上面有如
下输出信息:
Trying to move old root to /initrd ... okay
这里有个小问题,即最后/initrd没有卸载,这意味着initrd所分配内存也没有
释放。因此umount /initrd不是内核自动完成的,而应该在/linuxrc中的某个位置
进行。如果linuxrc仅包含卸载/initrd的命令,如下:
#!/sbin/nash
umount /initrd
则执行情况是这样的:umount命令报错(返回错误码22),然后出现前面所示
Trying…的信息。结果跟前面是一样的。因为linuxrc执行在先,当时旧根还没有被
转移到/initrd中来。
如果没有/initrd目录会怎样?我们把/dev/hda11上面的/initrd目录删除,重
新启动(没有/linuxrc),结果是可以启动到以/dev/hda11为根,且看不到原来的
initrd。该过程中出现如下信息:
Trying to move old root to /initrd ... failed
Unmouting old root
Trying to free ramdisk memory ... okey
第二,pivot_root可以用,但是chroot不能用。
pivot_root是在initrd.txt-2000文档中引入的新机制。在测试pivot_root的过
程中,除了在nash中不能用cd命令造成的困惑(这个问题前面已经说明)之外,使
用pivot_root基本上是成功的。/linuxrc脚本的主要内容如下:
#!/sbin/nash
mount -o defaults --ro -t ext2 /dev/hda11 /sysroot
pivot_root /sysroot /sysroot/initrd
但是,用这个脚本不能启动,造成系统挂起:
VFS: Cannot open root device “hda11” or 03:0b
Please append a correct “root=” boot option
Kernel panic: VFS: Unable to mount root fs on 03:0b
如果按照initrd.txt-2000的指导,pivot_root之后应该执行chroot,但是我始
终没有办法使chroot成功执行(有关情况前面已经说明)。不过可以肯定的是,pi
vot_root执行成功之后,当前目录就是新的根目录。
第三,/proc/sys/kernel/real-root-dev
initrd.txt的新版本中最后一节专门指出,向/proc/sys/kernel/real-root-d
ev文件中echo数字的方法(称为change_root机制)是过时的。然而令人迷惑的是,
Redhat 9的缺省安装产生的initrd-2.4.20-8.img中(前面已经列出了其/linuxrc文
件内容),同时使用了pivot_root和change_root机制。
测试下列/linuxrc脚本,系统可以启动到/dev/hda11,但是/initrd没有卸载。
如果在脚本末尾umount /initrd,会返回错误码16。
#!/sbin/nash
mount -t proc /proc /proc
mkrootdev /dev/root
echo 0x0100 > /proc/sys/kernel/real-root-dev
#echo 0x030b > /proc/sys/kernel/real-root-dev
mount -o defaults --ro -t ext2 /dev/hda11 /sysroot
pivot_root /sysroot /sysroot/initrd
umount /initrd/proc
虽然这样可以启动,但是有关real-root-dev还有几个没有弄清楚的问题:
real-root-dev的值到底是什么?
目前我还没有看到解释real-root-dev的值是什么东西的正式文档,根据情况判
断的话,我认为应该是该设备的“major-minor”(主、次设备号)。
为什么用0100而不是030b?
如果上述判断准确的话,那么写入0100就表示/dev/ram0。这就奇怪了,“真实
的根设备”应该是/dev/hda11呀,那么是不是应该写030b呢?但是测试发现用030b
同样会导致不能启动。
Kernel panic: VFS: Unable to mount root fs on 03:0b
5.7 提示
5.7.1 文件系统目录层次结构
根文件系统的目录结构最好符合“Filesystem Hierarchy Standard(文件系统
层次标准)”[7],而不能自己随意创建。
顺便说一下,象这种情况:/bin, /sbin, /usr/bin, /usr/sbin 这4个目录都
可以放应用程序,具体放在哪个目录,则要根据该应用程序在系统中的作用来确定
。一般地,系统的关键应用程序放在/bin,只能给超级用户root访问的关键应用程
序放在/sbin,相比之下比较次要的应用程序和root应用程序分别放在/usr/bin和/
usr/sbin。
5.7.2 mklips.sh(制作LiPS的脚本)
复制文件的工作全部可以手工进行,但是做一个shell脚本来完成所有工作显然
是一个更好的办法。
在我们的项目中,制作LiPS(也就是完成裁减Linux)的工作,除了编译内核之
外,都可以通过mklips.sh脚本完成。
5.7.3 lips.conf(LiPS配置文件)
lips.conf是LiPS的配置文件,由mklips.sh脚本使用,主要用来控制所制作的
LiPS系统包括哪些可选的模块,例如:
INCLUDE_APACHE=no
INCLUDE_IPTABLES=no
INCLUDE_PROFTPD=yes
INCLUDE_NFSD=no
INCLUDE_SAMBA=yes
通过一系列的INCLUDE_?=yes或者no,可以实现LIPS的模块化。在mklips.sh脚
本中,判断每个INCLUDE的值,来确定是否复制某个模块相关的文件。
配置文件中还可以包括其他可配置的信息。
5.7.4 cp –dpR
复制文件请使用“cp –dpR”命令,可以保留文件的属性(特别是对于设备节
点和符号连接等特殊文件),还可以复制整个目录。
5.7.5 如何判断一个程序依赖哪些文件
除了库文件外,关键是找出相关的配置文件。man手册页是判断应用程序配置文
件的重要依据。我们以安装Apache Server为例来说明判断复制哪些文件的过程。
首先确定其可执行文件是/usr/sbin/httpd。然后看httpd的手册页,即
[root@lips lips]# man httpd
httpd(8)
httpd(8)
NAME
httpd - Apache hypertext transfer protocol server
…………(略)
FILES
/etc/httpd/conf/httpd.conf
/etc/mime.types
/etc/httpd/conf/magic
/var/log/httpd/error_log
/var/log/httpd/access_log
/var/run/httpd.pid
February 1997
httpd(8)
(END)
这时关键看最后一段FILES,列出了有关的配置文件、日志文件和进程文件,那
么我们就知道了httpd要在目标系统中运行,这几个配置文件是必不可少的。
把httpd和用ldd找出的库文件以及上述配置文件复制到目标系统,运行httpd试
一下,看看是否正常。一般情况下应该没有问题。但是httpd比较特殊,除了ldd找
出的库文件外,它还需要很多可加载的模块(库)。怎么发现呢?重要的一点是看
出错信息(有时候还要查看日志中的信息,/var/log/messages或者应用程序日志)
,看httpd报告缺少什么;还可以看看相关的配置文件httpd.conf,也能找到有用的
信息。然后我们发现需要/usr/lib/httpd这个目录下的库,把它也复制过去即可。
5.7.6 带库文件复制——lcp()
在复制可执行文件(软件)的过程中,如果没有正确复制相关的库文件,该软
件肯定不能在目标系统中正常运行。而通过ldd命令手工确定每个软件的库文件,是
一个繁琐且费时的任务。另一个隐含的缺陷是,当你不再需要某个软件时,删掉可
执行文件之后,相应的库文件是否需要删除呢?如果删除了,而该库文件是其他软
件也需要的,会导致其他软件不能用;如果不删除,就有可能留一些垃圾库文件在
目标根文件系统中。
因此,最好是能够保证每次复制可执行文件的同时自动复制相关联的库文件。
我写了一个脚本函数 lcp() 来解决这个问题。语法和主要代码如下(以#开头的行
为注释):
# lcp BIN BINPATH LIBPATH
# BIN:可执行文件路径
# BINPATH:可执行文件的目标路径(复制到哪里)
# LIBPATH:相关库文件的目标路径
lcp()
# run()是另一个函数,执行它后面的shell命令,并在遇到错误时报错、写错
误日志
# 首先复制BIN到BINPATH
run "$CP $BIN $BINPATH"
# 使用ldd找出与BIN相关的库文件,把ldd的输出送给LDDSTR
LDDSTR=`ldd $BIN`
# 循环检查LDDSTR字符串的每一项
for STR in $LDDSTR; do
# 判断出库文件路径
LIB=`echo $STR | grep "/lib/" `
if [ -z "$LIB" ]; then
continue
fi
# 复制LIB到LIBPATH,除非要复制的库文件在目标路径中已经存在
LIBNAME=`basename $LIB`
if [ ! -f "$LIBPATH/$LIBNAME" ]; then
run "$CP $LIB $LIBPATH"
run "cp $LIB $LIBPATH"
else
log "Warning: File "$LIBPATH/$LIBNAME" exis
t."
fi
done
}
5.7.7 库文件的命名
(参考资料[9]之6.2节)
我们这里所说的“库”都是指的GNU C Library,也就是glibc。
在/lib下面可以看到很多库文件,主要分为4类:
O 实际的共享库(Actual shared libraries)
文件名格式为 libLIBRARY_NAME-GLIBC_VERSION.so,其中LIBRARY_NAME是库的真正
名称,GLIBC_VERSION是glibc包的版本号。比如glibc 2.2.3版math(数学)库的文
件名为libm-2.2.3.so。
O 主修订版本符号链接(Major revision version symbolic links)
库的主修订版本号与glibc版本号不同。比如,glibc 2.2.3的C共享库libc-2.2.3的
主修订版本号为6,libdl-2.2.3为2。主修订版本符号链接的命名格式为 :libLIB
RARY_NAME.so.MAJOR_REVISION_VERSION,那么C共享库的符号链接即 libc.so.6。
一个程序连接到一个库之后,它在运行时访问的就是这个符号链接。
O 版本无关的符号链接(Version-independent symbolic links to the major re
vision version symbolic links)
这些符号链接的作用是为用到某个库的所有程序提供一个统一的入口,而不管实际
上glibc的版本或者该库的主修订版本。其命名格式为 libLIBRARY_NAME.so。比如
,libm.so链接到libm.so.6,libm.so.6又链接到libm-2.2.3.so。唯一的例外是li
bc.so,是个脚本。连接一个程序时用的是这个符号链接。
O 静态库文件(Static library archives)
这些库是应用程序静态连接用的。格式为 libLIBRARY_NAME.a.
创建根文件系统比编译内核要复杂的多,也更难理解。这里的关键是掌握init
rd(初始化RAM盘)的使用方法。
5.1 根文件系统
这里我们将要创建的根文件系统与通常Linux主机的根文件系统类似,只是它应
该仅仅包括系统运行所必须的应用程序、库和相关文件的最小集合。根文件系统的
尺寸大小是一个重要的指标。
5.2 文件系统的内容
5.2.1 应用程序(applications)
/bin,/sbin,/usr/bin,/usr/sbin
应用程序大致可以分为3部分,第一是操作系统正常运行所需的基本工具软件,
比如bash,cp,rm等;第二是提供某项服务的服务器软件,比如httpd,telnetd,
proftpd等;第三是我们所开发的应用程序。其实后面讲的配置文件等也可以粗略按
照这个原则来分类。
到底需要复制哪些软件,弹性是比较大的,很多软件都是可要可不要,可以实
际情况灵活选择。这个列表可能很长,并且变化也比较大,因此不在这里列出。
为了进一步减小所创建的根文件系统的尺寸,可以考虑使用下列工具包软件来
替代某些标准的工具:
O BusyBox(http://www.busybox.net)
O TinyLogin(http://tinylogin.busybox.net)
O Embutils(http://www.fefe.de/embutils/)
其详细使用方法请参考相关资料,此处不再赘述。LIPS的实现目前没有使用这
些软件包。
5.2.2 设备文件(device files)
/dev
设备文件也可以称作设备节点(device node)。设备文件非常重要,缺少某些
有些设备文件可能导致系统不能正常运行甚至不能引导。有些设备文件是必须的,
而更多的是根据具体目标系统的硬件配置来进行取舍。
比如硬盘的设备文件,在完整的系统中一般有hda,hdb,……,hdt,即最多支
持20个IDE硬盘,每个硬盘有hdX1,hdX2,……,hdX32,(其中X表示a-t),即支
持32个分区,另外还有表示SCSI硬盘的节点。根据实际情况,如果只需要支持少量
的硬盘、少量的分区,这些节点可以被大大简化。如果目标系统中没有的设备,其
对应的设备文件也可以省掉。
设备文件
描述
/dev/console
系统控制台设备,非常重要。
/dev/fd0
第一个软驱
/dev/hda
/dev/hda[1-8]
IDE硬盘及分区
/dev/initctl
实际上是一个FIFO设备,跟init有关(切换运行级别时用于新init与原init通信)
/dev/initrd
Initial RAM disk
/dev/input
(目录)Input core(包括游戏杆、鼠标等)
/dev/kmem
内核虚拟内存
/dev/loop[0-7]
Loopback设备
/dev/mem
访问物理内存
/dev/null
NULL设备
/dev/psaux
PS/2鼠标
/dev/ptmx
UNIX98 PTY master
/dev/pts
(目录)UNIX98 PTY slaves
/dev/ptyp[0-7]
伪终端主设备(远程登录使用)
/dev/ram[0-7]
/dev/ramdisk
/dev/ram
RAM Disk设备。至少/dev/ram0是应用initrd机制所必须的。
/dev/ramdisk链接到/dev/ram0,是为了兼容老版本内核而保留的。
/dev/ram链接到/dev/ram1。
/dev/random
随机数发生器
/dev/sda
/dev/sda[1-8]
SCSI磁盘及分区设备
/dev/shm
共享内存设备
/dev/systty
指向系统tty设备的符号链接,一般是tty0。
/dev/tty
当前TTY设备
/dev/tty[0-7]
虚控制台(Virtual console)
/dev/ttyp[0-7]
伪终端从设备
/dev/ttyS0
/dev/ttyS1
串口(COM1和COM2)
/dev/urandom
速度更快、安全性较差的随机数发生器
/dev/zero
零设备,只能读0出来
设备节点的主设备号(Major)、次设备号(Minor)的文档是内核源代码中的
/Documentation/device.txt,如果有疑问可以查看这个文件[8]。
5.2.3 脚本和配置文件(scripts and configuration files)
/etc
/etc/rc.d目录下的启动脚本是系统的重要部分。必须对启动脚本做相应的修改
以简化系统的启动过程。
系统和各种应用程序用到的几乎所有的配置文件都位于/etc目录,是裁减Linu
x最麻烦的部分,最容易出问题。配置文件的选择需要综合很多方面的信息,需要对
系统有比较全面、深入的了解,并结合经验才能做出正确的判断。
配置文件
描述
/etc/default
(目录)某个命令(比如useradd)的缺省设置(man useradd(8))
/etc/ld.so.cache
由ldconfig命令根据/etc/ld.so.conf文件产生
/etc/ld.so.conf
库文件路径配置文件,ldconfig命令根据该配置文件生成/etc/ld.so.cache
/etc/localtime
本地时间、时区设置
/etc/login.defs
全局缺省设置
/etc/fstab
文件系统列表(man fstab(5))
/etc/group
组文件(man group(5))
/etc/hosts
列出主机名和IP地址(man hosts(5))
/etc/init.d
符号链接到/etc/rc.d/init.d
/etc/initlog.conf
Initlog日志配置文件(man initlog(8))
/etc/inittab
Init配置文件(man inittab(5))
/etc/ioctl.save
该文件包含了用于单用户模式的串口和终端参数,因为这些参数是由getty设置的,
而在单用户模式时没有运行getty,所以用该文件保存参数。单用户模式对系统安全
是个威胁,我们应该禁止使用单用户模式,因此这个文件实际上并没有必要复制过
来。
/etc/issue
登录信息和标识文件(man issue(5))
/etc/modules.conf
模块的配置文件(man modules.conf(5))
/etc/mtab
已经挂载的文件系统列表(man mount(8))
/etc/nsswitch.conf
Name Service Switch的配置文件(配置名称服务数据源和查询的顺序)(man nss
witch.conf(5))
/etc/pam.d
放置PAM配置文件的目录(有关PAM请参考5.5节)
/etc/passwd
用户口令文件(man passwd(5))
/etc/profile
系统环境变量和登录配置文件
/etc/rc.d
放置启动脚本的目录
/etc/services
列出可用的网络服务及其端口(man services(5))
/etc/termcap
终端(terminal)功能数据库(man termcap(5))
还有那些跟特定应用程序相关的配置文件,比如apache服务器需要的/etc/htt
pd/conf/httpd.conf 等,此处不再一一列出。
5.2.4 库文件(libraries)
/lib,/usr/lib,/usr/share
库文件也是系统运行所必需的。到底需要哪些库文件,是根据所复制的可执行
程序用 ldd 工具来确定的。比如,要知道/bin/bash需要哪些库文件,使用如下命
令:
[root@lips xmdong]# ldd /bin/bash
libtermcap.so.2 => /lib/libtermcap.so.2 (0x40020000)
libdl.so.2 => /lib/libdl.so.2 (0x40024000)
libc.so.6 => /lib/tls/libc.so.6 (0x42000000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
对复制到新的根文件系统的每个可执行程序,都要使用这种方法来确定其所需
要的库,然后把这些库文件也复制过来。
需要注意的是,有些库文件使用上述方法是找不出来的,但是却是系统必须的
。比如:
库文件
描述
/lib/libnss_files*
这个库是跟/etc/nsswitch.conf有关的,必不可少,否则系统不能正常使用。(ma
n nsswitch.conf(5))
/lib/security/pam_unix.so
这个库跟PAM有关,是安全、认证方面的,必不可少,否则系统无法登录。
5.2.5 必要的目录
/home,/mnt,/proc,/root,
/var,/var/log,/var/run,/var/lock/subsys
5.3 初始化RAM盘(initrd)
(参考资料[5])
初始化RAM盘(initrd)提供了引导器加载RAM盘的能力。这个RAM盘可以被挂载
(mount)成根文件系统,并执行其上的程序。然后,可以从另一个设备挂载一个新
的根文件系统,而原来的根文件系统(也就是initrd)会被移到一个目录里并卸载
。
initrd技术主要设计用来让系统启动过程可以分两个阶段进行,首先让内核以
一组最小的、被编译进内核里的驱动程序来启动,然后从initrd中加载其他的模块
。
5.3.1 操作步骤
使用initrd时,系统典型的引导步骤如下:
1) 引导器加载内核和初始化RAM盘;
2) 内核把initrd转到一个一般的RAM盘中,并且释放原来被initrd占用的RAM;
3) initrd以可读写模式被挂载到根目录;
4) 执行/linuxrc(linuxrc可以是任何可执行文件,包括shell脚本在内;它具有u
id 0即超级用户的权限,基本上可以做init程序中能够做的任何事情);
5) 在linuxrc中挂载真正的根文件系统;
6) linuxrc使用pivot_root系统调用,把真正使用的根文件系统挂载到根目录;
7) 在根文件系统上执行通常的引导过程(比如执行/sbin/init);
8) initrd文件系统被删除。
注意,改变根目录并不包括卸载旧的根文件系统,因此有可能在转变过程中仍
有进程在initrd上运行。另外,挂载在initrd目录下的根文件系统仍然是可用的。
5.3.2 引导选项
initrd技术增加了下列引导选项:
initrd=<path>
装入指定的文件作为初始化RAM盘。当使用 LILO 当引导器时,你可以用/etc/
lilo.conf 文件中 INITRD 这个配置参数,来指定初始化RAM盘文件。
noinitrd
initrd的数据仍会保留,但不会装入到一个RAM盘里,真正使用的根文件系统将
会被挂载。initrd的数据能夠从 /dev/initrd 这个设备中被读出来。注意,initr
d的数据可以是任何结构,并不一定必须是一个文件系统的映像,这个选项主要用来
进行debug。
注意:/dev/initrd 是一个只读并且只能使用一次的设备,最后一个程序一旦
关闭它, 所有数据将被释放,而且设备也不能再被打开。
root=/dev/ram0
initrd 先被挂载成根目录,接着进行正常的启动过程(这时RAM盘仍被挂载成
根)。
5.3.3 安装
首先:我们要在正常的根文件系统中创建一个容纳initrd文件系统的目录,例
如:
# mkdir /initrd
对目录名称并没有特别的限制,在pivot_root(2)的man手册页中有更详细的说
明。
如果根文件系统是在引导程序时被建立的(例如,你在制作安装软盘),在建
立根文件系统的同时应该创建 /initrd 目录。
在某些情况下initrd虽然未被挂载,只要有下列设备存在,它的內容仍是可被
访问的。(注意:这个设备无法在 devfs 下使用)
# mknod /dev/initrd b 1 250
# chmod 400 /dev/initrd
第二:支持初始化RAM盘的能力,及所有执行格式和文件系统模块,都必须直接
编译进内核,不能采用可加载模块的方式。
第三:必须制作一个RAM盘映像文件。大概的步骤是,在一个块设备上创建一个
文件系统,把需要的文件复制进去,然后把这个块设备的内容输出成一个initrd文
件。目前至少有3种设备适合作为这种块设备:
O 软盘(能拿到任何地方试验,但速度太慢);
O RAM盘(最快,但浪费内存);
O Loopback设备(比较合适的解决方案)。
下面,我们将讨论使用loopback设备创建initrd文件的方法。
1)
确认lookback设备已经配置到内核里。
2)
创建一个适当大小的空白文件系统,例如:
# dd if=/dev/zero of=initrd bs=300k count=1
# mke2fs -F -m0 initrd
3)
把这个文件系统挂载进来,例如:
# mount -t ext2 -o loop initrd /mnt
4)
创建控制台设备。
# mkdir /mnt/dev
# mknod /mnt/dev/console c 5 1
5)
复制所有可能在initrd环境中用到的文件到/mnt目录中。(别忘了/linuxrc文
件)
6)
initrd执行环境的测试需要反复进行修正,为了避免不断的重新启动,可以使
用下列命令:
# chroot /mnt /linuxrc
当然这样启用initrd还是有限制的,就是不能干扰正常系统的执行状态(比如
重新配置网络接口等等。如果在一个用pivot_root指令转换根目录的执行环境中,
就可以做这些事情了)。
7)
卸载这个文件系统:
# umount /mnt
8)
这时,初始化RAM盘系统就在“initrd”这个文件中了。可以把它压缩小一点:
# gzip -9 initrd
最后,必须启动内核并载入initrd系统。需要设置下列引导参数:
root=/dev/ram0 init=/linuxrc rw
(只有需要写入initrd文件系统时才需要附加rw参数)。
使用 LOADLIN 当引导器时,可以执行:
LOADLIN C:/LINUX/BZIMAGE initrd=C:/LINUX/INITRD.GZ root=/dev/ram0 i
nit=/linuxrc rw
使用 LILO当引导器时,可以在 /etc/lilo.conf 中如下设置:
image = /boot/bzImage
initrd = /boot/initrd.gz
append = "root=/dev/ram0 init=/linuxrc rw"
如果使用其他引导器,请参考相关文档。
5.3.4 改变根目录设备
在执行完重要任务的尾声,linuxrc一般会执行改变根目录设备的动作,并让所
有程序在真实的根文件系统中展开。这个过程包括下列步骤:
O 挂载新的根文件系统
O 把initrd自己转到这个根文件系统中
O 结束所有旧的对initrd根文件系统的存取动作
O 卸载initrd文件系统并释放RAM盘内存
挂载新的根文件系统很简单:只需要把它挂载到当前根文件系统中的一个目录
下即可。例如:
# mkdir /new-root
# mount -o ro /dev/hda1 /new-root
最终改变根文件系统由pivot_root()系统调用或者pivot_root工具来完成(参
考手册页pivot_root(8))。pivot_root可以把当前根文件系统转移到新根的一个目
录中,然后把指定的目录当作新的根。在调用pivot_root之前,必须先为旧根准备
目录,例如:
# cd /new-root
# mkdir initrd
# pivot_root . initrd
现在,linuxrc仍然可以访问旧的根文件系统。执行下列指令将完全结束与旧根
的联系:
# exec chroot . what-follows <dev/console >dev/console 2>&1
其中what-follows是新根下面的一个程序,比如/sbin/init。(注意,这时如
果没有/dev/console,系统不能启动)。同时,为了保持不同版本之间的兼容性,
需要特别注意下列事项:
O 在调用pivot_root之前,当前目录应该是新的根目录;
O 使用“.”(当前目录)作为pivot_root的第一个参数(新根),为旧根指定目录
的第二个参数也要用相对路径;
O chroot这个命令应该在新、旧根系统当中都能使用;
O 然后调用chroot转移根文件系统;
O 在exec命令中使用象 dev/console 这样的相对路径。
特别提示:让initrd文件系统的目录结构与新根文件系统的结构一致,有利于
转换过程的顺利。
这时,initrd可以卸载,RAM盘也可以释放:
# umount /initrd
# blockdev --flushbufs /dev/ram0
注意:如果linuxrc或者它调用的其他程序的执行因为某种原因中断,则会启用
旧的change_root机制。
5.3.5 使用场合
实现initrd的主要动机是允许系统安装时模块化的内核配置。这个过程大致如
下:
1) 系统可以用一个最小配置的内核从软盘或者其他存储媒体启动,然后载入initr
d系统;
2) /linuxrc来决定需要什么来进一步挂载真实的根文件系统(如设备类型、驱动程
序等)或者支持发行版媒体(如CD-ROM,网络,磁带等);
3) /linuxrc载入必须的内核模块;
4) /linuxrc建立并安装根文件系统;
5) /linuxrc调用pivot_root改变根文件系统,并通过chroot一个程序(继续安装过
程);
6) 安装引导器;
7) 引导器被配置成装入initrd以及相关的模块,建立起系统环境;
8) 现在,系统可以引导,并执行其他的安装任务。
Initrd的关键作用是能够多重配置一个正常操作的系统,而不需要用一个庞大
的内核,或者重新编译、连接内核。在制作Linux发行版(光盘等)、系统恢复盘等
方面有广泛的应用。
5.3.6 淘汰的根转换机制(change_root)
可以通过改写/proc/sys/kernel/real-root-dev这个文件的数字值来改变真实
的根设备。例如:
# echo 0x301 >/proc/sys/kernel/real-root-dev
但是这个机制已经被淘汰,虽然目前的内核仍然支持,不能保证以后的内核会
支持。所以不要使用。
5.4 系统初始化(init)
(参考资料[10])
5.4.1 init的任务
UNIX的init指的不是一个程序、而是一类程序。Init一般是指系统引导时执行
的第一个进程,也是唯一的进程。当内核完成计算机硬件的设置之后,就把控制权
交给init。内核只产生init这一个进程,而系统中其他所有进程都是由init负责产
生(spawn),主要包括各种系统服务进程,比如控制台的登录会话(getty)。主
要任务包括:
O 产生其他进程;
O 重新启动已经退出的进程;
O 负责清理系统中的“僵尸”进程(init是所有其他进程的祖先);
O 处理系统关机(stop所有进程,unmount文件系统);
内核并不关心拿什么来作为系统的init,可以是下列几种选择之一:
O SysVinit (作者:Miquel van Smoorenburg),或者
O simpleinit (作者:Peter Orbaek),或者
O 一个shell脚本,或者
O 嵌入式系统中你的应用程序。
不过路径名字必须是/sbin/init,/etc/init,或/bin/init(因为已经编译到
内核里面了)。如果这几个路径都找不到,系统就完蛋了。为了增加灵活性,内核
提供了命令行选项可以指定init路径:“init=”。
5.4.2 SysVinit
/etc/inittab
/etc/rc.d
大多数Linux发行版使用的init是SysVinit,也就是System V UNIX的实现。其
主要思想是规定了不同的“运行级别(runlevel)”。通过配置文件/etc/inittab
,定义了系统引导时做什么,进入或者切换到一个运行级别时做什么。配置文件每
一行的语法为:
id:runlevel:action:command
细节请参考手册页inittab(5)。
整个过程中用到的脚本都放在/etc/rc.d目录。
5.4.3 两种风格:Slackware vs. Debian
关于配置文件/etc/inittab和脚本/etc/rc.d的实现和组织主要有两种不同的风
格,其有代表性的发行版分别为Slackware和Debian(Redhat同Debian)。这两种风
格之间有几个明显的区别,可以比较容易的识别。
例如在/etc/inittab中,定义进入运行级别0时运行的脚本分别为/etc/rc.d/r
c.0和/etc/init.d/rc 0(在Redhat中/etc/init.d是指向/etc/rc.d/init.d的一个
符号链接,注意这里0是脚本rc的命令行参数)。因此,Slackware风格的/etc/rc.
d中应该是一系列相对独立的脚本,对应于配置文件中每个动作的定义(我没有见过
Slackware哦)。
在我们所熟悉的Redhat中,/etc/rc.d的组织要复杂的多,每个运行级别对应一
个子目录/etc/rc.d/rcX.d(X表示运行级别0~6),下面放的是一系列形如SXXfoo
和KXXbar(S表示Start某个服务,K表示Kill某个服务,XX是两位数字,决定了该脚
本执行的顺序)的符号链接,指向/etc/rc.d/init.d中的脚本,每个脚本对应一项
服务程序。
另外,还有两个重要脚本值得一提:/etc/rc.d/rc.sysinit是系统引导时首先
要执行的(完成系统初始化的各项工作),而/etc/rc.d/rc.local在最后执行(类
似DOS的autoexec.bat)。
读一下/etc/rc.d/rc.sysinit,/etc/rc.d/rc和/etc/rc.d/init.d/中的某个脚
本,就会对Redhat的启动过程和风格有比较清晰的了解。
据说Slackware风格比Debian的速度要快一些。可能是后者的组织结构比较复杂
的缘故吧。
5.5 PAM
(参考资料[11],http://www.kernel.org/pub/linux/libs/pam/,手册页pam
(8))
5.5.1 什么是PAM
PAM(Pluggable Authentication Modules)是为了解决计算机系统中用户认证
的问题而引入的一种实现方案。PAM的目标为:
O 将认证功能从应用中独立出来,单独进行模块化设计、实现和维护(而不是象以
前那样,将认证功能的代码跟应用程序编译在一块);
O 为这些认证模块建立标准API,以便各应用程序能方便的使用它们提供的各种功能
;
O 认证机制对其上层用户(包括应用程序和最终用户)是透明的。
PAM机制由SUN设计并首先在Solaris 2.3上部分实现,后来逐渐在其他UNIX平台
上实现,包括Linux(其实现称为Linux-PAM)。
5.5.2 PAM的结构
PAM采用分层的体系结构。最下面是模块层,负责实现具体的认证功能,包括帐
户管理(account)、口令鉴别(auth)、口令管理(password)和会话管理(ses
sion)4个模块。
应用接口层位于模块层之上,调用下层提供的服务,并向上(应用程序)隐藏
PAM实现的细节。
5.5.3 配置文件
/etc/pam.conf
/etc/pam.d (如果存在这个目录,则忽略/etc/pam.conf)
配置文件是应用接口层的另一个重要组件。其作用主要是为应用选定具体的鉴
别模块,模块间的组合以及规定模块的行为。
/etc/pam.conf的语法为每行5项(在Linux-PAM的手册页pam(8)中有更详细的描
述):
service type control module-path module-arguments
Service是服务的名字,比如login,su等。注意,/etc/pam.d目录下面有许多
配置文件,分别对应某项系统服务,文件名即等于service,因此每行只有4项。
Type为account,auth,password,session之一,即要使用的认证模块。每个
应用可以使用多个认证模块,或者说可以将模块“堆叠”使用,但是每一行只能定
义一个,因此一项服务可以有多行配置。
Control规定了如何处理模块认证失败或成功,可以是requisite、required、
sufficient或optional。
Module-path是PAM库文件的文件名。缺省路径是/lib/security/。
Module-arguments是可以传给模块的参数。
5.5.4 other
特别需要指出的是,有一个特殊的服务名字——other。如果没有明确指明应用
于某项服务的规则,就用other的定义来处理。因此,我们可以想到,系统中PAM的
最简配置为,只有/etc/pam.d/other这一个配置文件,即可处理所有的认证请求。
下面是我们用过的一个other配置文件的例子:
[xmdong@lips pam.d]$ cat other
#%PAM-1.0
auth required /lib/security/pam_unix.so
account required /lib/security/pam_unix.so
password required /lib/security/pam_unix.so
session required /lib/security/pam_unix.so
当然,这样处理对系统的安全性来说是不利的,最好还是给每项服务定义单独
的配置文件。这不过是一种简化问题的临时方案。
5.6 再论initrd
在前面的5.3节中,对initrd机制已经有了比较详细的讨论。准确的说,该部分
内容来自linux-2.4/Documentation/initrd.txt 文件。但是在实践中,我们发现其
中描述的有些方法并不适用。是新版本内核的变化造成的(内核实现与文档不同步
),还是2.4版内核的bug,不得而知。在我查证的过程中,看到网上有不少相关的
问题和讨论。遗憾的是至今我还不能确认到底是什么原因。
后来我又专门读了2.2版内核中所附带的 initrd.txt 文件。我感觉实际的情况
似乎更接近于这个老文档的描述。我还看到一个有关的patch文件,从该文件中可以
明确看到新的initrd.txt文档的改变。下面是该文件的开始部分(行首的-表示老文
档有而新文档中已经删除,+表示新文档中增加的内容)。
Using the initial RAM disk (initrd)
===================================
-Written 1996 by Werner Almesberger <almesber@lrc.epfl.ch> and
- Hans Lermen <lermen@elserv.ffm.fgan.de>
+Written 1996,2000 by Werner Almesberger <werner.almesberger@epfl.ch
> and
+ Hans Lermen <lermen@fgan.de>
-initrd adds the capability to load a RAM disk by the boot loader. T
his
-RAM disk can then be mounted as the root file system and programs c
an be
-run from it. Afterwards, a new root file system can be mounted from
a
-different device. The previous root (from initrd) is then either mo
ved
-to the directory /initrd or it is unmounted.
+initrd provides the capability to load a RAM disk by the boot loade
r.
+This RAM disk can then be mounted as the root file system and progr
ams
+can be run from it. Afterwards, a new root file system can be mount
ed
+from a different device. The previous root (from initrd) is then mo
ved
+to a directory and can be subsequently unmounted.
下面我试图将我遇到的问题和能够确认的东西记录一下。(后面我们分别称新
、老文档为initrd.txt-2000和initrd.txt-1996)
5.6.1 根到底在哪里
在配置使用initrd之前,首先你要清楚,系统正常运行时,根文件系统挂载在
什么设备上(也就是说,root device是什么——root在哪里?)。
我们知道,使用initrd机制的话,系统启动时先以RAM Disk(/dev/ram0)作为
根,然后通过某种方法将根转换到“真实的根(real-root-dev)”(之所以说rea
l,是与前面的RAM里临时的根比较而言)。一种常见的情形是启动时用initrd,然
后切换到一个硬盘分区,比如/dev/hda1。
事实上,这个根转换不是必须的。我们完全可以就把/dev/ram0作为我们真实的
根设备。这时,系统启动之后所有的操作都在RAM Disk中进行。当然这样做也有一
个明显的缺点,即无法保存数据。比如对系统配置进行的修改(象IP地址),重新
启动之后就丢掉了。如果要保存数据,就要想别的办法。
LiPS目前的做法就是这一种(没有根转换,initrd就是真实的根)。为了在重
新启动之后保持配置文件的修改,我们将目录/etc链接到了其他非易失存储器(例
如,一个硬盘分区,或者USB盘的分区)。
指定根设备是通过内核参数“root=”来完成的。以GRUB引导器的配置为例:
kernel /boot/bzImage ro ramdisk_size=65536 root=/dev/ram0
initrd /boot/initrd.img
其中 ramdisk_size=65536 说明初始RAM盘的大小为64MB,root=/dev/ram0 说
明根的位置。下面第二个例子来自一台在hda1安装了Redhat 9的机子:
kernel /boot/vmlinuz-2.4.20-8 ro root=/dev/hda1
initrd /boot/initrd-2.4.20-8.img
很明显,这里 root=/dev/hda1 指定了真实的根,其启动过程包含根的转换。
但是这里没有指定 ramdisk_size,因为initrd的缺省大小是4MB,而initrd-2.4.2
0-8.img比较小,所以用缺省值即可。
(我们多次提到root, root filesystem, root device, 自己体会吧。)
5.6.2 linuxrc
在initrd机制的设计中,初始RAM盘只是作为一个过渡性质的根设备,由/linu
xrc来完成操作环境准备(比如加载一些不常用的、没有编译进内核的驱动程序模块
)和根转换工作。
Linuxrc的执行是自动的,即内核挂载initrd盘作为根,如果根下面有这个文件
(也就是/linuxrc),就执行它。(如果没有呢?当然什么也不做。所以linuxrc也
并不是必不可少的。)
接下来说说linuxrc的内容。下面的例子是从initrd-2.4.20-8.img中释放出来
的:
[root@lips initrd]# cat linuxrc
#!/bin/nash
echo "Loading jbd.o module"
insmod /lib/jbd.o
echo "Loading ext3.o module"
insmod /lib/ext3.o
echo Mounting /proc filesystem
mount -t proc /proc /proc
echo Creating block devices
mkdevices /dev
echo Creating root device
mkrootdev /dev/root
echo 0x0100 > /proc/sys/kernel/real-root-dev
echo Mounting root filesystem
mount -o defaults --ro -t ext3 /dev/root /sysroot
pivot_root /sysroot /sysroot/initrd
umount /initrd/proc
该脚本中首先加载了支持ext3文件系统所需的模块,然后挂载真实的根文件系
统,并进行转换。特别需要注意的是,linuxrc脚本一般是用nash解释执行的,而不
是通常的bash。有关nash的详情请看手册页nash(8)。简单说,nash内置实现了多数
在执行linuxrc这个阶段可能用到的命令,比如mount, pivot_root, umount;而如
果使用bash,象mount等都是“外部命令”,就需要复制/bin/mount到initrd映像文
件中。实际上,在initrd-2.4.20-8.img的/bin中只有insmod和nash两个程序(mod
probe符号链接到insmod):
[root@lips initrd]# ls bin/
insmod modprobe nash
当我按照5.3.4节描述的方法测试linuxrc的时候发现,我的脚本头两句执行就
报错。
mount –o ro /dev/hda11 /sysroot
cd /sysroot
这里我的真实的根设备是/dev/hda11,/sysroot是initrd映像中存在的一个目
录。这两句完全是按照该文档的要求写的。后来经过多次试验才发现问题之所在。
第一,nash实现的mount命令语法与我们常用的/bin/mount并不完全兼容;第二,n
ash根本没有实现cd命令。
因此,可以推断initrd.txt-2000中讲linuxrc时所举例子不是用的nash。实际
上在该文档中根本没有指明用什么shell。再回头仔细看才发现,所有例句都是在r
oot的shell命令提示符“#”后面的,那么它用的是bash之类了。但是令人迷惑的是
,同样在该小节中稍后出现如下例句: # exec chroot . what-follows <dev
/console >dev/console 2>&1
为什么用exec执行chroot呢?chroot可以在命令行直接用啊。文档中讲到,wh
at-follows可以是/sbin/init。我用nash试了下面这个句子(nash中用exec执行外
部命令):
exec chroot . /sbin/init <dev/console >dev/console 2>&1
还是不行。错误信息:
Usage: init 0123456SsQqAaBbCcUu
开始我还以为后面那一串是个“magic number”,后来想以Usage开头应该是讲
使用这个命令的语法。看init(8)手册页,果然要求给一个参数,0~6当然是运行级
别啦,试了一个init 3也还是不行。尤其搞不明白后面那些个console什么意思。
5.6.3 根转换机制:新的?旧的?
前面我们多次提到了根的转换。在initrd.txt的两个版本中,分别描述了两种
根转换机制,其中2000版中称1996版的内容已经“过时”,不推荐使用。然而事实
上要麻烦的多,事情并不总是按照预期(如文档所述)运行。
为了弄清楚这个问题,我进行了一系列测试。环境是这样的:
O 使用initrd机制,已经准备好了根文件系统映像文件initrd.img,准备好了内核
bzImage;
O initrd.img的所有内容释放到一个硬盘分区/dev/hda11,ext2文件系统;
O 在/dev/hda11上面增加一个/boot目录,存放bzImage和initrd.img文件;
O 以/dev/ram0作为根启动,然后以某种机制转换到“真实的根”/dev/hda11。
系统的引导器是GRUB,安装在硬盘MBR,grub.conf位于/dev/hda1(也就是做测
试的实际的根文件系统)。按照上述环境配置,在grub.conf中增加如下内容:
title lips (testing ......)
root (hd0,10)
kernel /boot/bzImage ro root=/dev/hda11 ramdisk_size=65536
initrd /boot/initrd.img
下面我们分别使用不同的/linuxrc进行了测试,以确认到底哪种机制是可行的
。
第一,如果没有/linuxrc会怎么样呢?
结果是系统能够成功启动到以/dev/hda11作为根,并且把initrd(也就是旧根
文件系统)的内容移到了/initrd目录。这是符合initrd.txt-1996文档中第一段的
描述的。所以只要你使用了initrd,指定了新的根,并且准备好了/initrd目录(已
经在/dev/hda11创建了/initrd),内核可以自动完成根的转换工作。终端上面有如
下输出信息:
Trying to move old root to /initrd ... okay
这里有个小问题,即最后/initrd没有卸载,这意味着initrd所分配内存也没有
释放。因此umount /initrd不是内核自动完成的,而应该在/linuxrc中的某个位置
进行。如果linuxrc仅包含卸载/initrd的命令,如下:
#!/sbin/nash
umount /initrd
则执行情况是这样的:umount命令报错(返回错误码22),然后出现前面所示
Trying…的信息。结果跟前面是一样的。因为linuxrc执行在先,当时旧根还没有被
转移到/initrd中来。
如果没有/initrd目录会怎样?我们把/dev/hda11上面的/initrd目录删除,重
新启动(没有/linuxrc),结果是可以启动到以/dev/hda11为根,且看不到原来的
initrd。该过程中出现如下信息:
Trying to move old root to /initrd ... failed
Unmouting old root
Trying to free ramdisk memory ... okey
第二,pivot_root可以用,但是chroot不能用。
pivot_root是在initrd.txt-2000文档中引入的新机制。在测试pivot_root的过
程中,除了在nash中不能用cd命令造成的困惑(这个问题前面已经说明)之外,使
用pivot_root基本上是成功的。/linuxrc脚本的主要内容如下:
#!/sbin/nash
mount -o defaults --ro -t ext2 /dev/hda11 /sysroot
pivot_root /sysroot /sysroot/initrd
但是,用这个脚本不能启动,造成系统挂起:
VFS: Cannot open root device “hda11” or 03:0b
Please append a correct “root=” boot option
Kernel panic: VFS: Unable to mount root fs on 03:0b
如果按照initrd.txt-2000的指导,pivot_root之后应该执行chroot,但是我始
终没有办法使chroot成功执行(有关情况前面已经说明)。不过可以肯定的是,pi
vot_root执行成功之后,当前目录就是新的根目录。
第三,/proc/sys/kernel/real-root-dev
initrd.txt的新版本中最后一节专门指出,向/proc/sys/kernel/real-root-d
ev文件中echo数字的方法(称为change_root机制)是过时的。然而令人迷惑的是,
Redhat 9的缺省安装产生的initrd-2.4.20-8.img中(前面已经列出了其/linuxrc文
件内容),同时使用了pivot_root和change_root机制。
测试下列/linuxrc脚本,系统可以启动到/dev/hda11,但是/initrd没有卸载。
如果在脚本末尾umount /initrd,会返回错误码16。
#!/sbin/nash
mount -t proc /proc /proc
mkrootdev /dev/root
echo 0x0100 > /proc/sys/kernel/real-root-dev
#echo 0x030b > /proc/sys/kernel/real-root-dev
mount -o defaults --ro -t ext2 /dev/hda11 /sysroot
pivot_root /sysroot /sysroot/initrd
umount /initrd/proc
虽然这样可以启动,但是有关real-root-dev还有几个没有弄清楚的问题:
real-root-dev的值到底是什么?
目前我还没有看到解释real-root-dev的值是什么东西的正式文档,根据情况判
断的话,我认为应该是该设备的“major-minor”(主、次设备号)。
为什么用0100而不是030b?
如果上述判断准确的话,那么写入0100就表示/dev/ram0。这就奇怪了,“真实
的根设备”应该是/dev/hda11呀,那么是不是应该写030b呢?但是测试发现用030b
同样会导致不能启动。
Kernel panic: VFS: Unable to mount root fs on 03:0b
5.7 提示
5.7.1 文件系统目录层次结构
根文件系统的目录结构最好符合“Filesystem Hierarchy Standard(文件系统
层次标准)”[7],而不能自己随意创建。
顺便说一下,象这种情况:/bin, /sbin, /usr/bin, /usr/sbin 这4个目录都
可以放应用程序,具体放在哪个目录,则要根据该应用程序在系统中的作用来确定
。一般地,系统的关键应用程序放在/bin,只能给超级用户root访问的关键应用程
序放在/sbin,相比之下比较次要的应用程序和root应用程序分别放在/usr/bin和/
usr/sbin。
5.7.2 mklips.sh(制作LiPS的脚本)
复制文件的工作全部可以手工进行,但是做一个shell脚本来完成所有工作显然
是一个更好的办法。
在我们的项目中,制作LiPS(也就是完成裁减Linux)的工作,除了编译内核之
外,都可以通过mklips.sh脚本完成。
5.7.3 lips.conf(LiPS配置文件)
lips.conf是LiPS的配置文件,由mklips.sh脚本使用,主要用来控制所制作的
LiPS系统包括哪些可选的模块,例如:
INCLUDE_APACHE=no
INCLUDE_IPTABLES=no
INCLUDE_PROFTPD=yes
INCLUDE_NFSD=no
INCLUDE_SAMBA=yes
通过一系列的INCLUDE_?=yes或者no,可以实现LIPS的模块化。在mklips.sh脚
本中,判断每个INCLUDE的值,来确定是否复制某个模块相关的文件。
配置文件中还可以包括其他可配置的信息。
5.7.4 cp –dpR
复制文件请使用“cp –dpR”命令,可以保留文件的属性(特别是对于设备节
点和符号连接等特殊文件),还可以复制整个目录。
5.7.5 如何判断一个程序依赖哪些文件
除了库文件外,关键是找出相关的配置文件。man手册页是判断应用程序配置文
件的重要依据。我们以安装Apache Server为例来说明判断复制哪些文件的过程。
首先确定其可执行文件是/usr/sbin/httpd。然后看httpd的手册页,即
[root@lips lips]# man httpd
httpd(8)
httpd(8)
NAME
httpd - Apache hypertext transfer protocol server
…………(略)
FILES
/etc/httpd/conf/httpd.conf
/etc/mime.types
/etc/httpd/conf/magic
/var/log/httpd/error_log
/var/log/httpd/access_log
/var/run/httpd.pid
February 1997
httpd(8)
(END)
这时关键看最后一段FILES,列出了有关的配置文件、日志文件和进程文件,那
么我们就知道了httpd要在目标系统中运行,这几个配置文件是必不可少的。
把httpd和用ldd找出的库文件以及上述配置文件复制到目标系统,运行httpd试
一下,看看是否正常。一般情况下应该没有问题。但是httpd比较特殊,除了ldd找
出的库文件外,它还需要很多可加载的模块(库)。怎么发现呢?重要的一点是看
出错信息(有时候还要查看日志中的信息,/var/log/messages或者应用程序日志)
,看httpd报告缺少什么;还可以看看相关的配置文件httpd.conf,也能找到有用的
信息。然后我们发现需要/usr/lib/httpd这个目录下的库,把它也复制过去即可。
5.7.6 带库文件复制——lcp()
在复制可执行文件(软件)的过程中,如果没有正确复制相关的库文件,该软
件肯定不能在目标系统中正常运行。而通过ldd命令手工确定每个软件的库文件,是
一个繁琐且费时的任务。另一个隐含的缺陷是,当你不再需要某个软件时,删掉可
执行文件之后,相应的库文件是否需要删除呢?如果删除了,而该库文件是其他软
件也需要的,会导致其他软件不能用;如果不删除,就有可能留一些垃圾库文件在
目标根文件系统中。
因此,最好是能够保证每次复制可执行文件的同时自动复制相关联的库文件。
我写了一个脚本函数 lcp() 来解决这个问题。语法和主要代码如下(以#开头的行
为注释):
# lcp BIN BINPATH LIBPATH
# BIN:可执行文件路径
# BINPATH:可执行文件的目标路径(复制到哪里)
# LIBPATH:相关库文件的目标路径
lcp()
# run()是另一个函数,执行它后面的shell命令,并在遇到错误时报错、写错
误日志
# 首先复制BIN到BINPATH
run "$CP $BIN $BINPATH"
# 使用ldd找出与BIN相关的库文件,把ldd的输出送给LDDSTR
LDDSTR=`ldd $BIN`
# 循环检查LDDSTR字符串的每一项
for STR in $LDDSTR; do
# 判断出库文件路径
LIB=`echo $STR | grep "/lib/" `
if [ -z "$LIB" ]; then
continue
fi
# 复制LIB到LIBPATH,除非要复制的库文件在目标路径中已经存在
LIBNAME=`basename $LIB`
if [ ! -f "$LIBPATH/$LIBNAME" ]; then
run "$CP $LIB $LIBPATH"
run "cp $LIB $LIBPATH"
else
log "Warning: File "$LIBPATH/$LIBNAME" exis
t."
fi
done
}
5.7.7 库文件的命名
(参考资料[9]之6.2节)
我们这里所说的“库”都是指的GNU C Library,也就是glibc。
在/lib下面可以看到很多库文件,主要分为4类:
O 实际的共享库(Actual shared libraries)
文件名格式为 libLIBRARY_NAME-GLIBC_VERSION.so,其中LIBRARY_NAME是库的真正
名称,GLIBC_VERSION是glibc包的版本号。比如glibc 2.2.3版math(数学)库的文
件名为libm-2.2.3.so。
O 主修订版本符号链接(Major revision version symbolic links)
库的主修订版本号与glibc版本号不同。比如,glibc 2.2.3的C共享库libc-2.2.3的
主修订版本号为6,libdl-2.2.3为2。主修订版本符号链接的命名格式为 :libLIB
RARY_NAME.so.MAJOR_REVISION_VERSION,那么C共享库的符号链接即 libc.so.6。
一个程序连接到一个库之后,它在运行时访问的就是这个符号链接。
O 版本无关的符号链接(Version-independent symbolic links to the major re
vision version symbolic links)
这些符号链接的作用是为用到某个库的所有程序提供一个统一的入口,而不管实际
上glibc的版本或者该库的主修订版本。其命名格式为 libLIBRARY_NAME.so。比如
,libm.so链接到libm.so.6,libm.so.6又链接到libm-2.2.3.so。唯一的例外是li
bc.so,是个脚本。连接一个程序时用的是这个符号链接。
O 静态库文件(Static library archives)
这些库是应用程序静态连接用的。格式为 libLIBRARY_NAME.a.