LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 15955|回复: 50

精通initramfs构建step by step

[复制链接]
发表于 2008-10-10 22:40:52 | 显示全部楼层 |阅读模式
最近在研究试验initramfs,准备用实现把Windows下构建的CLFS最小系统自动部署到裸机上,无需额外的LiveCD等Linux系统的帮助。同时希望通过一系列的研究最终能够精通initramfs的构建方法和基本原理,而试验因为是一步一步进行试验,所以叫step by step。
已经写了两节内容,先放在这里共享给大家,也希望能得到大家的批评指正。后续会逐步把后面的内容放上来,预计会有十几节吧。

全文已经贴完,为了方便大家阅读,我整理出了总目录:
(一)hello world
编写一个最简单的initramfs
(二)initramfs的前世今生
前面讲initramfs的概念时,提到了rootfs,那么rootfs又是什么,它与initramfs又有些什么瓜葛呢?
(三)busybox
我们开始利用initramfs做些有意思的工作了。
(四)mini linux
busybox系统做好了,我们就以它为基础在initramfs上构建一个可运行的mini Linux系统吧。
(五)initrd
目前为止,我们的initramfs都由内核编译生成的,并链接到内核中。其实我们也可以用cpio命令生成单独的initramfs,与内核编译脱钩,在内核运行时以initrd的形式加载到内核,以增加灵活性。
(六)switch_root
除了基于initramfs的系统(如第四节的mini linux),通常initramfs都是为安装最终的根文件系统做准备工作,它的最后一步需要安装最终的根文件系统,然后切换到新根文件系统上去。
(七)modules
initramfs的最重要的功能就是包含大量的硬盘驱动和文件系统驱动,而不需要把所有的驱动程序编译进内核,减少内核大小。initramfs负责在安装实际根文件系统前根据具体的文件系统设备情况加载合适的驱动到内核中,使系统能够正常安装实际的根文件系统。如何在initramfs中增加驱动程序模块呢?
(八)coldplug
如果要做一个支持各种硬件配置的initramfs,要借助著名的udev来自动根据内核侦测到硬件类型来加载相应的驱动程序,也就是系统的coldplug。在initramfs中如何使用udev做coldplug呢?
(九)内核编译时构建initramfs补遗
前一节中,initramfs构建中最困难的识别并自动加载硬件设备的驱动模块的问题已经解决了,我们稍稍停顿一下,回头再看看在内核编译时构建initramfs的另外两种方式。
(十)uclibc
前面编译busybox时,都是连接到了glibc库。但是glibc库比较大,而且busybox不能静态连接(有资料讲可以通过修改busybox源码来静态连接glibc),在initramfs容量受限或需要静态连接的情况下,使用uclibc库可能是个很好的选择。
(十一)klibc
截至到目前,我们都是基于busybox来构建initramfs。还有一种广泛使用的initramfs构建方式是使用klibc软件包,包括一个小巧的C库和常用命令和工具。Debian的initramfs构建工具就综合应用了busybox、udev、klibc。
(十二)大结局 -测试一下
到现在为止,我们已经掌握了initramfs的构建方法,该到大结局了。在大结局里,读者可应用前面step中介绍的方法,尝试分析一下Debian的initramfs,看看是否已精通了initramfs的构建方法。


总目录的后续的更新版本可以在这里找到:http://blog.ccidnet.com/blog.php ... &itemid=1100709
 楼主| 发表于 2008-10-10 22:42:45 | 显示全部楼层

(一)hello world

一、initramfs是什么
在2.6版本的linux内核中,都包含一个压缩过的cpio格式的打包文件。当内核启动时,会从这个打包文件中导出文件到内核的rootfs文件系统,然后内核检查rootfs中是否包含有init文件,如果有则执行它,作为PID为1的第一个进程。这个init进程负责启动系统后续的工作,包括定位、挂载“真正的”根文件系统设备(如果有的话)。如果内核没有在rootfs中找到init文件,则内核会按以前版本的方式定位、挂载根分区,然后执行/sbin/init程序完成系统的后续初始化工作。
这个压缩过的cpio格式的打包文件就是initramfs。编译2.6版本的linux内核时,编译系统总会创建initramfs,然后把它与编译好的内核连接在一起。内核源代码树中的usr目录就是专门用于构建内核中的initramfs的,其中的initramfs_data.cpio.gz文件就是initramfs。缺省情况下,initramfs是空的,X86架构下的文件大小是134个字节。

