4.5 将硬盘驱动编译为模块
initramfs的重要作用之一就是允许内核将保存根文件系统的存储设备的驱动不再编译进内核。上一节,我们体验了基本的initramfs,这一节,我们就将硬盘驱动编译为模块。
4.5.1 配置devtmpfs
既然提到设备,而且Linux将设备也抽象为文件,这里就不得不讨论一下设备文件或者说设备节点。通常情况下,某些需要从用户空间访问的设备都会在文件系统中建立一个设备文件,作为用户空间访问设备的接口。得益于Linux中虚拟文件系统的设计,用户空间的程序可以像访问普通文件一样,使用标准的文件访问接口实现与设备的交互。
根据FHS的规定,设备文件存放在/dev目录下。在Linux系统的早期,设备文件是静态创建的,所有的设备节点是手动、事先创建的。笔者还记得在早期制作Linux发行版时,安装系统时,需要静态安装大量的设备文件,把所有可能的设备节点一并创建出来。但是,这样带来的一个问题就是,随着设备的种类越来越多,这个目录会越来越大,对于某一台具体的机器来说,dev目录下充斥着大量无用的设备文件,因为某些设备在某些机器上根本就不存在。而且,这种方法会逐渐耗尽设备号,虽然可以通过扩展设备号的位数来增加设备号,但终究不是长久之计。
鉴于静态创建设备文件的种种问题,开发人员开发了devfs。devfs虽然解决了按需创建设备节点的机制,但是还是有很多问题,比如设备文件的名称依然由驱动开发人员在代码中指定,而不能由系统管理员指定。因此,后来又出现了udev,使得设备命名策略、权限控制等都在用户空间完成。如此,设备文件不再是静态创建,而是由udev根据内核检测到的实际连接的设备,创建相应的设备文件。
对于动态创建设备文件,推荐在/dev目录下挂载一个基于内存的文件系统。基于内存的文件系统会完全驻留在RAM中,读写可以瞬间完成。除了性能优势之外,基于内存的文件系统的另外一个特点就是没有持久性,基于内存的文件系统中的数据在系统重新启动之后不会保留。这看起来可能不像是个积极因素,然而,对于动态创建设备节点这种情况来说,这实际上是一个优势。在系统关闭后,所有的设备节点无须保留,系统重启后,udev将根据内核检测到的实际设备创建设备文件。
Linux从2.6.18开始采用udev,/dev目录使用了基于内存的文件系统tmpfs管理设备文件。
2009年初,开发人员又提出了devtmpfs,并在同年年底被Linux 2.6.32正式收录。内核引导时,devtmpfs将所有注册的设备在devtmpfs中建立相应的设备文件,一旦进入用户空间,在启动udev前,就可以将devtmpfs挂载到/dev目录下。也就是说,在启动udev前,devtmpfs中已经建立了初步的设备文件,一般启动程序不必再等待udev建立设备节点,甚至在某些嵌入式系统上,不再需要udev创建设备节点,因为这个基本的/dev已经足够,从而缩短了系统的启动时间。同rootfs类似,devtmpfs也不是新设计的文件系统,如果内核配置支持tmpfs,那么其就是tmpfs;否则,devtmpfs就是ramfs,只不过换了一个名字而已。
下面我们就实际体验一下devtmpfs。为此,我们需要在initramfs中安装工具ls和mount。
工具ls在coreutils中,所以首先编译安装coreutils:
下面使用ldd检查ls依赖的库:
根据ldd的输出可见,ls依赖的库除了librt和libpthread尚未安装到initramfs中外,其余已经安装,所以我们将ls以及librt和libpthread复制到initramfs中:
工具mount在软件包util-linux中,我们首先来编译这个软件包:
下面使用ldd检查mount依赖的库:
根据ldd的输出可见,mount依赖libmount、libblkid、libuuid以及libc。libc已经安装到了initramfs中,我们将mount和其余几个库复制到initramfs:
重新压缩initramfs,并将其保存到/vita/sysroot/boot/目录下。接下来,我们准备支持devtmpfs的内核,配置步骤如下:
1)执行make menuconfig,出现如图4-9所示的界面。
图 4-9 配置内核支持devtmpfs(1)
2)在图4-9中,选择"Device Drivers",出现如图4-10所示的界面。
图 4-10 配置内核支持devtmpfs(2)
3)在图4-10中,选择"Generic Driver Options",出现如图4-11所示的界面。
图 4-11 配置内核支持devtmpfs(3)
4)在图4-11中,选中"Maintain a devtmpfs filesystem to mount at/dev"。
编译内核,并将内核与前面准备好的initramfs复制到虚拟机的目标系统上,然后重启进入vita系统。使用ls列出vita系统/dev下的设备文件,如图4-12所示。
图 4-12 未挂载任何文件系统的/dev目录
根据图4-12可见,/dev目录下包含一个设备节点console。但是我们的initramfs中并没有包含这个设备节点,那么这个设备节点是谁创建的呢?大家一定还记得我们前面讨论过的内置的initramfs吧?没错,这个console就是由内置的initramfs创建的。
接下来我们使用下面的命令将ramfs挂载到/dev目录下。
因为ramfs只是一个基于内存的文件系统,与设备无关,所以mount命令中的"device"参数可以使用任意字串描述。习惯上,对于这类没有具体设备的,一般使用字串"none"表示。但是mount命令不推荐这样使用,因为在某些情况下,某些提示很容易让用户费解,比如"mount:none already mounted"。这里我们暂时使用"none",在最终系统中我们使用"udev",表示该目录下的节点是由udev创建的。默认情况下,mount命令会在文件/etc/mtab中维护一份当前已挂载的文件系统列表,因为我们的initramfs中没有创建/etc/mtab文件,initramfs中也不需要,因此使用参数"-n"告诉mount不需维护这份列表。
挂载完成后,我们查看/dev下的设备文件。情况非常糟糕,/dev下挂载了一个空的ramfs文件系统,原有的console设备节点也被覆盖了,如图4-13所示。
图 4-13 挂载ramfs文件系统的/dev目录
接下来我们使用下面的命令将devtmpfs挂载到/dev目录下:
挂载完成后,我们再次查看/dev下的设备文件。可以看到,devtmpfs下面已经建立了若干设备节点,如图4-14所示。
图 4-14 挂载devtmpfs后的/dev目录
既然devtmpfs有这么多的好处,/dev目录当然要使用devtmpfs文件系统了。因此,按照下面的脚本修改initramfs中的init文件: