Linux 引导加载学习笔记
????????? 本文以是我的学习记录,其中一些文字和图片来自参考资料所列文档,感谢作者对其知识和分享!
?
????????? 最近在自学 Linux kernel 方面的东西,这两天了粗浅的研究了下 kernel boot 过程,在此记录。这里所指 Linux 引导加载未涉及虚拟化环境,即系统未运行在 hypervisor 之上。
?
????????? Linux 通过执行不同阶段的引导加载程序(boot loader)程序来引导操作系统,在完成内核等引导之后,最终会由调度器接管 CPU,其通过启用中断来周期性的抢占控制权,处理多个用户进程/客户进程(kvm 虚拟化)。Top level 的引导过程如下图。
?

?
?
????????? 整个 Linux 系统引导共分 5 步执行操作:
?
?
????????? 加电后首先被执行的是 BIOS (Base input/output system)程序。嵌入式环境使用 boot monitor,它负责在一个位于 rom/flash 中预定地址开始执行引导程序,而在 PC 环境中这个启动地址是 0xFFFF0,相对来讲 BIOS 提供了更多的配置功能。它主要由两部分组成:
?
????????? 当 BIOS POST 执行完后,其将会从内存中清理,而 Runtime 服务会常驻内存,为操作系统提供一些底层的支持。最后 BIOS 将控制权交给称为第一阶段引导程序的 MBR (Master boot record)程序。
?
????????? 接下来执行的 MBR 是一个512 byte 固定大小的映像。包括 446 byte 长的被称为初始程序加裁程序 (Initial program loader, IPL)的可执行代码和 64 byte 分区表(16 byte * 4 个),最后以 0xaa55 特殊字节结束。如下图所示。
?

?
????????? MBR 引导程序会将扫描分区表,获得唯一活动分区后,将其中的引导程序读入 RAM 并开始执行。
?
????????? MBR 启动的引导程序被称为第二阶段引导程序,它是引导的主体,是引导加载的真正部分。Linux 中该阶段有两个流行的程序,LILO (较老)和 GRUB。如果安装了 lilo 程序,可以通过 root 用户执行如下命令来通过 lilo 生成默认配置的 MBR ,并写入到启动磁盘 0 柱面 1扇区位置上。
?
# /sbin/lilo -v -v
?
????????? 一般需要修改 lilo 的配置文件,使生成的 MBR 有效。位于 /etc/lilo.conf 。lilo 配置示例。
?
boot=/dev/hdamap=/boot/mapinstall=/boot/boot.bprompttimeout=100compactdefault=Linuximage=/boot/vmlinuz-2.4.18-14label=Linuxroot=/dev/hdb3read-onlypassword=linuxother=/dev/hdalabel=WindowsXP
?
????????? boot 键指定了 lilo 在哪里安装 MBR。可以通过替换 boot=/dev/fd0 配置来指定 lilo 创建有引导记录的软盘。
?
????????? LILO 天生存在一些缺点和不足,因此 linux 在新版本中引入了 GRUB 程序。它为了从磁盘来加裁配置和 kernel 映像,不像 LILO 只能从裸扇区中执行引导程序,而具有了访问磁盘文件系统(ext2/3、reiserfs、jfs、fat 等)的能力。GRUB 是通过引入所谓 1.5 阶段的引导加载程序来实现这项功能的,在该阶段中,GRUB 主要来加载特殊的文件系统驱动。此后,阶段 2 的引导加载程序就可以进行加载了。
????????? 一般 GRUB 有一个不错的 GUI 界面,其中通过分析配置文件来显示了一此引导选项。在我的 ubuntu 8.10 系统中,该配置文件位于 /boot/grub/menu.lst。我们可以选择内核甚至修改附加内核参数,甚至可以使用 GRUB shell 对引导过程进行高级手工控制。我的 menu.lst 文件内容如下。
?
default 0timeout 3hiddenmenutitle Ubuntu 8.10, kernel 2.6.27-11-genericuuid e2cf53c5-11de-4d57-a532-878901afd9b4kernel /boot/vmlinuz-2.6.27-11-generic root=UUID=e2cf53c5-11de-4d57-a532-878901afd9b4 ro locale=zh_CN quiet splash initrd /boot/initrd.img-2.6.27-11-genericquiettitle Ubuntu 8.10, kernel 2.6.27-11-generic (recovery mode)uuid e2cf53c5-11de-4d57-a532-878901afd9b4kernel /boot/vmlinuz-2.6.27-11-generic root=UUID=e2cf53c5-11de-4d57-a532-878901afd9b4 ro locale=zh_CN singleinitrd /boot/initrd.img-2.6.27-11-generictitle Ubuntu 8.10, kernel 2.6.27-7-genericuuid e2cf53c5-11de-4d57-a532-878901afd9b4kernel /boot/vmlinuz-2.6.27-7-generic root=UUID=e2cf53c5-11de-4d57-a532-878901afd9b4 ro locale=zh_CN quiet splash initrd /boot/initrd.img-2.6.27-7-genericquiettitle Ubuntu 8.10, kernel 2.6.27-7-generic (recovery mode)uuid e2cf53c5-11de-4d57-a532-878901afd9b4kernel /boot/vmlinuz-2.6.27-7-generic root=UUID=e2cf53c5-11de-4d57-a532-878901afd9b4 ro locale=zh_CN singleinitrd /boot/initrd.img-2.6.27-7-generictitle Ubuntu 8.10, memtest86+uuid e2cf53c5-11de-4d57-a532-878901afd9b4kernel /boot/memtest86+.binquiet
????????? 将第二阶段的引导加载程序加载到内存中之后,就可以对文件系统进行查询了,并将默认的内核映像和 initrd 映像加载到内存中。当这些映像文件准备好之后,阶段 2 的引导加载程序就可以调用内核映像。 正如上面配置文件描述的那样,我的 ubuntu 启动会将加载 /boot/vmlinuz-2.6.27-11-generic (zImage/bzImage 格式的 kernel 映像)和 /boot/initrd.img-2.6.27-11-generic (cpio 格式的 initrd 映像)。
?
????????? 接下来就是 kernel 引导加载过程,这个过程包括如下 6 步。
?
????????? 上面第 5 步加载的 RAM 盘(initrd)是由阶段 2 引导加载程序加载到内存中的,RAM 盘内的微型系统用来加载必要的磁盘驱动内核模块,最终挂载真正磁盘的 root 文件系统。
?