二、构建第一个initramfs:hello world
从C语言开始,学习计算机编程语言的第一个程序几乎都是hello world,因此我们也构建一个最简单的hello world式的initramfs,以说明initramfs的基本构建方法。
initramfs的灵魂是init文件(或者叫程序,因为它会被内核第一个执行),我们先写一个简单的init程序,它会在内核的console中打印出经典的hello world信息。
hello.c:
#include <stdio.h>
#include <unistd.h>
int main(int argc,char argv[])
{
printf("hello world, from initramfs.\n");
sleep(9999999);
return 0;
}

其中的sleep()函数语句是为了避免执行时内核很快打出panic的信息,并非功能上的需要。

接着把hello.c编译成静态连接程序:
gcc -o hello_static -static -s hello.c
命令行中的-s参数表示编译后的程序不包含调试定位信息,目的是减少编译出来的程序文件的大小。
再创建一个initramfs的构建源文件目录image,把hello_static程序拷入这个目录,并改名为init。
在image目录下,创建一个dev/console的设备文件,否init程序无法在内核console中输出信息:
mknod -m 600 dev/console c 5 1
注意,执行这个命令需要有root权限。

好了,现在可以设置内核配置参数,进行initramfs的构建了:
在general setup配置目录下的initramfs sources配置项下输入image的路径名,比如我的路径就是/home/wyk/initramfs-test/image。因为我们的init程序是ELF格式的,所以内核需要支持ELF的可执行文件,否则启动这个init程序会失败。在内核的 Executable file formats配置目录下,选择 kernel support for ELF binaries,则可使内核支持ELF格式的可执行文件。其他内核配置参数根据实际需要设置即可,不过,为了减少内核编译时间,可参考这篇文章http://linuxman.blog.ccidnet.com/blog-htm-do-showone-uid-60710-type-blog-itemid-293122.html设置一个最简单的内核配置。
内核配置参数设置完成后,按常规的内核编译方法进行编译,initramfs就自动连接到编译好的内核映像文件中了。

三、试验环境搭建
试验initramfs需要经常重启系统,所以使用CPU模拟器是不错的选择。我们可以选用qemu,它支持直接启动linux内核,无需在模拟器中安装OS。从方便使用的角度考虑,我们采用qemu launcher设置qemu的各项参数,它的安装可参考http://linuxman.blog.ccidnet.com/blog-htm-do-showone-uid-60710-type-blog-itemid-612280.html
在qemu launcher的linux配置标签中,打勾直接启动linux,然后在下面的文本框中填上刚才编译好的内核映像文件的路径名。因为qemu的运行还需要设置硬盘映像文件,所以还需要在左边的配置标签中新建一个硬盘映像文件,但实际上我们并不使用硬盘。
配置好qemu的参数后,点击launcher按钮,内核就开始在qemu中运行了。内核输出一堆内核运行信息后,最后打出了
hello world, from initramfs.
哈哈,我们构建的initramfs已经能够正常工作了!


---下节预告---

前面讲initramfs的概念时,提到了rootfs,那么rootfs又是什么,它与initramfs又有些什么瓜葛呢?这就需要看看下一个step:
精通initramfs构建step by step (二):initramfs的前世今生
回复 支持 反对

使用道具 举报

 楼主| 发表于 2008-10-10 22:44:20 | 显示全部楼层

(二)initramfs的前世今生

