Grub简介
GNU GRUB 是一个多重操作系统启动管理器。GNU GRUB 是由GRUB(GRand Unified Bootloader) 派生而来。GRUB最初由Erich Stefan Boleyn 设计和应用。
目前,GRUB分成GRUB legacy和GRUB 2。版本号为0.9x及以前的版本均被称为GRUB legacy,版本号从1.x开始就被称为GRUB2。目前GRUB legacy已经停止开发了,只是不断推出bug fix,不会再引入新功能了,所有的开发都转移到GRUB2上面。
相信许多liunxer初次听到“GRUB4DOS”【grub for dos: 看名气让人误解为只为DOS而开发,其实他是对 GNU GRUB Lagecy 的二次开发,是他的一个分支】是它的名字给人们造成了误解,这也是历史遗留问题——一开始仅仅是“for dos”,而后来开发越来越深入,“FOR DOS”已远不能涵盖其全部。于是它的名字就成了一个障碍,新接触的人往往望名生意,以为它是专给DOS用的,便不去关注——我本人就是如此,很久之后才重新发现它,所以写下这篇文字,希望更多人能够了解并使用这一优秀作品,享受它带来的可靠与便捷。
而 GRUB4DOS 则是对 GNU GRUB Lagecy 的二次开发。该项目最早由 不点 在2003年发起,目前主要由他、bean 和另外几位中国人维护,很多人贡献了代码,再加上广大网友热心帮助,使得 GRUB4DOS 不断完善。在当前 新的 GRUB2 没有到位、老的 GNU GRUB Lagecy 又有许多不完善 的情况下,GRUB4DOS 受到越来越多人的欢迎。
什么是 GRUB?
GRUB 是引导装入器 -- 它负责装入内核并引导 Linux 系统。GRUB 还可以引导其它操作系统,如 FreeBSD、NetBSD、OpenBSD、GNU HURD 和 DOS,以及 Windows 95、98、NT 和 2000。尽管引导操作系统看上去是件平凡且琐碎的任务,但它实际上很重要。如果引导装入器不能很好地完成工作或者不具有弹性,那么就可能锁住系统,而无法引导计算机。另外,好的引导装入器可以给您灵活性,让您可以在计算机上安装多个操作系统,而不必处理不必要的麻烦。
GRUB 很棒
GRUB 是一个很棒的引导装入器。它有许多功能,可以使引导过程变得非常可靠。例如,它可以直接从 FAT、minix、FFS、ext2 或 ReiserFS 分区读取 Linux 内核。这就意味着无论怎样它总能找到内核。另外,GRUB 有一个特殊的交互式控制台方式,可以让您手工装入内核并选择引导分区。这个功能是无价的:即使 GRUB 菜单配置不正确,你仍可以引导系统。哦,对了 -- GRUB 还有一个彩色引导菜单。我们只是刚开始。
为什么使用 GRUB?
您也许会奇怪,为什么全世界都需要 GRUB -- 毕竟,Linux 世界在很长一段时间里一直使用 LILO 引导装入器,而且它可以让上百万的 Linux 用户引导系统。是的,的确是这样,LILO 很有效。但是,LILO 的维修率很高,而且很不灵活。与其花很多时间来描述 GRUB 的优点,还不如演示如何创建自己的 GRUB 引导盘以及如何使用它来引导系统。然后,我将说明 GRUB 的一些很“酷”的技术细节,并指导您完成将 GRUB 安装到 MBR(主引导记录)的过程,以使它成为缺省引导装入器。
如果您有点胆小,不必害怕。可以学习本教程的前半部分,创建 GRUB 引导盘,尝试使用 GRUB 而不必弄乱现有的引导装入器。或者,可以用其安全的“驻留”方式来熟悉 GRUB。那么,让我们立即开始吧。
Grub 2虽在名称上是Grub的升级版,但其代码实际上被完全重写了。[1],对于最终用户来讲,Grub 2带来了若干改进,如:
自动搜索可用的内核和硬盘中的可用系统。
可支持NTFS等更多分区格式。
可启动Windows、Mac OS X等多种系统。
更人性化的设计,如键入命令时TAB补全很强大,输出很长时会分页显示等。
界面的定制更加灵活
从配置的角度,Grub 2主要有以下几点不同[2] [3]:
/boot/grub/menu.lst 被 /boot/grub/grub.cfg所取代
主配置文件grub.cfg不能直接被用户修改,而是由系统自动生成
每当安装内核、更新Grub 2或用户主动运行grub-mkconfig -o /boot/grub/grub.cfg(曾经是update-grub)后,Grub 2会根据用户配置文件重新生成grub.cfg。
可编辑的Grub 2配置文件主要包括/etc/default/grub、和/etc/grub.d/下的各文件。
分区命名方式有变,第一个分区现在是1而不是0,但第一个设备仍然以0开始计数,如(hd0, 1)表示第一块硬盘上的第一个分区
※需要注意的是,我的ISO文件在机器的sda4的硬盘分区内,而我是通过写U盘中的grub2来引导的。U盘中没有Fedoara 18 ISO文件,在机器的硬盘内。U盘中只有grub2。
repo=hd:/dev/sda6:/==》从硬盘安装fedora15需要指定这项,因为fedora15的安装iso中没有install.img,这项是指定repo也就是软件源位于硬盘/dev/sda6的根目录/,而不需要从网上下载。不指定这项安装需要联网。
hd表示硬盘,:/dev/sda6:/ 表示安装源路径
为什么是sda6?是因为 grub的分区从0开始,你可以通过ls 来查看hd的所有分区,比如(hd0,6)=> 表示第一块硬盘上的第7个分区,那么对应linux下(hd0,6)==>repo=hd:/dev/sda7:/
开机进入grub,输入以下命令
loopback loop (hd0,5)/Fedora-19-i386-DVD.iso
linux (loop)/images/pxeboot/vmlinuz linux repo=hd:/dev/sda5:/
initrd (loop)images/pxeboot/initrd.img
通过上面的分析,可以得出从硬盘安装方法。
假设,我已经安装了grub。ubuntu-10.10-desktop-i386.iso 为了方便,用ubuntu.iso代替,并放在(hd0,3)里。
进入grub2命令行。
loopback loop (hd0,3)/ubuntu.iso
这是利用grub的回放设备,挂iso,这样可以使你不用把casper文件夹提取出来了。
set root=(loop)
这是设置grub的根目录。
linux /casper/vmlinuz boot=casper iso-scan/filename=/ubuntu.iso
这是让grub挂内核。并传递参数boot=casper 给initramfs
initrid /casper/initrid.lz
boot
命令介绍:
ls:
ls (hd0,5)/ ==> 显示该分区的所有
列出当前的所有设备。如(hd0),(hd0,1),(hd0,5),(hd1),(hd1,1),(hd1,2)等
ls -l 详细列出当前的所有设备。对于分区,会显示其label及uuid。
ls / 列出当前设为root的分区下的文件
ls (hd1,1)/ 列出(hd1,1)分区根目录的文件
root: 指定用于启动系统的分区(也就是根目录 /)。(set root 就是设置root这个环境变量的值)
root (hd0,x)==>挂载硬盘0的 第X个分区,你可以通过ls来查看这个分区的内容
grub2 同样以 fd 表示软盘, hd 表示硬盘(包含 IDE 和 SCSI 硬盘)。
设备是从 0 开始编号,分区则是从 1 开始,主分区从 1-4,逻辑分区从 5 开始,而 grub 分区编号是从 0 开始的。
下面给出几个例子 : (fd0) :表示整个软盘 (hd0,1) :表示 BIOS 中的第一个硬盘的第 1 个分区 (hd0,5)/boot/vmlinuz :表示 BIOS 中的第一个硬盘的第一个逻辑分区下的 boot 目录下的 vmlinuz 文件
loopback:
loopback loop (hd0,3)/ubuntu.iso
这是利用grub的回放设备,挂iso,这样可以使你不用把casper文件夹提取出来了。
search
search -f /ntldr 列出根目录里包含ntldr文件的分区,返回为分区号
search -l LINUX 搜索label是LINUX的分区。
search --set -f /ntldr 搜索根目录包含ntldr文件的分区并设为root,注意如果多个分区含有ntldr文件,
set失去作用。
loopback
loopback命令可用于建立回放设备,如
loopback lo0 (hd1,1)/abc.iso 可以使用lo0设备来访问abc.iso里的内容,比如说,可以从abc.iso里的软盘映像中启动
loopback lo0 (hd1,1)/aa.iso
linux (lo0)/memdisk
initrd (lo0)/abc.img
要删除某一回放设备,可以使用-d参数:
loopback -d lo0
linux
用linux命令取代grub中的kernel命令,kernel 是Grub的一个命令,用来加载Linux内核
kernel /boot/bzImage ro ramdisk_size=65536 root=/dev/ram0
指定根设备是通过内核参数“root=”来完成的
linux /boot/vmlinuz-XXX root=UUID=XXX【告诉内核,从哪里加载必要的一些文件,如果是ISO镜像启动,那肯定这些文件就在这个镜像所在的磁盘分区上,这时指定这个磁盘分区就好了,如果是硬盘上已装的系统,一般指定系统所在路径即可】
linux 是一个指令,它告诉 grub2 程序:“请使用后面给出的这个内核”,于是名为“/boot/vmlinuz-XXX”的内核将被载入内存,并读取“root=UUID=XXX”这个分区的一些必需的文件。【这里传入的内核参数root=UUID=XXX 也可以用 /dev/ram0 这种格式,这里这个路径 是从linux角度出发】
initrd /boot/initrd.img-XXX
initrd 也是一个指令,意思是:先用名称为“/boot/initrd.img-XXX”的文件,在内存中构造一个“虚拟”的根文件系统,以便于内核检测完硬件信息后载入必需的模块等等。
其实这个 initrd 也可以认为是内核的一部分,只是为了保持 vmlinuz 的简洁而分离出来的,有的内核甚至根本不需要这个东东
kernel是什么以及有哪些功能相信大家都很清楚,我就不多说了。这里主要介绍ubuntu10.04上安装的相关软件包、文件、目录以及kernel的配置方法。
至于initrd文件是干什么用的呢?
大家只要做一个简单的实验就可以明白了:在开机的GRUB命令行上去掉initrd这一行,然后按CTRL+x启动,过会你会发现屏幕出现“kernel panic”的提示。
为什么会这样呢?这是因为为了减小kernle文件(vmlinuz)的大小和增强灵活性,现在的发行版如ubuntu默认将硬盘接口卡如IDE,STAT,SCSI的驱动程序以模块(modules)的形式
放在initrd文件中,GRUB中去掉initrd这一行意味着initrd中的modules没有被加载kernel就不能识别该硬盘更别提挂载上面的根文件系统了。
initrd文件的功能主要有两个:
1、提供开机必需的但kernel文件(即vmlinuz)没有提供的驱动模块(modules)
2、负责加载硬盘上的根文件系统并执行其中的/sbin/init程序进而将开机过程持续下去【内核先以initrd /boot/initrd.img-XXX 的内存盘为根文件系统,再去加载硬盘上的根文件系统】
GRUB将kernle加载到内存并执行,kernel在运行的后期会读取并执行initrd文件中的init脚本文件并按照其中命令逐行执行,所以要掌 握initrd的作用和硬盘上的根文件系统加载过程,全面解析init文件是很重要的。在本文的第二部分,我会将/boot/initrd文件打开,逐行 解读init文件.
initrd 详细的介绍:
initrd /boot/initrd.img-XXX,它是由 bootloader 初始化的内存盘。在 linux 启动之前,bootloader 会将它(通常是 initrd.img-xxx...xxx 文件)加载到内存中。内核启动的时候会将这个文件解开,并作为根文件系 统使用。而启动阶段的驱动模块(如jbd)放在这些文件系统上,内核是无法读取文件系统的,从而只能通过Linux initrd启动的虚拟文件系统来装载这些模块。
chainloader
调用另一个启动器,如
chainloader (hd0,1)+1
调用第一硬盘第一分区引导扇区内的启动器,可以是windows或linux的启动器
menuentry
'Fedora iso'{
setroot='hd1,msdos4'
search
--no-floppy --fs-uuid --set=root
1fec4ef0-a925-4711-8de8-c3a332b315c3
loopback lo
/Fedora-18-x86_64-DVD.iso
linux (lo)/images/pxeboot/vmlinuz
repo=hd:/dev/sda4:/ 【可用root=UUID=xxx 用ls -l 看你这个系统所在的磁盘分区的UUID】
initrd (lo)/images/pxeboot/initrd.img
boot
}
1、需要注意的是,我的ISO文件在机器的sda4的硬盘分区内,而我是通过写U盘中的grub2来引导的。U盘中没有Fedoara18 ISO文件,在机器的硬盘内。U盘中只有grub2。
举例说明
### BEGIN /etc/grub.d/00_header ###
load_env
#加载变量,如果在grubenv保存变量,则启动时装载
set default="0"
#设置默认引导项,默认值为0
insmod ext2
#插入文件系统支持的模块,除了用作启动的分区外,其他分区格式可在menuentry底下再添加
set root=(hd0,8)
# 指定系统root分区,也就是 / 分区
search --no-floppy --fs-uuid --set 2d61e5f9-1d2a-4167-a6f1-b991ba00878b
#指定uuid=2d61e5f9-1d2a-4167-a6f1-b991ba00878b的分区为root分区,如果前面的分区号(hd0,8)的#uuid与这里的uuid一致,这两句作用一样,如果不一致,则指定uuid的起作用。
if loadfont /usr/share/grub/unicode.pf2 ; then
#设置终端字体,unicode.pf2支持中文字符显示
set gfxmode=640x480
#设置显示分辨率,默认为640x480,可用800x600,1024x768,建议跟你想设定的图片大小一致
insmod gfxterm
#插入终端模块gfxterm,支持中文字符显示和支持24位图像
insmod vbe
#插入vbe模块,GRUB2引入模块化机制,要使用它,需要在这里加入
if terminal_output gfxterm ; then true ; else
# For backward compatibility with versions of terminal.mod that don't
# understand terminal_output
terminal gfxterm
#设定grub2终端为gfxterm
fi
fi
if [ ${recordfail} = 1 ]; then
set timeout=-1 # 若有启动失败的记录,则菜单项不再倒计时
else
set timeout=10 #倒计时10秒后进按默认启动项启动
fi
#设定默认启动前等待时间,默认为10秒
### END /etc/grub.d/00_header ###
### BEGIN /etc/grub.d/05_debian_theme ###
set menu_color_normal=white/black
#设定菜单字体及背景颜色
set menu_color_highlight=black/blue
#设定选择项字体及背景颜色
#如果使用默认,背景将完全被蓝色挡住了,需要修改blue为black,背景图片才能显示
### END /etc/grub.d/05_debian_theme ###
### BEGIN /etc/grub.d/10_linux ###
#10_linux为系统自动添加的当前root分区linux引导项
#每个菜单项要包括menuentry双引号" "和大括号{ }才完整,否则不显示菜单
menuentry "Ubuntu, Linux 2.6.31-10-generic" {
set quiet=1
insmod ext2
set root=(hd0,8)
search --no-floppy --fs-uuid --set 2d61e5f9-1d2a-4167-a6f1-b991ba00878b
#这句与set root=(hd0,8)效果一样,可删除其一,二者不一致以这句为准
linux /boot/vmlinuz-2.6.31-10-generic root=UUID=2d61e5f9-1d2a-4167-a6f1-b991ba00878b ro quiet splash
#不喜欢看到一长串的, roo=UUID=***可用root=/dev/sda8(/分区的分区号)代替
initrd /boot/initrd.img-2.6.31-10-generic
}
…
### END /etc/grub.d/10_linux ###
#20_memtest86+为系统自动添加的内存测试菜单项
### BEGIN /etc/grub.d/20_memtest86+ ###
menuentry "Memory test (memtest86+)" {
linux16 /boot/memtest86+.bin
}
menuentry "Memory test (memtest86+, serial console 115200)" {
linux16 /boot/memtest86+.bin console=ttyS0,115200n8
}
### END /etc/grub.d/20_memtest86+ ###
#30_os-prober或30_others为系统自动查找并添加其他系统菜单项,按windows,linux,macos顺序
#查找并添加,支持windows 7识别
### BEGIN /etc/grub.d/30_os-prober ###
### END /etc/grub.d/30_os-prober ###
### BEGIN /etc/grub.d/30_otheros ###
# This entry automatically added by the Debian installer for a non-linux OS
# on /dev/sda1
menuentry "Microsoft Windows XP Professional" {
set root=(hd0,1)
search --no-floppy --fs-uuid --set e852-230b
drivemap -s (hd0) $root
#对以ntldr引导的系统如win2000,xp,win2003,因其引导机制只能从第一硬盘启动,系统会自动添加#映射命令,对vista和win7就没有这句命令
chainloader +1
}
### END /etc/grub.d/30_otheros ###
#40_custom为自定义的启动项,如启动cdlinux
### BEGIN /etc/grub.d/40_custom ###
menuentry "CDLinux"{
set root=(hd0,8)
linux /CDlinux/bzImage root=/dev/ram0 vga=791 CDL_LANG=zh_CN.UTF-8
initrd /CDlinux/initrd
}
### END /etc/grub.d/40_custom ###
定制个性化的配置文件,可以加入背景图片,使用中文字符,让启动画面独具特色,而不是单调的黑、白、蓝三色。下面是一张经美化启动画面:
kernel被GRUB加载经内存并执行后会进一步加载initrd文件,它是按照initrd文件中提供的init脚本一步步执行的,所以要掌握initrd文件的执行过程就必须要搞清楚init文件的内容和作用。下面我会重点介绍init文件。
1、首先什么是 initial ram disk (缩写 ) initrd
它是由 bootloader 初始化的内存盘。在 linux 启动之前,bootloader 会将它(通常是 initrd.img-xxx...xxx 文件)加载到内存中。内核启动的时候会将这个文件解开,并作为根文件系 统使用。而启动阶段的驱动模块(如jbd)放在这些文件系统上,内核是无法读取文件系统的,从而只能通过Linux initrd启动的虚拟文件系统来装载这些模块。这里有些人会问: 既然内核此时不能读取文件系统,那内核的文件是怎么装入内存中的呢?答案很简单,Grub是file-system sensitive的,能够识别常见的文件系统。
2、设 计 initrd 的主要目的
目的是让系统的启动分为两个阶段。首先,带有最少但是必要的驱动(这些驱动是在配置内核时选择嵌入方式)的内核启动。然后,其它需要的模块将 从 initrd 中根据实际需要加载(使用udev机制,最重要的根文件系统所在硬盘的控制器接口module)。这样就可以不必将所有的驱动都编译进内核,而根据实际情况有选择地加载。对于启动较慢的设备 如 usb 设备等,如果将驱动编译进内核,当内核访问其上的文件系统时,通常设备还没有准备好,就会造成访问失败。所以,通常 在 initrd 中加载 usb 驱动,然后休眠几秒钟,带设备初始化完成后,再挂载其中的文件系统。
3、系统上安装的相关软件包
geekard@geekard-laptop:~$ dpkg -l \*ini\* |grep ii
ii busybox-initramfs 1:1.13.3-1ubuntu11 Standalone shell setup for initramfs
ii initramfs-tools 0.92bubuntu78 tools for generating an initramfs
ii initramfs-tools-bin 0.92bubuntu78 binaries used by initramfs-tools
geekard@geekard-laptop:~$
BusyBox combines tiny versions of many common UNIX utilities into a single small executable. It provides minimalist replacements for the most common
utilities you would usually find on your desktop system (i.e., ls, cp, mv, mount, tar, etc.). The utilities in BusyBox generally have fewer options than
their full-featured GNU cousins; however, the options that are included provide the expected functionality and behave very much like their GNU
counterparts.
initramfs-tools-bin软件包提供initrd文件制作工具命令,但更好的方法是手动制作(下面我会详细介绍)。
4、initrd 的具体形式
目前有两种形式:cpio-initrd 和 image-initrd。
image- initrd 的制作相对麻烦,处理流程相对复杂(内核空间->用户空间->内核空间 与初始化越来越多的在用户空间进 行的趋势不符),主要是2.4及以前的kernle使用,本文不对其进行介绍。
cpio- initrd 的处理流程(内核空间->用户空间):
1. boot loader 把内核以 及 initrd 文件加载到内存的特定位置。
2. 内核判断 initrd 的文件格式,如果 是 cpio 格式。
3. 将 initrd 的内容释放 到 rootfs 中。
4. 执行 initrd 中 的 /init 文件,执行到这一点,内核的工作全部结束,完全交给 /init 文件处 理。
cpio- initrd 的制作:
首先在一个目录中建立必要的文件及目录。例如:
song@ubuntu:/home/linux_src/initrd/debian_etch/initrd$ ls -l
总用量 5
drwxr-xr-x 2 song song 864 2007-05-01 21:37 bin
drwxr-xr-x 3 song song 160 2007-05-01 21:37 conf
drwxr-xr-x 4 song song 136 2007-05-01 21:37 etc
-rwxr-xr-x 1 song song 3233 2007-05-02 15:16 init
drwxr-xr-x 4 song song 416 2007-05-01 21:37 lib
drwxr-xr-x 2 song song 48 2007-04-14 15:59 modules
drwxr-xr-x 2 song song 208 2007-05-01 21:37 sbin
drwxr-xr-x 11 song song 400 2007-05-01 21:37 scripts
然后,将这些内容打成 gzip 压缩过的 cpio 包:
song@ubuntu:/home/linux_src/initrd/debian_etch/initrd$ find . | cpio -o -H newc | gzip -9 >../initrd.img.gz
20500 blocks
song@ubuntu:/home/linux_src/initrd/debian_etch/initrd$ ls -l ../initrd.img.gz
-rw-r--r-- 1 song song 4493175 2007-05-02 17:17 ../initrd.img.gz
initrd文件的解包:
首先建立一个空目录,然 后进入那个目录,并运行相应的命令。例如,在 /home/linux_src/initrd/debian_etch 目录下存 在 initrd.img-2.6.18-4-686 文件,我们现在要把它解开,过程如下:
song@ubuntu:/home/linux_src/initrd/debian_etch$ mkdir tmp
song@ubuntu:/home/linux_src/initrd/debian_etch$ cd tmp
song@ubuntu:/home/linux_src/initrd/debian_etch/tmp$ gzip -dc ../initrd.img-2.6.18-4-686 | cpio -idm
20500 blocks
song@ubuntu:/home/linux_src/initrd/debian_etch/tmp$ ls -l
总用量 5
drwxr-xr-x 2 song song 864 2007-05-02 17:23 bin
drwxr-xr-x 3 song song 160 2007-05-02 17:23 conf
drwxr-xr-x 4 song song 136 2007-05-02 17:23 etc
-rwxr-xr-x 1 song song 3213 2007-03-08 06:30 init
drwxr-xr-x 4 song song 416 2007-05-02 17:23 lib
drwxr-xr-x 2 song song 48 2007-04-14 15:59 modules
drwxr-xr-x 2 song song 208 2007-05-02 17:23 sbin
drwxr-xr-x 11 song song 400 2007-05-02 17:23 scripts
initrd 中 init 脚本的分析
由前面 cpio-initrd 的处理流程可以看到,内核在将其解开并放入 rootfs 后,将要执 行 /init 文件,所以我们分析的重点就是这个文件。其它的文件请结合具体的源码与本文的内容进行理 解。
#!/bin/sh
该行说明该init文件是一个由sh解释并执行的脚本文件,内核通过文件头来确定应该怎样执行(即是直接执行还是调用哪个程序执行该文件)。
[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir -m 0700 /root #这是硬盘上的根分区预先挂载到的目录
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
mkdir -p /var/lock
mount -t sysfs -o nodev,noexec,nosuid none /sys #udev会参考的vfs,udev会根据其中的信息加载modules和创建设备文件
mount -t proc -o nodev,noexec,nosuid none /proc
这一部分很简单,建立了相关目录和挂载点,并将kernel运行过程中产生的信息挂载到/sys和/proc目录下。注意/sys目录是udev会参考的 vfs,udev会根据其中的信息加载modules和创建设备文件,当不使用udev机制(我后面会讲)时/sys目录可以不建立。/proc目录和相 应的proc文件系统必须建立和挂载,因为脚本会参考其中的/proc/cmdline文件获得kernel命令行上的参数。
grep -q '\
如果在GRUB的kernel行上有quiet关键字,则在kernel启动和initrd中init脚本执行的过程中不会在屏幕上显示相关信息而是一个闪烁的下划线,否则将显示"Loading, please wait..."
# Note that this only becomes /dev on the real filesystem if udev's scripts
# are used; which they will be, but it's worth pointing out
if ! mount -t devtmpfs -o mode=0755 none /dev; then #其实then的代码一般不会执行
mount -t tmpfs -o mode=0755 none /dev
mknod -m 0600 /dev/console c 5 1
mknod /dev/null c 1 3
fi
这一部分在/dev目录下建立devtmpfs文件系统,devtmpfs是一个虚拟的文件系统被挂载后会自动在/dev目录生成很多常见的设备节点文 件,当挂载devtmpfs失败时会手动建立/dev/console和/dev/null设备节点文件。/dev/console 总是代表当前终端,用于输出kernel启动时的输出内容,在最后通 过 exec 命令用指定程序替换当前 shell 时使用。/dev/null 也是很常用的,凡是重定向到它的数据都将消失得无影无踪。
mkdir /dev/pts #主要用于nfs等启动时使用,对于本地/dev/pts不使用,故下面一段代码可忽略
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 none /dev/pts || true
> /dev/.initramfs-tools
mkdir /dev/.initramfs
usplash 会使用/dev/.initramfs 目录。usplash 会在机器启动的时候提供类似 windows的启动画面,ubuntu linux 的启动画面就是通过 usplash 实现的。由于 在 /sbin 目录当中没有任何 usplash 相关的文件,所以我们可以忽略这个目录的存 在。
# Export the dpkg architecture
export DPKG_ARCH=
. /conf/arch.conf
DPKG_ARCH 表明了当前运行linux的计算机的类型,对一般的pc是大多 i386,也可能是别的比如 powerpc 一类的。用export是为了让这个变量不仅在此 shell环境中有效,而且在它的子 shell环境中仍然有效。而且在第27行 export DPKG_ARCH 变量的时候,让 DPKG_ARCH 变量等于空。这样,当前运行的计算机 的类型就完全由 /conf/arch.conf 决定了
# Set modprobe env
export MODPROBE_OPTIONS="-qb"
设置modprobe默认的选项,-b 表示用use-blacklist(主要是系统的硬件有多个modules支持,选择使用哪一个,其他的加入到黑名单中以防冲突) ,-q表示quiet
# Export relevant variables
export ROOT= #从kernel中提取的realfs所在的设备
export ROOTDELAY=
export ROOTFLAGS=
export ROOTFSTYPE=
export IPOPTS=
export HWADDR=
export break=
export init=/sbin/init #realfs中的第一个执行的程序位置
export quiet=n
export readonly=y
export rootmnt=/root #realfs在rootfs中的临时挂载点
export debug= #将本脚本的输出定向到一个文件,以便启动系统后分析
export panic=
export blacklist= #设置modules的黑名单
export resume_offset=
这一部分主要是输出一些变量到环境中
ROOT 保存GRUB的kernel命令行上的root参数即硬盘上根分区所对应的设备节点文件
ROOTDELAY 指定将要进入的系统的根目录所在的分区必 须在多少秒之内准备好
ROOTFLAGS 指定将要进入的系统的根目录所在的分区挂 载到 ${rootmnt} 目录时的参数
ROOTFSTYPE 指定根分区所在的文件系统类型
IPOPTS 当kernel为nfs时指定的服务器IP
HWADDR 当kernel为nfs时指定的服务器MAC
break 由 maybe_break 函数使用。若 break 的值同 maybe_break 的第一个参数相同,则 maybe_break 函数调 用 panic 函数(注意 panic 函数和 panic 变量是不同 的)。 若 panic 变量为"0"(此 处是字符串,其内容是"0",不是整数), 则 panic 函数将重新启动机器。其他情况下(包括 panic 变量为空的情况)都将以交互的方式调出 shell,此shell的输入输出使用已经创建好的节点 /dev/console。
init 指定硬盘realfs中的第一个执行的程序位置, 此变量指定在这个脚本最后要执行的进程。 此处 /sbin/init 是系统上所有进程的父进程,负责开启其它进程。当然,你也可以把它换成其他的程序,甚至是 ls,不一定非要是 /sbin/init,虽然这样你的系统启动之后什么都不能做。
quiet=n 指定为非"y",会显示一些启动的状态信息;若指定为"y"则不显示这些信息。
readonly=y 如果 readonly 等于字符串"y",则以只读方式挂 载最终要进入的系统的根目录所在分区到 ${rootmnt} 目录,其他情况(包括 readonly 为 空)以读写方式挂载。
rootmnt=/root 指定硬盘上的realfs在rootfs中的临时挂载点
debug #将本脚本的输出定向到一个文件,以便启动系统后分析
panic 描述见 break参数的说明。
blacklist #设置modules的黑名单
resume_offset 一般用不到
# Bring in the main config
. /conf/initramfs.conf #最重要的是BOOT参数,定义了是本地启动还是nfs启动
for conf in conf/conf.d/*; do #对于不支持kernel命令行选项的bootloader很有用
[ -f ${conf} ] && . ${conf}
done
. /scripts/functions #这个脚本文件很重要,在其中定义了很多以后要用到的工具函数
在当前shell中引入主配置文件 /conf/initramfs.conf。 这个配置文件实际上是 mkinitramfs(8) 的配置文件,其中定义了一些变量,并赋予了适当的值,如 BOOT=local 则默认从本地磁盘启动(可以是可移动磁盘)。 BOOT 变量的值实际上是 /scripts 目录下的一个文件,可以是 local 或 是 nfs。在此 init 脚本挂载将要进入的系统的根目录所在分区的时候,会先读取并运 行 /scripts/${BOOT} 文件。 在这个文件中定义了 mountroot 函数,对于 local 启动和 nfs 启动 此函数的实现不同。这样通过对不同情况引入不同的文件,来达到同样名称的函数行为不同的目的。这就导致了具体挂载的行为和启动方式相关。
引入 /conf/conf.d 下的所有文件,注意在引入的时候用了 -f 参数判断,这样只有普通的文件才 会被引入,链接(硬链接除外)、目录之类的非普通文件不会被引入,当使用不支持命令行参数的开机引导
程序时,可以在该目录下建立各种参数设置文件。(Uubunt使用的GRUB支持kernel命令行参数,所以这个目录下就上面提到的两个文件initramfs.conf和arch.conf)
按照/scripts/init-top/ORDER文件的配置依次执行其下的脚本文件,这里最重要的是开启了udev daemon
udev 以 daemon 的方式启动 udevd,接着执行 udevtrigger 触发在机器启动前已 经接入系统的设备的 uevent,然后调用 udevsettle 等待,直到当 前 events 都被处理完毕。之后,如果 ROOTDELAY 变量不为空,就 sleep ROOTDELAY 秒以等待 usb/firewire disks 准备 好。
maybe_break modules
log_begin_msg "Loading essential drivers..."
load_modules
log_end_msg
load_modules按照/conf/modules文件的配置加载重要的module,由于使用了udev机制这一步其实是多余的,实际上/conf/modules文件并不存在。
maybe_break premount
[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/init-premount"
run_scripts /scripts/init-premount
[ "$quiet" != "y" ] && log_end_msg
这段代码从字面上理解是为接下来挂载将要使用的系统的根目录所在的分区作准备,/scripts/init-premount实际并不存在,所以这一步其实啥都没干。
maybe_break mount #这一步主要是执行/scripts/local中的mountroot函数,将realfs挂载到${rootmnt},在mountroot函数中最重要的是检测realfs的fytype
log_begin_msg "Mounting root file system..."
. /scripts/${BOOT}
parse_numeric ${ROOT} #这一步主要用于解析lilo启动管理程序传递的参数,对于GRUB用不到。
mountroot
log_end_msg
这一部分的功能主要是把硬盘上的realfs挂载到${rootmnt}特别重要,大部分的系统启动问题都是这一部分存在问题引起的。其中很大一部分的原因是硬盘控制器的module
没有被udev或上面的load_modules加载,导致kernel不能读取硬盘中的数据。
下面我们来看一下 /scripts/local 中定义的 mountroot 函数是如何工作 的。
首先,它通 过 run_scripts 函数执行 /scripts/local-top 目录下所有具有可执行权限的文件。在这个目录下有3个文件:lvm,mdrun 和 udev_helper。
lvm 是逻辑卷管理方面的脚本,我没有过(估计一般pc很少有人会用),而且其中调用的具有可执行权限的文件在此 initrd.img 中也不存在。因为这个脚本在运行的时候会先检查需要的文件是否存在,若不存在则退出,所以这个脚本相当于什么也没做。略过。
mdrun 是 raid 方面的脚本。它要求 udev_helper 先被执行(见第136行代码的说明)其中用到的具有可执行权限的文件在 此 initrd.img 中不存在。这等效于这个脚本不起作用。
udev_helper 脚本 mdrun 的先决条件,根据实际情况 ide-generic 模块可能会被加 载。
在这三个脚本执行过之 后,mountroot 函数会查看 ROOT 设备节点是否已经存在,如果不存在将等 ${ROOTDELAY} 秒。若在这段时间内 ROOT 设备节点没有出现则调 用 panic 函数重启机器或是生一 个交互 shell。
若 ROOT 设备节点已经存在,则查看 ROOTFSTYPE 变量是否为空。若不空, 则 FSTYPE 变量的值就是 ${ROOTFSTYPE};否则通过 eval 用 fstype 命令得到 ROOT 的分区格式。其中,fstype 命令会输 出 FSTYPE=blabla 类型的字符串,它跟在 eval 后面就相当于作了 FSTYPE=blabla 这样的赋值操作。如果经过这一步之后 ROOTFSTYPE 的值是 "unknown"(包括通过在 kernel 后添 加 rootfstype=unknown 参数和 fstype 输出的 FSTYPE=unknown),则 mountroot 函数调用 /lib/udev/vol_id 得到 分区的格式。此时,FSTYPE 的值仍有可能是 "unknown"。 如果是这样的话,在最后的 mount 操作就会失败。或许你会觉得这里要判断分区格式是不是很麻烦。是的,确实如此。但是要知道这 里的 mount 不会自己判断分区格式,所以要在参数中指定。
在得到了 FSTYPE 之后,mountroot 函数调用 run_scripts 函数运 行 /scripts/local-premount 下面具有可执行权限的文件。
在 /scripts/local-premount 目录中只有一个具有可执行权限的脚本 resume。此脚本负责在 计算机休眠后恢复休眠前的状态。若 resume 变量为空或者这个变量所指的设备不存在,则直接退出;否则,运 行 /bin/resume 恢复状态。
在这之后,mountroot 函数根据变量 readonly 确定是以只读还是读写的方式挂载,根 据 FSTYPE 变量加载适当得内核模块。在得到了所有必要的参数之后,通过 mount 命令将将要进入的 系统的根目录所在的分区挂载到 ${rootmnt} 目录下。
最后,mountroot 函数通过 run_scripts 函数执行 /scripts/local- bottom 下具有可执行权限的文件。由于在此目录下没有文件,所以这一步什么都没有做。
parse_numeric 函数 ( /scripts/functions 中定义)从它的注释中可以看出,这个是为了和 lilo 兼容而存 在的。由于现在一般用 grub 作为 bootloader,我们平常写的 root=/dev /hdxx,root=LABEL=xx...xx 或 root=UUID=x...x-...-xxx 的形式都会造 成此函数的直接返回,相当于什么都没有做。由于我没有用过 lilo,所以对于下面 lilo 的处理,我也不好说什 么。
小 结
好了,上面我已经说了这么多。那么,init 脚本究竟都作了什么呢?
首先,建立一些必要的文件夹作为程序工作的时候需要的目录或者必要的挂载点,以及必需的设备节点。
然后,根据提供的参数建立适当的设备节点并加载适当的内核模块,启动适当的进程(udevd)帮助我们完成这一步骤。当没有使用udev机制时应在/conf/modules中指明要加载的驱动。同时要自己建好相关的设备节点。
最后,在做完了这些乱七八糟的为挂载根目录及运行 /sbin/init 进程作准备的事情之后,调用 run- init 来运行 /sbin/init 从而启动我们的系统。
由以上可以看出Ubunt为了兼容各种不同的硬件配置将该init脚本写的非常繁琐,在实际应用中我们可以根据自己主机的情况定制该文件。