?
????????? 引导加载的最后的一步就是执行 init (1 进程),该进程会根据配置来启动服务。一般的配置都会写在 inittab 里,不过我这里用的 ubuntu 使用的是 upstart,它是基于事件驱动的,发生什么 event 怎么处理,在这里 init 进程会产生 startup event, upstart 据此来启动 rc.* 配置的进程。不过无论如何,此时引导加载程序已经放权了。
?
这里抄录一段 LILO 与 GURB 的优缺点对比。
?
????????? 关于 kernel 和 initrd 两个映像。技术含量很高的,嵌入式开发中 bootloader 可是很大一块。值得深入,只可惜现在的技术水平,哎~
?
?
????????? initrd 映像是打包的 RAM 盘根文件系统。一般 initrd.img-2.6.27-7-generic 是一个 cpio 包文件,老版本也有 gzip 压缩格式的。通过 cpio 命令将其解包到当前目录中,如下。cpio 使用方法可参见 cpio 命令详解。
?
zcat initrd.img-2.6.27-11-generic | cpio -i -d --no-absolute-filenames?
????????? 在我这里解包后的根文件系统包括如下内容。
?

?
????????? 从上面的 directory tree 可以看到 initrd 中主要包括的就是磁盘、网络、文件系统的驱动 lkm 文件。其中还有最主要是的 init shell 脚本,它包括了初使化的全过程。
?
#!/bin/shecho "Loading, please wait..."[ -d /dev ] || mkdir -m 0755 /dev[ -d /root ] || mkdir -m 0700 /root[ -d /sys ] || mkdir /sys[ -d /proc ] || mkdir /proc[ -d /tmp ] || mkdir /tmpmkdir -p /var/lockmount -t sysfs -o nodev,noexec,nosuid none /sys mount -t proc -o nodev,noexec,nosuid none /proc # 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 outmount -t tmpfs -o mode=0755 udev /dev[ -e /dev/console ] || mknod -m 0600 /dev/console c 5 1[ -e /dev/null ] || mknod /dev/null c 1 3> /dev/.initramfs-toolsmkdir /dev/.initramfs# Export the dpkg architectureexport DPKG_ARCH=. /conf/arch.conf# Set modprobe envexport MODPROBE_OPTIONS="-Qb"# Export relevant variablesexport ROOT=export ROOTDELAY=export ROOTFLAGS=export ROOTFSTYPE=export break=export init=/sbin/initexport quiet=nexport readonly=yexport rootmnt=/rootexport debug=export panic=export blacklist=export resume_offset=# Bring in the main config. /conf/initramfs.conffor conf in conf/conf.d/*; do[ -f ${conf} ] && . ${conf}done. /scripts/functions# Parse command line optionsfor x in $(cat /proc/cmdline); docase $x ininit=*)init=${x#init=};;root=*)ROOT=${x#root=}case $ROOT inLABEL=*)ROOT="/dev/disk/by-label/${ROOT#LABEL=}";;UUID=*)ROOT="/dev/disk/by-uuid/${ROOT#UUID=}";;/dev/nfs)[ -z "${BOOT}" ] && BOOT=nfs;;esac;;rootflags=*)ROOTFLAGS="-o ${x#rootflags=}";;rootfstype=*)ROOTFSTYPE="${x#rootfstype=}";;rootdelay=*)ROOTDELAY="${x#rootdelay=}"case ${ROOTDELAY} in*[![:digit:].]*)ROOTDELAY=;;esac;;resumedelay=*)RESUMEDELAY="${x#resumedelay=}";;loop=*)LOOP="${x#loop=}";;loopflags=*)LOOPFLAGS="-o ${x#loopflags=}";;loopfstype=*)LOOPFSTYPE="${x#loopfstype=}";;cryptopts=*)cryptopts="${x#cryptopts=}";;nfsroot=*)NFSROOT="${x#nfsroot=}";;netboot=*)NETBOOT="${x#netboot=}";;ip=*)IPOPTS="${x#ip=}";;boot=*)BOOT=${x#boot=};;resume=*)RESUME="${x#resume=}";;resume_offset=*)resume_offset="${x#resume_offset=}";;noresume)noresume=y;;panic=*)panic="${x#panic=}"case ${panic} in*[![:digit:].]*)panic=;;esac;;quiet)quiet=y;;ro)readonly=y;;rw)readonly=n;;debug)debug=yquiet=nexec >/tmp/initramfs.debug 2>&1set -x;;debug=*)debug=yquiet=nset -x;;break=*)break=${x#break=};;break)break=premount;;blacklist=*)blacklist=${x#blacklist=};;esacdoneif [ -z "${noresume}" ]; thenexport resume=${RESUME}elseexport noresumefidepmod -amaybe_break top# export BOOT variable value for compcache,# so we know if we run from casperexport BOOT# Don't do log messages here to avoid confusing usplashrun_scripts /scripts/init-topmaybe_break moduleslog_begin_msg "Loading essential drivers..."load_moduleslog_end_msgmaybe_break premount[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/init-premount"run_scripts /scripts/init-premount[ "$quiet" != "y" ] && log_end_msgmaybe_break mountlog_begin_msg "Mounting root file system...". /scripts/${BOOT}parse_numeric ${ROOT}mountrootlog_end_msgmaybe_break bottom[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/init-bottom"run_scripts /scripts/init-bottom[ "$quiet" != "y" ] && log_end_msg# Move virtual filesystems over to the real filesystemmount -n -o move /sys ${rootmnt}/sysmount -n -o move /proc ${rootmnt}/proc# Check init bootargif [ -n "${init}" ] && [ ! -x "${rootmnt}${init}" ]; thenecho "Target filesystem doesn't have ${init}."init=fi# Search for valid initif [ -z "${init}" ] ; thenfor init in /sbin/init /etc/init /bin/init /bin/sh; doif [ ! -x "${rootmnt}${init}" ]; thencontinuefibreakdonefi# No init on rootmountif [ ! -x "${rootmnt}${init}" ]; thenpanic "No init found. Try passing init= bootarg."fi# Confuses /etc/init.d/rcif [ -n ${debug} ]; thenunset debugfi# Chain to real filesystemmaybe_break initexec run-init ${rootmnt} ${init} "$@" <${rootmnt}/dev/console >${rootmnt}/dev/console 2>&1panic "Could not execute run-init."?????????? Kernel 映像与 initrd 不同,它是个 zImage/bzImage 文件。通过 linux 编译脚本可以确认 zImage 实际上就是是由一个压缩后的内核(piggy.o),连接上一段初始化及解压功能的代码(head.o、misc.o)组成的。前面 kernel 引导加载过程中的硬件基本设置、设置基本环境(堆栈等)并清除BSS,直至解压内核都是 kernel 映像中压缩内核所连接的代码完成的。关于 kernel 映像这块还在研究、学习中。
?
学习的资料有如下文档,但不限于此。
?
整个的 Linux kernel 引导过程还在研究、学习中欢迎大家分享、指正。
最后再次对技术前辈的进取和谦虚致敬!
?
晚安~~
?
// 2009.02.20 22:16 添加 ////
?
????????? 更新了第二阶段引导过程中几处相关的内容,之前存在理解模糊的地方,今天再看的时候发现的。知识还是要多多重复、巩固。呵呵。
?
// 2009.03.07 13:23 添加 ////
作者:lzy.je
出处:http://lzy.iteye.com
本文版权归作者所有,只允许以摘要和完整全文两种形式转载,不允许对文字进行裁剪。未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
?