四、什么是rootfs和ramfs
所有的2.6版本linux内核都有一个特殊的文件系统rootfs,是内核启动的初始始根文件系统,initramfs的文件会复制到rootfs。如果把initramfs比作种子,那么rootfs就是它生长的土壤。大部分linux系统正常运行后都会安装另外的文件系统,然后忽略rootfs。
rootfs是ramfs文件系统的一个特殊实例。ramfs是一种非常简单的文件系统,是基于内存的文件系统。ramfs文件系统没有容量大小的限制,它可以根据需要动态增加容量。
ramfs直接利用了内核的磁盘高速缓存机制。所有的文件的读写数据都会在内存中做高速缓存(cache),当系统再次使用文件数据时,可以直接从内存中读写,以提供系统的I/O性能。高速缓存中的写入数据会在适当的时候回写到对应的文件系统设备(如磁盘等)中,这时它的状态就标识为clean,这样系统在必要时可以释放掉这些内存。ramfs没有对应文件系统设备,所以它的数据永远都不会回写回去,也就不会标识为clean,因此系统也永远不会释放ramfs所占用的内存。
因为ramfs直接使用了内核已有的磁盘高速缓存机制,所以它的实现代码非常小。也由于这个原因,ramfs特性不能通过内核配置参数删除,它是内核的天然特性。

五、ramfs不是ramdisk
ramdisk是在一块内存区域中创建的块设备,用于存放文件系统。ramdisk的容量是固定的,不能象ramfs一样动态增长。ramdisk需要内核的文件系统驱动程序(如ext2)来操作其上的数据,而ramfs则是内核的天然特性,无需额外的驱动程序。ramdisk也象其他文件系统设备一样,需要在块设备和内存中的磁盘高速缓存之间复制数据,而这种数据复制实际不必要的。

六、从ramfs派生的文件系统tmpfs
ramfs的一个缺点是它可能不停的动态增长直到耗尽系统的全部内存,所以只有root或授权用户允许使用ramfs。为了解决这个问题,从ramfs派生出了tmpfs文件系统,增加了容量大小的限制,而且允许把数据写入交换分区。由于增加了这两个特性,所以tmpfs允许普通用户使用。
关于tmpfs文件系统更多的信息,可以看内核源码中的 Documentation/filesystems/tmpfs.txt 文档。

综上所述,initramfs是一种ramfs文件系统,在内核启动完成后把它复制到rootfs中,作为内核初始的根文件系统,它的任务是挂载系统真正的根文件系统。这就是initramfs的前世今生。


---下节预告---

这节的内容都是一些概念,可能比较枯燥。好的,从下一节开始,我们开始利用initramfs做些有意思的工作了。请继续看看下一个step:
精通initramfs构建step by step (三):busybox
回复 支持 反对

使用道具 举报

发表于 2008-10-11 07:21:44 | 显示全部楼层
非常好呀,但好像还未完的吧

Btw,春风兄过去有篇 initramfs 的实际应用,连结如下:

http://www.linuxsir.cn/bbs/showthread.php?t=309927
回复 支持 反对

使用道具 举报

发表于 2008-10-11 16:15:46 | 显示全部楼层
非常好的文章,期待后续...
回复 支持 反对

使用道具 举报

 楼主| 发表于 2008-10-11 23:48:21 | 显示全部楼层

第3节写好了,贴上来——精通initramfs构建step by step (三):busybox

七、什么是busybox
busybox号称是嵌入式Linux中的瑞士军刀——小巧、功能齐全。它把许多常用的Linux命令都集成到一个单一的可执行程序中,只用这一个可执行程序(即busybox)加上Linux内核就可以构建一个基本的Linux系统。busybox程序非常小巧,包含全部命令可执行文件大小也只有750多K。busybox是完全模块化的,可以很容易地在编译时增加、删除其中包含的命令。
由于busybox的这些特点,它广泛应用于LiveCD、应急修复盘、安装盘等系统中。我们也是以它为基础,构建initramfs。

