linux新手编程必读 & 详解linux内存磁盘初始化技术
原文作者:hari
翻译者:likunarmstrong.googlepages.com
假如,你是一个linux编程方面的初学者,那么,在开始这段神奇经历之前,你一定会对这篇文章所讲的一些方面非常感兴趣。特别当你以前是一个windows环境下的程序员的话,那就必须改变一些编程方面的想法和思维,这样当你遇到完全不同的理念和思维方式的时候,才能将这种所谓的“文化”差异带来的影响降到最低。
针对程序员,特别是一些有经验的Windows程序员在论坛上所问的关于linux编程方面的常见问题,我的建议如下:
1.评估每一种可能性
当你在Windows下编程时,通常都会使用系统提供的标准API,这让你只能选择唯一一种方式来完成一个特定的目标。而在linux下,实现相同目的的方式有很多种,例如,有各种不同的GUI编程工具包,包括编写GUI程序时最常见的QT和GTK。另外,你也不一定要选择c或者c++语言编程,因为在linux下的编程方式有很多种,包括基本的shell编程,Perl,Python和PHP等。因此“需要学习硬件工作方式”这种固有的观念已经过时了,事实上,脚本编程语言的强大及优雅会让你大吃一惊的。
注意,不要让这些选择来主导你。只需要先了解他们,然后选择一种最让你适应的工具和技术即可。
2.使用IDE不见得更有效率
那些经常使用微软Visual Studio来进行开发的程序员,一开始肯定不会适应linux的编程方式。当然,在linux下也有一些相当不错的IDE,例如Kdevelop,anjuta和Eclipse等,但在长期的编程过程中,你会发现使用文本编辑器以及创建一个make文件其实是更好的方式。特别是当你在开发自由软件的时候,考虑到代码会被分享,其它的程序员可能参与到这个项目中,你肯定不会愿意将开发过程绑定在某个特定的平台或者IDE上面的。当然,IDE并不是完全没用的,但使用简单的文本编辑器配合makefile的方式显然更有利于一些较小项目的开发。
3.不要去关注那些与发行版相关的特性
让很多Windows开发人员感到惊讶的是,实际上,你可能无法知道最终用户电脑上所使用的linux系统配置,因为不同的发行版可能使用位于不同位置,设置选项也不同的配置文件,因此,除非你是在为某个特定的发行版本编写系统配置的工具,否则尽量避免在编程过程中假设与发行版相关的那些特性。同时,也不要让用户以root身份运行你的程序,除非该程序的唯一目的是系统特定设置。
4.不要去修改,也别试图去修改系统
避免“不良编程”的规则:
1 不要去假设某个系统文件存在于最终用户的电脑中。前面已经说过了,不同的发行版之间存在差异。
2 作为一个普通权限的用户,你是不能修改系统文件的,同时,不要试图让一个普通程序以“root”身份去运行。
在大多数情况下,你都会发现,去接触系统定制文件是完全没有必要的。
5.让程序看起来和谐而稳定
GUI程序员,特别是那些GTK和QT的程序员,需要明白的是,这些GUI库是相当相当主题化(themeable),什么意思呢?就是说最终用户能够通过各种不同的方式来修改GUI的可视外观------包括字体,颜色以及widget等。因此,避免在你的GUI中使用特殊的字体和颜色。记住,你不需要这些东西。不要强制你的最终用户在他们的系统中安装任何特殊的字体,让你使用的GUI库来决定程序的可视外观。总的来说,除非你是在开发某个字处理程序,否则,基本上是不需要在应用程序代码中直接操作字体元素的。
6.随时做好研究的准备
在linux中,没有类似于MSDN这样的工具来为你提供那些讲解每一个简单的编程工具或者api的文档。这是不实际的,因为linux并不是由某一个单独的公司进行开发。当然,大多数情况下,如果你使用那些第三方的开发库,就可以在维护这些库的公司的官方网页上找到开发文档(可在线观看或下载)。但是需要注意的是,还是有相当一部分开发库提供的文档并不完整,甚至就没有。所以很多时候你都需要寻找示例代码,或者直接在头文件中去学习更多的关于某个库的知识。
当然,如果你幸运的话,在使用大多数流行的第三方库时都不会遇到这种情况,尽管这样,还是要做好自己深入研究的准备。
7.不要对依赖文件进行打包
当你创建一个可发行的安装包时,请不要在你的tar仓库文件中包括它的依赖组件。你只需要将源代码打包,并尽可能详细得提供如何编译的指示即可。至于其所需要的依赖,请在README或者INSTALL的文件中,以及你的网页上介绍。为什么呢?既然绝大多数的linux发行版都拥有它们自己的安装包管理系统(而且如果你的软件足够优秀的话,甚至可能被采纳到官方的软件安装包仓库中),你就应当把处理依赖关系的工作留给那些手工编译安装你程序的最终用户或者发行版本的软件包维护者,他们会将你的程序作为自己所维护的安装包的一部分的。既然每一种linux发行版本都有不同的管理依赖关系的机制,你就不要再去试图创建任何看起来比较智能的,能自动安装其它库的“安装步骤”了,这只会和系统管理机制产生冲突。
除了创建版本的问题以外,在程序中包含依赖关系是相当乏味而麻烦的。因此,请尽力让程序的依赖程度降到最低,特别是当你打算利用第三方开发库来编写程序的时候。甚至就是当你并不打算以自由软件的方式发行你的程序时,也请尽量最小化你的安装包,并向最终用户提供关于依赖关系的说明。
我真诚希望,这篇文章对你,一个linux编程方面的新手来说,能够有用。下面,就开始尽情享受你的编程吧!
翻译者:likunarmstrong.googlepages.com
假如,你是一个linux编程方面的初学者,那么,在开始这段神奇经历之前,你一定会对这篇文章所讲的一些方面非常感兴趣。特别当你以前是一个windows环境下的程序员的话,那就必须改变一些编程方面的想法和思维,这样当你遇到完全不同的理念和思维方式的时候,才能将这种所谓的“文化”差异带来的影响降到最低。
针对程序员,特别是一些有经验的Windows程序员在论坛上所问的关于linux编程方面的常见问题,我的建议如下:
1.评估每一种可能性
当你在Windows下编程时,通常都会使用系统提供的标准API,这让你只能选择唯一一种方式来完成一个特定的目标。而在linux下,实现相同目的的方式有很多种,例如,有各种不同的GUI编程工具包,包括编写GUI程序时最常见的QT和GTK。另外,你也不一定要选择c或者c++语言编程,因为在linux下的编程方式有很多种,包括基本的shell编程,Perl,Python和PHP等。因此“需要学习硬件工作方式”这种固有的观念已经过时了,事实上,脚本编程语言的强大及优雅会让你大吃一惊的。
注意,不要让这些选择来主导你。只需要先了解他们,然后选择一种最让你适应的工具和技术即可。
2.使用IDE不见得更有效率
那些经常使用微软Visual Studio来进行开发的程序员,一开始肯定不会适应linux的编程方式。当然,在linux下也有一些相当不错的IDE,例如Kdevelop,anjuta和Eclipse等,但在长期的编程过程中,你会发现使用文本编辑器以及创建一个make文件其实是更好的方式。特别是当你在开发自由软件的时候,考虑到代码会被分享,其它的程序员可能参与到这个项目中,你肯定不会愿意将开发过程绑定在某个特定的平台或者IDE上面的。当然,IDE并不是完全没用的,但使用简单的文本编辑器配合makefile的方式显然更有利于一些较小项目的开发。
3.不要去关注那些与发行版相关的特性
让很多Windows开发人员感到惊讶的是,实际上,你可能无法知道最终用户电脑上所使用的linux系统配置,因为不同的发行版可能使用位于不同位置,设置选项也不同的配置文件,因此,除非你是在为某个特定的发行版本编写系统配置的工具,否则尽量避免在编程过程中假设与发行版相关的那些特性。同时,也不要让用户以root身份运行你的程序,除非该程序的唯一目的是系统特定设置。
4.不要去修改,也别试图去修改系统
避免“不良编程”的规则:
1 不要去假设某个系统文件存在于最终用户的电脑中。前面已经说过了,不同的发行版之间存在差异。
2 作为一个普通权限的用户,你是不能修改系统文件的,同时,不要试图让一个普通程序以“root”身份去运行。
在大多数情况下,你都会发现,去接触系统定制文件是完全没有必要的。
5.让程序看起来和谐而稳定
GUI程序员,特别是那些GTK和QT的程序员,需要明白的是,这些GUI库是相当相当主题化(themeable),什么意思呢?就是说最终用户能够通过各种不同的方式来修改GUI的可视外观------包括字体,颜色以及widget等。因此,避免在你的GUI中使用特殊的字体和颜色。记住,你不需要这些东西。不要强制你的最终用户在他们的系统中安装任何特殊的字体,让你使用的GUI库来决定程序的可视外观。总的来说,除非你是在开发某个字处理程序,否则,基本上是不需要在应用程序代码中直接操作字体元素的。
6.随时做好研究的准备
在linux中,没有类似于MSDN这样的工具来为你提供那些讲解每一个简单的编程工具或者api的文档。这是不实际的,因为linux并不是由某一个单独的公司进行开发。当然,大多数情况下,如果你使用那些第三方的开发库,就可以在维护这些库的公司的官方网页上找到开发文档(可在线观看或下载)。但是需要注意的是,还是有相当一部分开发库提供的文档并不完整,甚至就没有。所以很多时候你都需要寻找示例代码,或者直接在头文件中去学习更多的关于某个库的知识。
当然,如果你幸运的话,在使用大多数流行的第三方库时都不会遇到这种情况,尽管这样,还是要做好自己深入研究的准备。
7.不要对依赖文件进行打包
当你创建一个可发行的安装包时,请不要在你的tar仓库文件中包括它的依赖组件。你只需要将源代码打包,并尽可能详细得提供如何编译的指示即可。至于其所需要的依赖,请在README或者INSTALL的文件中,以及你的网页上介绍。为什么呢?既然绝大多数的linux发行版都拥有它们自己的安装包管理系统(而且如果你的软件足够优秀的话,甚至可能被采纳到官方的软件安装包仓库中),你就应当把处理依赖关系的工作留给那些手工编译安装你程序的最终用户或者发行版本的软件包维护者,他们会将你的程序作为自己所维护的安装包的一部分的。既然每一种linux发行版本都有不同的管理依赖关系的机制,你就不要再去试图创建任何看起来比较智能的,能自动安装其它库的“安装步骤”了,这只会和系统管理机制产生冲突。
除了创建版本的问题以外,在程序中包含依赖关系是相当乏味而麻烦的。因此,请尽力让程序的依赖程度降到最低,特别是当你打算利用第三方开发库来编写程序的时候。甚至就是当你并不打算以自由软件的方式发行你的程序时,也请尽量最小化你的安装包,并向最终用户提供关于依赖关系的说明。
我真诚希望,这篇文章对你,一个linux编程方面的新手来说,能够有用。下面,就开始尽情享受你的编程吧!
原文链接:http://www-128.ibm.com/developerworks/linux/library/l-initrd.html?ca=dgr-lnxw02LinuxInitialRam
正文:
linux内存初始化技术(initrd)用于支持两阶段的系统引导过程,是在系统启动过程中被挂载的临时root文件系统(译者注:这里的root文件系统是指的根文件系统)。initrd包含很多可执行程序和驱动,并允许在临时的内存磁盘根文件系统被卸载,内存被释放后挂载真实的root文件系统。在许多嵌入式linux文件系统中,initrd是最终的根文件系统。这篇文章主要讲解了linux2.6内核的initrd技术,包括在内核中的创建及使用。
1 什么是内存磁盘初始化?
initrd挂载优先级高于真实根文件系统,它被邦定在内核上,做为内核启动过程的一部分被加载(load)。然后,做为两阶段引导过程的第一部分,内核挂载(mount)initrd,用于获得并加载真实有效的文件系统。
为了达到这个目的,initrd包含有最起码的目录与程序,例如insmod,来安装内核模块到内核中。
对于桌面或服务器linux,initrd是临时文件系统,它的生存周期很短,仅仅是做为到达真实根文件系统的桥梁。但对于没有存储设备的嵌入式系统来说,它才是永久性的根文件系统。本篇文章对这两方面均有涉及。
2 深入分析initrd
initrd包含有必须的程序和系统文件,用于支持系统的启动的第二阶段过程。创建初始化内存的方法,是随着你所使用的系统版本而改变的。从Fedora Core3以后,initrd就由回送设备(loop device)建立。什么是回送设备?它是一个设备驱动,允许你将一个文件挂载为块设备,并对其文件系统做出描述。也许loop device并不存在与你的内核中,但是你能够通过内核的配置工具(make menuconfig)打开它。路径是:Device Drivers-》Block Devices-》LoopBack Device support。下面为检查命令:
# mkdir temp ; cd temp
# cp /boot/initrd.img.gz .
# gunzip initrd.img.gz
# mount -t ext -o loop initrd.img /mnt/initrd
# ls -la /mnt/initrd
#
现在,你可以通过查看/mnt/initrd的子目录来查看initrd的内容。需要注意的是,即使你的initrd镜像文件并不是以.gz做为后缀名,但是你同样可以通过增加此后缀名来让gunzip打开它。
从Fedora Core3开始,默认的initrd镜像就是一个压缩的gpio归档文件。除了用挂载文件的方式以外,你同样可以通过cpio归档的方式来将其挂载成使用了回送设备的压缩镜像。你可以通过以下的指令来检查这个cpio归档文件的内容:
# mkdir temp ; cd temp
# cp /boot/initrd-2.6.14.2.img initrd-2.6.14.2.img.gz
# gunzip initrd-2.6.14.2.img.gz
# cpio -i --make-directories < initrd-2.6.14.2.img
你看到的结果将是一个小型根文件系统,如下所示:
# ls -la
#
drwxr-xr-x 10 root root 4096 May 7 02:48 .
drwxr-x--- 15 root root 4096 May 7 00:54 ..
drwxr-xr-x 2 root root 4096 May 7 02:48 bin
drwxr-xr-x 2 root root 4096 May 7 02:48 dev
drwxr-xr-x 4 root root 4096 May 7 02:48 etc
-rwxr-xr-x 1 root root 812 May 7 02:48 init
-rw-r--r-- 1 root root 1723392 May 7 02:45 initrd-2.6.14.2.img
drwxr-xr-x 2 root root 4096 May 7 02:48 lib
drwxr-xr-x 2 root root 4096 May 7 02:48 loopfs
drwxr-xr-x 2 root root 4096 May 7 02:48 proc
lrwxrwxrwx 1 root root 3 May 7 02:48 sbin -> bin
drwxr-xr-x 2 root root 4096 May 7 02:48 sys
drwxr-xr-x 2 root root 4096 May 7 02:48 sysroot
#
一些小的,但是很有必要的程序组合能在./bin目录下得到,包括nash(它不是一个shell,而是一个脚本解释工具),用于加载内核模块的insmod,以及lvm等。
上面所示目录中,相对比较有趣的是root目录下的初始化文件。这些文件,和传统的linux启动过程中一样,是在initrd镜像被解压缩到RAM中时生成的。待会我们将继续探讨这个问题。
3 创建initrd的工具。
现在,让我们回到一开始的讨论:initrd的镜像是如何被创建的?在传统的linux系统中,initrd是在linux build的时候被创建的。像mkinitrd这样的许许多多的工具,都能够用于通过必须的库和模块来自动构建一个用于过渡到真实根文件系统的initrd。事实上,mkinitrd工具是一个脚本文件,因此,我们能够很清楚得看到,这个过程是如何进行的。同样的,YAIRD (Yet Another Mkinitrd)工具,也允许我们自定制每一个initrd被构建的阶段。
4 自己动手,打造自定义的初始化内存盘
由于很多基于linux的嵌入式系统都没有硬盘驱动器,initrd也可以做为永久性的根文件系统。下面我就将告诉你们,如何创建一个initrd镜像。我使用的是标准linux桌面系统,因此大家即使没有嵌入式目标设备也可以照着做。除了交叉编译以外,嵌入式目标文件的构建过程是相同的。
#!/bin/bash
# Housekeeping...
rm -f /tmp/ramdisk.img
rm -f /tmp/ramdisk.img.gz
# Ramdisk Constants
RDSIZE=4000
BLKSIZE=1024
# Create an empty ramdisk image
dd if=/dev/zero of=/tmp/ramdisk.img bs=$BLKSIZE count=$RDSIZE
# Make it an ext2 mountable file system
/sbin/mke2fs -F -m 0 -b $BLKSIZE /tmp/ramdisk.img $RDSIZE
# Mount it so that we can populate
mount /tmp/ramdisk.img /mnt/initrd -t ext2 -o loop=/dev/loop0
# Populate the filesystem (subdirectories)
mkdir /mnt/initrd/bin
mkdir /mnt/initrd/sys
mkdir /mnt/initrd/dev
mkdir /mnt/initrd/proc
# Grab busybox and create the symbolic links
pushd /mnt/initrd/bin
cp /usr/local/src/busybox-1.1.1/busybox .
ln -s busybox ash
ln -s busybox mount
ln -s busybox echo
ln -s busybox ls
ln -s busybox cat
ln -s busybox ps
ln -s busybox dmesg
ln -s busybox sysctl
popd
# Grab the necessary dev files
cp -a /dev/console /mnt/initrd/dev
cp -a /dev/ramdisk /mnt/initrd/dev
cp -a /dev/ram0 /mnt/initrd/dev
cp -a /dev/null /mnt/initrd/dev
cp -a /dev/tty1 /mnt/initrd/dev
cp -a /dev/tty2 /mnt/initrd/dev
# Equate sbin with bin
pushd /mnt/initrd
ln -s bin sbin
popd
# Create the init file
cat >> /mnt/initrd/linuxrc << EOF
#!/bin/ash
echo
echo "Simple initrd is active"
echo
mount -t proc /proc /proc
mount -t sysfs none /sys
/bin/ash --login
EOF
chmod +x /mnt/initrd/linuxrc
# Finish up...
umount /mnt/initrd
gzip -9 /tmp/ramdisk.img
cp /tmp/ramdisk.img.gz /boot/ramdisk.img.gz
想创建initrd的话,你需要首先创建一个空文件,将/dev/zero(0字符流)做为ramdisk.img的输入。得到的文件大小大约是4MB(有4000个1K的块组成)。接下来,用mke2fs命令来创建一个使用这个空文件的ext2文件系统。现在,这个文件就是一个ext2文件系统。ok,接下来,以回路设备的形式挂载这个文件到/mnt/initrd,现在,你就在挂载点拥有一个代表着ext2文件系统的目录,并用与存放你的initrd。其他大多数的脚本语句都是用于实现这个功能。
下一步,就是创建一些必须的子目录,用于生成你的根文件系统: /bin, /sys, /dev, 和 /pro。这里只需要少数几个目录,例如,没有/lib。但是它们已经包含了大部分功能。
如果想让你的根文件系统发挥更大的作用,请使用 BusyBox。这个工具是一个包含了许多独立工具的镜像,这些独立的工具你都能在linux中找到( ash, a等等wk, sed, insmod)。BusyBox的优势在于,它把它们集合在了一起,并分享了公用的部分,从而极大缩小了镜像的体积。这对于嵌入式系统来讲,是非常理想的。请将BustBox镜像从它的源目录中复制出来,到你的/bin目录下,这样,很多指向BusyBox工具集的符号链接将被创建,BusyBox能确定哪一个工具将被使用,并自动引用它。这个/bin目录下被创建的链接的小型集合将用于对启动脚本的支持。
再下一步,就是一小部分特殊设备文件的创建。我从我的/dev文件夹中直接拷贝了出来,别忘了加上-a选项来保持它们原有的属性。
倒数第二步,就是生成linuxrc文件。在内核挂载了内存盘之后,它将搜索并执行相关的启动文件,如果没有找到,内核就将linuxrc文件做为其启动脚本。你最好在这个文件中对环境变量做一些基本设置,例如挂载/proc文件系统等。除了/proc外,我还挂载了/sys文件系统,将消息发送给终端。最后,我调用ash并通过它和根文件系统交互。最后记住,用chmod把linuxrc文件的属性改为可执行。
最后,你的根文件系统算是ok了。现在它并没有被挂载,用gzip将它压缩,并将压缩后的文件ramdisk.img.gz拷贝到/boot目录下,这样它就能被GRUB调用。
想要构建你的初始化ram盘的话,你只需要调用mkird,镜像就将自动创建并拷贝到/boot目录下。
5 测试自定义的初始化RAM盘。
你拥有的新的initrd镜像是在/boot目录下,因此,下一步就是要用你默认的内核来测试它。ok,现在你可以先重新启动你的linux系统,当GRUB引导画面出现时,按下C键,打开GRUB的命令行工具。现在,你就能通过GRUB确定启动专门的内核和initrd镜像。内核命令是允许你定制内核文件的,而initrd命令则允许你指定专门的initrd镜像文件。当它们都被指定之后,通过启动命令来启动内核,如下所示:
GNU GRUB version 0.95 (638K lower / 97216K upper memory)
[ Minimal BASH-like line editing is supported. For the first word, TAB
lists possible command completions. Anywhere else TAB lists the possible
completions of a device/filename. ESC at any time exits.]
grub> kernel /bzImage-2.6.1
[Linux-bzImage, setup=0x1400, size=0x29672e]
grub> initrd /ramdisk.img.gz
[Linux-initrd @ 0x5f2a000, 0xb5108 bytes]
grub> boot
Uncompressing Linux... OK, booting the kernel.
在内核启动之后,它开始检查initrd镜像是否可用,如果答案是确定的,那么就作为根文件系统加载并挂载它。下面就是这个特殊启动过程的结尾:
...
md: Autodetecting RAID arrays
md: autorun
md: ... autorun DONE.
RAMDISK: Compressed image found at block 0
VFS: Mounted root (ext2 file system).
Freeing unused kernel memory: 208k freed
/ $ ls
bin etc linuxrc proc sys
dev lib lost+found sbin
/ $ cat /proc/1/cmdline
/bin/ash/linuxrc
/ $ cd bin
/bin $ ls
ash cat echo mount sysctl
busybox dmesg ls ps
/bin $ touch zfile
/bin $ ls
ash cat echo mount sysctl
busybox dmesg ls ps zfile
当启动之后,可以通过ash来进入命令模式。在本例中,我探究了根文件系统并向你演示了,你能通过新建文件来写入这个文件系统。只需要注意,第一步是要创建linuxrc。
6 通过初始化内存盘启动
现在,大家已经看到了如何构建并使用一个自定制的初始化内存盘,这一节则用于介绍,内核是如何辨认initrd并将其作为它的根文件系统挂载的。我将涉及一些boot chain中的主要的函数并对发生的事件做出解释。
像GRUB这样的boot loader,通常会确认即将加载的内核并复制该内核镜像与任何相关联的initrd到内存中,你可以在你linux内核源程序目录下的./init子目录中找到这些功能实现。
在内核与initrd镜像被解压缩和复制到内存后,内核被调用。此时,开始各种各样的初始化过程,最终,你会发现自己处于init/main.c:init() (subdir/file:function)。这个函数实现了很多的子系统初始化。在这里,要调用init/do_mounts.c:prepare_namespace(),用来准备命名空间(挂载dev 文件系统, RAID, 或者md, devices, 以及, 最后的initrd)。通过对 init/do_mounts_initrd.c:initrd_load()的调用,最终完成对initrd的加载。
initrd_load()调用init/do_mounts_rd.c:rd_load_image(),来决定是否通过调用init/do_mounts_rd.c:identify_ramdisk_image()来加载内存盘镜像。后面这个函数通过检查内核的编号来确定文件究竟是是minux,etc2,romfs,cramfs,还是gzip格式,直到返回initrd_load_image后,init/do_mounts_rd:crd_load()又被调用。这个函数负责分配空间给内存盘,并进行校验计算,解压缩,最后将内存盘镜像加载到内存中。此时,你就已经拥有了一个适合于挂载的,在块设备中的initrd镜像。
现在,通过调用init/do_mounts.c:mount_root()将这个块设备做为root挂载。ok,根设备就被创建了,接下来调用的函数是init/do_mounts.c:mount_block_root(),此函数又调用fs/namespace.c:sys_mount()来挂载真实的根文件系统并对其进行chdir操作。
最后,会返回到启动函数中,并调用init/main.c:run_init_process。调用的结果是,初始化进程开始(在这里是通过/linuxrc)。linuxrc可以是一个可执行程序,也可以是脚本(只要脚本解释器能够正常解释它)。
函数调用的层次关系可以从下表中看出。并不是所有与复制、挂载初始化内存盘的函数都被列举出来,这里仅仅是大概的,对整体基本流程的回顾:
init/main.c:init
init/do_mounts.c:prepare_namespace
init/do_mounts_initrd.c:initrd_load
init/do_mounts_rd.c:rd_load_image
init/do_mounts_rd.c:identify_ramdisk_image
init/do_mounts_rd.c:crd_load
lib/inflate.c:gunzip
init/do_mounts.c:mount_root
init/do_mounts.c:mount_block_root
init/do_mounts.c:do_mount_root
fs/namespace.c:sys_mount
init/main.c:run_init_process
execve
7 无盘启动的应用
同很多嵌入式系统的启动一样,本地磁盘(软驱或者光驱)对于启动内核和内存盘根文件系统来说,并不是必须的。DHCP工具能被用于确认网络参数,例如大家熟悉的IP抵制和子网掩码等。此外,TFTP能被用于将内核镜像以及初始化内存盘镜像传送到本地设备。一旦传输完成,linux内核就能被启动以及挂载initrd,和本地镜像启动的过程一样。
8 让你的initrd尽可能小
当你在构建嵌入式系统时,总是希望initrd的镜像尽可能小,恩,这里将提供一些小技巧。首先就是使用BusyBox。前面已经提到过,BusyBox包含了很多较大的工具,通常体积都以MB计算,但是它成功得将自己的体积控制在几百KB的范围内。
在本例中,BusyBox镜像使用的是静态链接,因此不需要提供任何库文件。但是,如果你需要得到标准的C库文件来满足自己的二进制程序,除了大体积的glibc库,你有其他更好的选择。第一个,小体积的uClibc库,是专门用于有空间限制的,标准C库的缩水版本。另一个适用于有空间限制环境的库是dietlib。记住,你需要在自己的嵌入式系统中,用这些库重新编译你的二进制程序。虽然使用它们会带来一些附加的工作,但是,是值得的。
9 总结
初始化内存盘技术被创建的最初目的,是为了让内核通过一个临时的根文件系统来过渡到最终的根文件系统。initrd对于嵌入式linux系统同样是很有用处的:它能做为一个非持续性的根文件系统挂载到内存盘中。
正文:
linux内存初始化技术(initrd)用于支持两阶段的系统引导过程,是在系统启动过程中被挂载的临时root文件系统(译者注:这里的root文件系统是指的根文件系统)。initrd包含很多可执行程序和驱动,并允许在临时的内存磁盘根文件系统被卸载,内存被释放后挂载真实的root文件系统。在许多嵌入式linux文件系统中,initrd是最终的根文件系统。这篇文章主要讲解了linux2.6内核的initrd技术,包括在内核中的创建及使用。
1 什么是内存磁盘初始化?
initrd挂载优先级高于真实根文件系统,它被邦定在内核上,做为内核启动过程的一部分被加载(load)。然后,做为两阶段引导过程的第一部分,内核挂载(mount)initrd,用于获得并加载真实有效的文件系统。
为了达到这个目的,initrd包含有最起码的目录与程序,例如insmod,来安装内核模块到内核中。
对于桌面或服务器linux,initrd是临时文件系统,它的生存周期很短,仅仅是做为到达真实根文件系统的桥梁。但对于没有存储设备的嵌入式系统来说,它才是永久性的根文件系统。本篇文章对这两方面均有涉及。
2 深入分析initrd
initrd包含有必须的程序和系统文件,用于支持系统的启动的第二阶段过程。创建初始化内存的方法,是随着你所使用的系统版本而改变的。从Fedora Core3以后,initrd就由回送设备(loop device)建立。什么是回送设备?它是一个设备驱动,允许你将一个文件挂载为块设备,并对其文件系统做出描述。也许loop device并不存在与你的内核中,但是你能够通过内核的配置工具(make menuconfig)打开它。路径是:Device Drivers-》Block Devices-》LoopBack Device support。下面为检查命令:
# mkdir temp ; cd temp
# cp /boot/initrd.img.gz .
# gunzip initrd.img.gz
# mount -t ext -o loop initrd.img /mnt/initrd
# ls -la /mnt/initrd
#
现在,你可以通过查看/mnt/initrd的子目录来查看initrd的内容。需要注意的是,即使你的initrd镜像文件并不是以.gz做为后缀名,但是你同样可以通过增加此后缀名来让gunzip打开它。
从Fedora Core3开始,默认的initrd镜像就是一个压缩的gpio归档文件。除了用挂载文件的方式以外,你同样可以通过cpio归档的方式来将其挂载成使用了回送设备的压缩镜像。你可以通过以下的指令来检查这个cpio归档文件的内容:
# mkdir temp ; cd temp
# cp /boot/initrd-2.6.14.2.img initrd-2.6.14.2.img.gz
# gunzip initrd-2.6.14.2.img.gz
# cpio -i --make-directories < initrd-2.6.14.2.img
你看到的结果将是一个小型根文件系统,如下所示:
# ls -la
#
drwxr-xr-x 10 root root 4096 May 7 02:48 .
drwxr-x--- 15 root root 4096 May 7 00:54 ..
drwxr-xr-x 2 root root 4096 May 7 02:48 bin
drwxr-xr-x 2 root root 4096 May 7 02:48 dev
drwxr-xr-x 4 root root 4096 May 7 02:48 etc
-rwxr-xr-x 1 root root 812 May 7 02:48 init
-rw-r--r-- 1 root root 1723392 May 7 02:45 initrd-2.6.14.2.img
drwxr-xr-x 2 root root 4096 May 7 02:48 lib
drwxr-xr-x 2 root root 4096 May 7 02:48 loopfs
drwxr-xr-x 2 root root 4096 May 7 02:48 proc
lrwxrwxrwx 1 root root 3 May 7 02:48 sbin -> bin
drwxr-xr-x 2 root root 4096 May 7 02:48 sys
drwxr-xr-x 2 root root 4096 May 7 02:48 sysroot
#
一些小的,但是很有必要的程序组合能在./bin目录下得到,包括nash(它不是一个shell,而是一个脚本解释工具),用于加载内核模块的insmod,以及lvm等。
上面所示目录中,相对比较有趣的是root目录下的初始化文件。这些文件,和传统的linux启动过程中一样,是在initrd镜像被解压缩到RAM中时生成的。待会我们将继续探讨这个问题。
3 创建initrd的工具。
现在,让我们回到一开始的讨论:initrd的镜像是如何被创建的?在传统的linux系统中,initrd是在linux build的时候被创建的。像mkinitrd这样的许许多多的工具,都能够用于通过必须的库和模块来自动构建一个用于过渡到真实根文件系统的initrd。事实上,mkinitrd工具是一个脚本文件,因此,我们能够很清楚得看到,这个过程是如何进行的。同样的,YAIRD (Yet Another Mkinitrd)工具,也允许我们自定制每一个initrd被构建的阶段。
4 自己动手,打造自定义的初始化内存盘
由于很多基于linux的嵌入式系统都没有硬盘驱动器,initrd也可以做为永久性的根文件系统。下面我就将告诉你们,如何创建一个initrd镜像。我使用的是标准linux桌面系统,因此大家即使没有嵌入式目标设备也可以照着做。除了交叉编译以外,嵌入式目标文件的构建过程是相同的。
#!/bin/bash
# Housekeeping...
rm -f /tmp/ramdisk.img
rm -f /tmp/ramdisk.img.gz
# Ramdisk Constants
RDSIZE=4000
BLKSIZE=1024
# Create an empty ramdisk image
dd if=/dev/zero of=/tmp/ramdisk.img bs=$BLKSIZE count=$RDSIZE
# Make it an ext2 mountable file system
/sbin/mke2fs -F -m 0 -b $BLKSIZE /tmp/ramdisk.img $RDSIZE
# Mount it so that we can populate
mount /tmp/ramdisk.img /mnt/initrd -t ext2 -o loop=/dev/loop0
# Populate the filesystem (subdirectories)
mkdir /mnt/initrd/bin
mkdir /mnt/initrd/sys
mkdir /mnt/initrd/dev
mkdir /mnt/initrd/proc
# Grab busybox and create the symbolic links
pushd /mnt/initrd/bin
cp /usr/local/src/busybox-1.1.1/busybox .
ln -s busybox ash
ln -s busybox mount
ln -s busybox echo
ln -s busybox ls
ln -s busybox cat
ln -s busybox ps
ln -s busybox dmesg
ln -s busybox sysctl
popd
# Grab the necessary dev files
cp -a /dev/console /mnt/initrd/dev
cp -a /dev/ramdisk /mnt/initrd/dev
cp -a /dev/ram0 /mnt/initrd/dev
cp -a /dev/null /mnt/initrd/dev
cp -a /dev/tty1 /mnt/initrd/dev
cp -a /dev/tty2 /mnt/initrd/dev
# Equate sbin with bin
pushd /mnt/initrd
ln -s bin sbin
popd
# Create the init file
cat >> /mnt/initrd/linuxrc << EOF
#!/bin/ash
echo
echo "Simple initrd is active"
echo
mount -t proc /proc /proc
mount -t sysfs none /sys
/bin/ash --login
EOF
chmod +x /mnt/initrd/linuxrc
# Finish up...
umount /mnt/initrd
gzip -9 /tmp/ramdisk.img
cp /tmp/ramdisk.img.gz /boot/ramdisk.img.gz
想创建initrd的话,你需要首先创建一个空文件,将/dev/zero(0字符流)做为ramdisk.img的输入。得到的文件大小大约是4MB(有4000个1K的块组成)。接下来,用mke2fs命令来创建一个使用这个空文件的ext2文件系统。现在,这个文件就是一个ext2文件系统。ok,接下来,以回路设备的形式挂载这个文件到/mnt/initrd,现在,你就在挂载点拥有一个代表着ext2文件系统的目录,并用与存放你的initrd。其他大多数的脚本语句都是用于实现这个功能。
下一步,就是创建一些必须的子目录,用于生成你的根文件系统: /bin, /sys, /dev, 和 /pro。这里只需要少数几个目录,例如,没有/lib。但是它们已经包含了大部分功能。
如果想让你的根文件系统发挥更大的作用,请使用 BusyBox。这个工具是一个包含了许多独立工具的镜像,这些独立的工具你都能在linux中找到( ash, a等等wk, sed, insmod)。BusyBox的优势在于,它把它们集合在了一起,并分享了公用的部分,从而极大缩小了镜像的体积。这对于嵌入式系统来讲,是非常理想的。请将BustBox镜像从它的源目录中复制出来,到你的/bin目录下,这样,很多指向BusyBox工具集的符号链接将被创建,BusyBox能确定哪一个工具将被使用,并自动引用它。这个/bin目录下被创建的链接的小型集合将用于对启动脚本的支持。
再下一步,就是一小部分特殊设备文件的创建。我从我的/dev文件夹中直接拷贝了出来,别忘了加上-a选项来保持它们原有的属性。
倒数第二步,就是生成linuxrc文件。在内核挂载了内存盘之后,它将搜索并执行相关的启动文件,如果没有找到,内核就将linuxrc文件做为其启动脚本。你最好在这个文件中对环境变量做一些基本设置,例如挂载/proc文件系统等。除了/proc外,我还挂载了/sys文件系统,将消息发送给终端。最后,我调用ash并通过它和根文件系统交互。最后记住,用chmod把linuxrc文件的属性改为可执行。
最后,你的根文件系统算是ok了。现在它并没有被挂载,用gzip将它压缩,并将压缩后的文件ramdisk.img.gz拷贝到/boot目录下,这样它就能被GRUB调用。
想要构建你的初始化ram盘的话,你只需要调用mkird,镜像就将自动创建并拷贝到/boot目录下。
5 测试自定义的初始化RAM盘。
你拥有的新的initrd镜像是在/boot目录下,因此,下一步就是要用你默认的内核来测试它。ok,现在你可以先重新启动你的linux系统,当GRUB引导画面出现时,按下C键,打开GRUB的命令行工具。现在,你就能通过GRUB确定启动专门的内核和initrd镜像。内核命令是允许你定制内核文件的,而initrd命令则允许你指定专门的initrd镜像文件。当它们都被指定之后,通过启动命令来启动内核,如下所示:
GNU GRUB version 0.95 (638K lower / 97216K upper memory)
[ Minimal BASH-like line editing is supported. For the first word, TAB
lists possible command completions. Anywhere else TAB lists the possible
completions of a device/filename. ESC at any time exits.]
grub> kernel /bzImage-2.6.1
[Linux-bzImage, setup=0x1400, size=0x29672e]
grub> initrd /ramdisk.img.gz
[Linux-initrd @ 0x5f2a000, 0xb5108 bytes]
grub> boot
Uncompressing Linux... OK, booting the kernel.
在内核启动之后,它开始检查initrd镜像是否可用,如果答案是确定的,那么就作为根文件系统加载并挂载它。下面就是这个特殊启动过程的结尾:
...
md: Autodetecting RAID arrays
md: autorun
md: ... autorun DONE.
RAMDISK: Compressed image found at block 0
VFS: Mounted root (ext2 file system).
Freeing unused kernel memory: 208k freed
/ $ ls
bin etc linuxrc proc sys
dev lib lost+found sbin
/ $ cat /proc/1/cmdline
/bin/ash/linuxrc
/ $ cd bin
/bin $ ls
ash cat echo mount sysctl
busybox dmesg ls ps
/bin $ touch zfile
/bin $ ls
ash cat echo mount sysctl
busybox dmesg ls ps zfile
当启动之后,可以通过ash来进入命令模式。在本例中,我探究了根文件系统并向你演示了,你能通过新建文件来写入这个文件系统。只需要注意,第一步是要创建linuxrc。
6 通过初始化内存盘启动
现在,大家已经看到了如何构建并使用一个自定制的初始化内存盘,这一节则用于介绍,内核是如何辨认initrd并将其作为它的根文件系统挂载的。我将涉及一些boot chain中的主要的函数并对发生的事件做出解释。
像GRUB这样的boot loader,通常会确认即将加载的内核并复制该内核镜像与任何相关联的initrd到内存中,你可以在你linux内核源程序目录下的./init子目录中找到这些功能实现。
在内核与initrd镜像被解压缩和复制到内存后,内核被调用。此时,开始各种各样的初始化过程,最终,你会发现自己处于init/main.c:init() (subdir/file:function)。这个函数实现了很多的子系统初始化。在这里,要调用init/do_mounts.c:prepare_namespace(),用来准备命名空间(挂载dev 文件系统, RAID, 或者md, devices, 以及, 最后的initrd)。通过对 init/do_mounts_initrd.c:initrd_load()的调用,最终完成对initrd的加载。
initrd_load()调用init/do_mounts_rd.c:rd_load_image(),来决定是否通过调用init/do_mounts_rd.c:identify_ramdisk_image()来加载内存盘镜像。后面这个函数通过检查内核的编号来确定文件究竟是是minux,etc2,romfs,cramfs,还是gzip格式,直到返回initrd_load_image后,init/do_mounts_rd:crd_load()又被调用。这个函数负责分配空间给内存盘,并进行校验计算,解压缩,最后将内存盘镜像加载到内存中。此时,你就已经拥有了一个适合于挂载的,在块设备中的initrd镜像。
现在,通过调用init/do_mounts.c:mount_root()将这个块设备做为root挂载。ok,根设备就被创建了,接下来调用的函数是init/do_mounts.c:mount_block_root(),此函数又调用fs/namespace.c:sys_mount()来挂载真实的根文件系统并对其进行chdir操作。
最后,会返回到启动函数中,并调用init/main.c:run_init_process。调用的结果是,初始化进程开始(在这里是通过/linuxrc)。linuxrc可以是一个可执行程序,也可以是脚本(只要脚本解释器能够正常解释它)。
函数调用的层次关系可以从下表中看出。并不是所有与复制、挂载初始化内存盘的函数都被列举出来,这里仅仅是大概的,对整体基本流程的回顾:
init/main.c:init
init/do_mounts.c:prepare_namespace
init/do_mounts_initrd.c:initrd_load
init/do_mounts_rd.c:rd_load_image
init/do_mounts_rd.c:identify_ramdisk_image
init/do_mounts_rd.c:crd_load
lib/inflate.c:gunzip
init/do_mounts.c:mount_root
init/do_mounts.c:mount_block_root
init/do_mounts.c:do_mount_root
fs/namespace.c:sys_mount
init/main.c:run_init_process
execve
7 无盘启动的应用
同很多嵌入式系统的启动一样,本地磁盘(软驱或者光驱)对于启动内核和内存盘根文件系统来说,并不是必须的。DHCP工具能被用于确认网络参数,例如大家熟悉的IP抵制和子网掩码等。此外,TFTP能被用于将内核镜像以及初始化内存盘镜像传送到本地设备。一旦传输完成,linux内核就能被启动以及挂载initrd,和本地镜像启动的过程一样。
8 让你的initrd尽可能小
当你在构建嵌入式系统时,总是希望initrd的镜像尽可能小,恩,这里将提供一些小技巧。首先就是使用BusyBox。前面已经提到过,BusyBox包含了很多较大的工具,通常体积都以MB计算,但是它成功得将自己的体积控制在几百KB的范围内。
在本例中,BusyBox镜像使用的是静态链接,因此不需要提供任何库文件。但是,如果你需要得到标准的C库文件来满足自己的二进制程序,除了大体积的glibc库,你有其他更好的选择。第一个,小体积的uClibc库,是专门用于有空间限制的,标准C库的缩水版本。另一个适用于有空间限制环境的库是dietlib。记住,你需要在自己的嵌入式系统中,用这些库重新编译你的二进制程序。虽然使用它们会带来一些附加的工作,但是,是值得的。
9 总结
初始化内存盘技术被创建的最初目的,是为了让内核通过一个临时的根文件系统来过渡到最终的根文件系统。initrd对于嵌入式linux系统同样是很有用处的:它能做为一个非持续性的根文件系统挂载到内存盘中。