八、busybox的配置、编译和安装
(1)去http://busybox.net 去下载最新的源码,解压展开。
(2)用
make menuconfig
命令启动配置界面配置,配置busybox的特性、选择要包含在busybox的命令(busybox称为applet);
也可以用
make defconfig
命令做缺省配置,包含全部的applet。
另外两个配置命令是
make allyesconfig——最大配置
make allnoconfig——最小配置
它们和make defconfig命令都可以用来作为自定义配置的初始配置,然后再用make menuconfing命令做定制化配置。
为了简单,我们用make defconfig做缺省配置。
(3)用
make
命令编译busybox软件。
(4)用
make CONFIG_PREFIX=<安装目录> install
命令安装。如果在命令行中省略CONFIG_PREFIX变量的赋值,则会安装缺省值 ./_install 目录下。CONFIG_PREFIX可以在make menuconfig的配置界面中修改。
我们用make CONFIG_PREFIX=~/initramfs-test/image 命令把busybox安装到initramfs的构建目录中。
(5)缺省配置下,busybox动态链接到glibc,所以要把它用到的动态库复制到initramfs的构建目录中。用ldd命令查看busybox用到了哪些动态库文件及相应的文件路径,然后把它们复制到相应的目录下即可。
我们编译的busybox需要向image/lib目录下复制
ld-linux.so.2
libc.so.6
libcrypt.so.1
libm.so.6
动态库文件。

九、在image下创建必要的目录和设备文件
(1)在imgae目录下创建
proc , sys , etc ,mnt
四个目录
(2)hello world 已经创建了console 设备文件,我们再用
mknod -m 600 dev/null c 1 3
命令创建另一个基本的设备文件。

十、试验一下
busybox的构建和准备工作做完了,我们试验一下吧:
在image目录下以root用户权限——
(1)用
mount -vt proc proc =proc
mount -vt sysfs sysfs =sys

命令安装内核虚拟文件系统
(2)用
mount -v -o bind /dev dev
命令绑定/dev的设备文件到image/dev
(3)用
chroot . /bin/sh
命令进入busybox的环境。出现shell的命令提示符,可以试着输入几个命令,看看执行结果。例如,输入 fdisk -l 命令看看是否能显示硬盘的分区。



---下节预告---

busybox系统做好了,我们就以它为基础在initramfs上构建一个可运行的mini Linux系统吧。请看下一个step:
精通initramfs构建step by step (四):mini linux
回复 支持 反对

使用道具 举报

发表于 2008-10-12 04:10:34 | 显示全部楼层
精华一下,很不错,很详细。
回复 支持 反对

使用道具 举报

发表于 2008-10-13 10:50:11 | 显示全部楼层
好文,收藏啦
回复 支持 反对

使用道具 举报

发表于 2008-10-15 21:54:43 | 显示全部楼层
不错!希望楼主继续写下去!
回复 支持 反对

使用道具 举报

 楼主| 发表于 2008-10-16 00:55:34 | 显示全部楼层

(四):mini linux

十一、自动生成/dev下的设备文件
上节用chroot方法试验busybox时,为了简单,是用“绑定”的方式把主机的/dev中的设备文件映射到image目录下的dev目录。在initramfs上,这种方法显然不能使用。
生成系统的设备文件,现在通常都是用udev动态生成,而initramfs为了做到通用,动态生成的要求是必须的。在busybox中有一个mdev命令,就是用来动态生成设备文件,填充到/dev目录的。
在系统启动时,用
mdev -s
命令可以根据内核的sysfs文件系统在/dev目录中自动生成相应的设备文件。命令执行前,需要先挂载内核的proc和sysfs虚拟文件系统。

十二、初始身手
解决了自动生成设备文件的问题后,我们可以试着做一个最简单的可运行的linux系统了:
(1)在image目录下写一个最简单的init脚本。
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mdev -s
/bin/sh
(2)为init脚本设置可执行权限,否则内核不会去执行它。
chmod +x init
(3)有些busybox配置中,mdev命令需要读取/etc/mdev.conf文件,为了避免出错信息,我们创建一个空文件。
touch etc/mdev.conf
(4)在内核源码目录下,执行
make
命令,重新编译内核,生成新的initramfs。

好了,在QEMU模拟环境下启动这个新的内核,系统初始化后,会进入SHELL环境。在这个SHELL环境下,试验一些常用命令,看看是否可以正常运行。

十三、can't access tty
上一步创建的简单linux系统在进入SHELL环境时,会打出下面这一句出错信息:
/bin/sh: can't access tty; job controll off
虽然不影响使用,但终究不够完美。
产生这个错误的原因是我们的SHELL是直接运行在内核的console上的,而console是不能提供控制终端(terminal)功能的,所以必须把SHELL运行在tty设备上,才能消除这个错误。解决问题的办法是使用正规init机制,在执行SHELL前打开tty设备。
另外,这个简单系统的reboot、halt等命令是不起作用的,也必须通过init方式解决。

十四、busybox的缺省init模式
busybox支持init功能,当系统没有/etc/inittab文件时,它有一套缺省的模式,按下面配置执行:
::sysinit:/etc/init.d/rcS
::askfirst:/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init

如果busybox检测到/dev/console不是串口控制台,init还要执行下面的动作:
tty2::askfirst:/bin/sh
tty3::askfirst:/bin/sh
tty4::askfirst:/bin/sh

我们试试这种模式是否可以解决我们的问题。
(1)写/etc/init.d/rcS脚本
这个脚本实际是要执行系统的初始化操作。我们把前面的init脚本改造一下,将最后的/bin/sh命令删除,然后移到 etc/init.d目录下,改名为rcS。
(2)initramfs不需要linuxrc,而且如果没有init文件,内核就不认为它是一个有效的initramfs,因而不安装它,导致内核panic。于是,我们在image目录下,把busybox安装的linuxrc改名为init
mv linuxrc init
(3)重新编译内核,生成新的initramfs
(4)用QEMU试验一下新编译的内核。系统启动后,会打出一句话“please press Enter to active this console”——感觉还不错。但是按下回车键后,系统依然会打出错误信息“-/bin/sh:
can't access tty; job controll off ”。用tty命令看看当前的终端设备文件名:
# tty
/dev/console
它还是console,不是tty设备,所以问题没有解决。不过,reboot和halt命令倒是可以正常工作了。

经过验证,busybox的缺省init模式无法满足我们的要求,我们还是要写inittab,定制自己的init初始化流程。

十五、busybox的inittab文件格式说明
要写自己的inittab,需要理解busybox的inittab文件格式。
busybox的inittab文件与通常的inittab不同,它没有runlevel的概念,语句功能上也有限制。inittab语句的标准格式是
<id>:<runlevels>:<action>:<process>
各字段的含义如下
<id>:
id字段与通常的inittab中的含义不同,它代表的是这个语句中process执行所在的tty设备,内容就是/dev目录中tty设备的文件名。由于是运行process的tty设备的文件名,所以也不能象通常的inittab那样要求每条语句id的值唯一。
<runlevels>:
busybox不支持runlevel,所以此字段完全被忽略。
<action>:
为下列这些值之一:
sysinit, respawn, askfirst, wait,once, restart, ctrlaltdel, shutdown
其含义与通常的inittab的定义相同。特别提一下askfirst,它的含义与respawn相同,只是在运行process前,会打出一句话“please press Enter to active this console”,然后等用户在终端上敲入回车键后才运行process。
<process>
指定要运行的process的命令行。

十六、写mini linux的inittab
理解了busybox的inittab格式,我们就可以写mini linux的inittab:
::sysinit:/etc/init.d/rcS
tty1::askfirst:/bin/sh
tty2::askfirst:/bin/sh
tty3::askfirst:/bin/sh
tty4::askfirst:/bin/sh
tty5::askfirst:/bin/sh
tty6::askfirst:/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r

把这个文件放到image的etc目录下。为了执行reboot命令时避免提示找不到/etc/fstab文件,我们再在etc目录下创建一个空文件
touch fstab

做好了这些,就可以重新编译内核,生成新的initramfs了。在QEMU试验环境下验证新生成的mini linux,系统运行正常,而且象通常的linux系统一样,用ALT+F1~F6键可以在6个终端间切换。


---下节预告---

目前为止,我们的initramfs都由内核编译生成的,并链接到内核中。其实我们也可以用cpio命令生成单独的initramfs,与内核编译脱钩,在内核运行时以initrd的形式加载到内核,以增加灵活性。请看下一个step:
精通initramfs构建step by step (五):initrd
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

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