前言
很多运维类书籍或文章仅从系统管理者的角度讲解了 GRUB 的安装以及使用,
本篇博文则从 GNU GRUB 2.00 的源码入手,从开发者,以及系统底层运行机制的角度,分析 GRUB 是如何作为跨平台的“全面统一的引导加载程序”,来引导操作系统,加载 Linux 内核的过程等等,
部分内容参考了《深度探索 Linux 操作系统》一书中相关的内容(ISBN 978-7-11143901-1 )以及 GNU GRUB 项目官方站点的文档,并且加入自己分析源码时的笔记。
(由于个人 C 语言,汇编语言,以及英语知识水平有限,博文中的错误还请提出指正,不胜感激)
下面的内容翻译自 http://www.gnu.org/software/grub/index.html
这是 GNU GRUB 2 项目的官方站点,通过这些信息,可以对 GNU GRUB 2 及其历史有个大体上的了解:
GNU GRUB
Introduction
简介
GNU GRUB is a Multiboot boot loader. It was derived from GRUB, the GRand Unified Bootloader, which was originally designed and implemented by Erich Stefan Boleyn.
Briefly, a boot loader is the first software program that runs when a computer starts. It is responsible for loading and transferring control to the operating system kernel software (such as the Hurd or Linux). The kernel, in turn, initializes the rest of the operating system (e.g. GNU).
GNU GRUB 是一个支持多重启动的引导加载器, 它源自于 GRUB ,全面统一的引导加载程序。后者最初由 Erich Stefan Boleyn 设计并实现。
简而言之,一个引导加载程序是计算机系统启动后,第一个运行的软件程序。
它负责加载并且将对 CPU 的控制权转交给一个操作系统内核软件(例如 Hurd 或 Linux)。而由内核再初始化操作系统剩余的部分。
GRUB Development
GRUB 的开发
GRUB 2 has replaced what was formerly known as GRUB (i.e. version 0.9x), which has, in turn, become GRUB Legacy. Enhancements to GRUB are still being made, but the current released versions are quite usable for normal operation.
GRUB Legacy is no longer being developed. For the differences between GRUB Legacy and GRUB, see the Grub Legacy Documentation.
GRUB 2 用于取代被大家熟知的 GRUB (例如 centos 6.x 默认安装的 GRUB 0.97 版),0.9x 版本改称为“传统的”GRUB
当前仍在增强 GRUB (GRUB 2 ) 的某些功能,不过当前的发布版已经能符合普通操作的需求
传统 grub 已经停止开发,阅读传统 GRUB 文档,可以了解传统 GRUB 与 GRUB (GRUB 2) 的差异
Useful and Historical Links
与GRUB 历史相关以及有用的链接
PUPA was a research project to develop the next generation of what is now GRUB Legacy.
Etherboot is a free software package for booting x86 PCs over a network. We are using its device drivers for our own netboot support.
The original GRUB site.
PUPA 是一个对当前的传统 GRUB 进行下一×××发的研究计划,PUPA 项目站点:
Etherboot 是一个通过局域网启动 X86 架构 PC 的免费软件,GRUB 使用它的设备驱动模块来实现自身的网络启动支持功能。
Etherboot 项目站点:http://www.etherboot.org/
最初的 GRUB 站点:http://www.uruk.org/grub/
Obtaining GRUB
获取 GRUB
GRUB 2 is now available via ftp. You can download releases from the ftp site ftp://ftp.gnu.org/gnu/grub.
All the development is done through a Git repository. If you are interested in the cutting-edge version of GNU GRUB, for example, to test a newer version or to add new features, we strongly recommend giving the latest repository version a try. You can obtain the latest GRUB source from the GIT:
git clone git://git.savannah.gnu.org/grub.git
For developers with write access via ssh, use:
git clone <membername>@git.sv.gnu.org:/srv/git/grub.git
GRUB 2 当前可以通过 ftp 下载获取。您可以从 ftp 站点 ftp://ftp.gnu.org/gnu/grub 下载发布版。
所有的开发工作均是通过 Git (一种分布式版本控制系统)仓库完成。如果您对最新版本的 GNU GRUB 有兴趣,例如,调试以及添加新特性,强烈建议您尝试
最新版的软件仓库。您可以通过如下 Git 获取最新的 GRUB 2 源码:
git clone git://git.savannah.gnu.org/grub.git
要以 GRUB 2 的开发成员通过 ssh 进行写(编辑源码)访问,使用:
git clone <membername>@git.sv.gnu.org:/srv/git/grub.git
GNU GRUB FAQ
1. Why do you need to rewrite GRUB?
Because GRUB Legacy has become unmaintainable, due to messy code and design failures. We received many feature requests, and extended GRUB beyond the original scope, without redesigning the framework. This resulted in the state that it was impossible to extend GRUB any further without rethinking everything from the ground.
2. What is the status of GRUB 2?
It is quite usable, but we are still making incompatible changes from time to time. The current release is working on Intel/AMD PCs, OpenFirmware-based PowerPC machines (PowerMac and Pegasos), EFI-based PC (IntelMac) and coreboot (formerly, LinuxBIOS), and is being ported to UltraSparc.
为何需要重写 GRUB ?
由于凌乱的代码与失败的设计,传统 GRUB 变得难以维护。我们(指开发者社区)接收到许多建议改善其特性的请求,
包括在不重新设计 GRUB 整体框架的前提下,在原始版本范围外对其进行扩展。
这导致不得不从根本上重新审视,以便进一步扩展 GRUB
GRUB 当前的状态如何?
相当实用,不过我们在开发过程中,仍旧会偶尔引入一些不兼容的问题。当前的发布版本可以在下列计算机上正常运作:
基于 Intel/AMD 处理器体系结构的计算机;
基于开源固件的 PowerPC 机器(PowerMac 与 Pegasos);
基于 EFI 的计算机(IntelMac);
coreboot (以前的 LinuxBIOS);
并且已经移植到 UltraSparc 计算机上
Summer of Code Ideas for GNU GRUB
This page is dedicated to providing our ideas for GNU GRUB in Google's Summer of Code. GNU GRUB takes part in this event, as a part of the GNU Project. Please look at GNU guidelines for Summer of Code projects for more information on the participation of the GNU Project. If you are going to apply this project, please take a look at Advice for students.
这个页面专用于提供我们的 GNU GRUB 项目在谷歌编程夏令营的设计思路。
GNU GRUB 作为 GNU 项目的一部分参与此次活动。
请参考 GNU 的谷歌编程夏令营指南,以获取更多关于参与 GNU 项目的信息。(http://www.gnu.org/software/soc-projects/guidelines.html)
如果你要以(GNU 的名义)申请参与此次活动,请浏览给学生的建议。(http://www.gnu.org/software/grub/grub-soc.html#advice)
GRUB, the GRand Unified Bootloader, has been rewritten from scratch for more portability and more flexibility. The next major version of GRUB will be GRUB 2. All of our ideas are for GRUB 2 to replace the older GRUB, which we've dubbed GRUB Legacy.
GRUB,统一引导加载程序,已经过完全的重写,具有更好的移植性与灵活性。
GRUB 的下一个主要版本为 GRUB 2,我们所有关于 GRUB 2 的设计思想,其目的都是为了替换老版本的 GRUB,我们将后者称为传统 GRUB .
USB support
USB 支持
A commonly-found problem when using GRUB is that it can't access USB devices (storage or keyboard) due to BIOS limitations.
Because adding support for all types of USB controllers is a lot of work, perhaps too much for a single Summer of Code project, we prefer if UHCI is supported first, because this is the one that can be emulated with QEMU, and therefore easier to work with (and to verify the results).
Once we have a driver for the UHCI controller, we need support for either USB keyboard or storage device (or both, if time permits), so that we can verify completeness of the resulting work.
使用 GRUB 时最常见的问题就是,由于 BIOS 的限制,它不能访问 USB 设备。
(现在多数主板的新型 BIOS, 如 UEFI ,都可以识别 USB 设备,因此,只要 BIOS 能识别,GRUB 就可以识别)
因为添加对所有类型的 USB 总线控制器的支持,需要大量的工作,或许这在单季的谷歌编程夏令营中无法完成。
我们将优先考虑提供对 UHCI 的支持,因为 UHCI 是一个可以通过 QEMU 模拟的 USB 总线控制器,因此能更好地进行开发测试与验证结果。
一旦我们在 GRUB 中添加了 UHCI 控制器的驱动,我们需要尝试提供对 USB 键盘或者 USB 存储设备的支持,如果时间允许,我们将提供对两者的支持,
以便验证我们开发的 UHCI 驱动的完整性。
ATA (parallel and serial)
ATA(并行或者串行)
A commonly-found problem when using GRUB is that bugs in disk access facilities provided by the BIOS prevent it from operating correctly. Often this can be worked around, but sometimes it can't.
GRUB already has a (parallel) ATA driver, although it is incomplete, and currently only supports devices in legacy mode (such as the ones emulated by QEMU). We need to extend it so that it can interact with our PCI bus driver and support all kind of ATA drives.
Additionally, we need specific support for AHCI in order to use SATA drives, since not all of them provide legacy compatibility with purely PATA drivers.
用户在使用 GRUB 碰到的另一个常见的问题是,最早先的时候, GRUB 使用 BIOS 提供的服务来访问磁盘设备,但是 BIOS 的磁盘驱动有某些缺陷,这导致
GRUB 有时候不能正确地操作磁盘设备,
GRUB 当前已经开发出一种 PATA (并行 ATA)驱动,虽然它还不完整,而且当前只能在传统模式下支持相应设备(例如通过 QEMU 模拟的设备 ?)
我们需要对其进行扩展,从而让我们的 PATA 驱动可以与我们的 PCI 总线驱动进行交互,并且支持所有类型的 ATA 驱动器。
另外,为了让用户能通过 GRUB 识别他们的 SATA 硬盘,我们需要添加对 AHCI 的支持,因为并非所有类型的 BIOS 都提供对纯 PATA 硬盘的前向兼容性?
(不明白这句话的意思,是否指:GRUB 添加 AHCI 驱动后,既可识别 SATA 硬盘,也能识别老旧的“纯” PATA 硬盘 ?)
Regression testing framework
回归测试框架
It would be convenient to have a regression test framework to detect regressions automatically and report them to the developers.
Dejagnu support could be integrated in the build system, so that different tests can be performed. Some features could be tested right away by using grub-fstest or grub-emu. Other tests could be performed using QEMU, etc. It is up to the student to propose ideas to build an extensive testsuite.
In addition, a monitoring system could be developed (or adapted) to check periodically that GRUB can build and pass the testsuite on the different platforms it supports, reporting to the developers when a regression is found, together with details on who caused it and when.
如果有一个回归测试框架,自动检测分析并将其回报给开发者将会很方便。
可能在构建(指编译 GRUB ?)的系统中集成对 Dejagnu 的支持?以便执行不同的测试。
通过使用 grub-fstest 或 grub-emu ,可以立即测试部分 GRUB 的功能。其它的测试可以通过使用 QEMU 等来执行。
这就要靠学生们提出的主意来构建一个功能丰富的测试套件。
此外,可以开发(或改造)一个监控系统,用于周期性地确认 GRUB 在它支持的不同平台上能正确地构建(指编译 GRUB 源码 ?)并且通过测试套件的检查,当发现一个回归时,回报给开发者 ??汇总关于造成回归的原因,时间的详细信息 ???
Accessing encrypted partitions
访问加密的分区
Crypted partitions are an increasingly common feature provided by GNU/Linux distributions. GRUB users would benefit from the ability to access them from the GRUB shell (for example, so that it can load kernel p_w_picpaths or other add-ons from a crypted partition).
The LUKS specification describes a platform-independant method for laying out these partitions, and is today widely used. This would be the preferred encryption scheme to support, although others could be added if time permits (as long as they aren't encumbered by Treacherous Computing devices such as TPMs).
加密分区这一功能近来被越来越多的 GNU/Linux 发行版所支持,GRUB 的用户可以从 GRUB shell 中访问这些加密的分区,以便从一个加密的分区加载
内核映像或者其它插件,
LUKS 规范描述了一个用于展示加密分区的跨平台解决方案,如今被广泛使用。尽管日后可能添加其它显示加密分区的标准, GRUB 2 项目将优先考虑提供对 LUKS 的支持,只要它们(项目开发小组)不被类似 TPMs 这样的 Treacherous Computing 设备所阻碍 ???
(关于 LUKS 的更多详细信息,请访问 http://en.wikipedia.org/wiki/Linux_Unified_Key_Setup )
(关于 Treacherous Computing 的更多详细信息,请访问 http://www.gnu.org/philosophy/can-you-trust.html)
Localization infrastructure
本地化的基础设施
GRUB is already capable of displaying UTF-8 characters by means of the gfxterm interface. Based on this, we could add support for string localization without big readjustments. We need support for the utility components via gettext, and additionally some mechanism to support localization in GRUB itself. It would probably be a good idea to use the same interface as gettext, possibly even the same binary format.
GRUB 已经能够通过 gfxterm 接口来显示 UTF-8 字符集。在此基础上,我们可以添加对本地化(根据各国语言以及字符编码而异)字符串的支持,而不需要对整体架构进行过多的调整。
我们需要通过 gettext (编程 API ?)来支持实用工具组件,并且加入一些 GRUB 自身实现的支持本地化的机制,关于这一点,设计与 gettext 类似的接口会是一个不错的构想,甚至可能与前者相同的二进制格式(这不就是照抄了吗 ?)
Fancy menu interface
精美奇特的菜单接口
We are looking forward to seeing a very fancy menu implementation, which supports animations, colorful effects, style sheets, etc. This work should be based on the GRUB's Video API, and should share as much code as possible with the text-based menu interface.
This feature is really important for GRUB 2, because GRUB Legacy has been patched by third parties frequently, as the official version never support a graphical interface, but such an interface attracts more casual users. Support for a fancy menu - even better than an unofficial patch for GRUB Legacy - would attract more people to GRUB 2, thus this is critical in a long term to accelerate the development.
我们期待能看到一种非常精美的 GRUB 启动菜单实现,甚至能支持动画内容,五彩缤纷的颜色特效,样式表等等。
相关的开发工作应该基于 GRUB 的视频 API 完成,并且尽可能地与基于纯文本的菜单接口之间共享代码 ?
对于 GRUB 2 而言,这个特色尤其重要,因为许多第三方(非 GRUB 项目组?)组织经常修补传统 GRUB 的缺陷,官方版本从不提供图形化的接口,但是图形化的接口将吸引更多用户(用户都喜欢看到友善的图形界面)
提供一个比传统 GRUB 的非官方补丁版实现的图形界面更优秀,精美的启动菜单,将会吸引更多人使用 GRUB 2,这是一个在长期加快 GRUB 2 开发进程的关键因素。
Parted integration
集成 Parted (一个 GNU 项目的磁盘分区工具)
Integrate GNU Parted's library (libparted) with GRUB so that we can make use of the full power of the Parted's functionality at boot time.
Parted supports creation, deletion, resizing of partitions and filesystems. These features are extremely useful when you get troubled or have a complicated boot environment. Because libparted uses some external libraries, you will have to think how to make things run on the GRUB's environment, where you cannot use POSIX or Unix system calls.
This should be implemented as an optional dynamic module by using the module loading feature in GRUB 2.
Please note that there are also separate suggestions for projects relating to GNU Parted itself.
在 GRUB 中集成 GNU Parted 工具的共享库文件(libparted),从而在系统引导时,可以充分利用 Parted 工具的强大功能(对磁盘进行分区管理)
Parted 支持创建,删除,重设分区的大小,以特定文件系统类型格式化分区等操作。当在引导时遇到麻烦,或者处于一个复杂的引导环境时,利用上述这些功能可以帮助你解决问题。
由于 libparted 又使用一些其它的外部共享库文件,你也许会问,既然 Parted (libparted)依赖的这些库文件需要通过 POSIX 或 Unix 的系统调用(这里指动态链接 ?)才能加载,而此刻尚未加载任何操作系统,那么 GRUB 要如何利用 Parted 来管理磁盘分区 ?
可以通过使用 GRUB 2 中的“模块加载”功能,将所有 Parted 依赖的共享库文件,作为可选的动态模块来实现并加载,从而解决上述难题。
(也就是说, GRUB 2 通过自身实现的 “动态链接与加载”机制,不依赖于任何操作系统的相应机制,从而完整集成并支持 Parted 分区工具的使用 ?)
请注意,要查看与 GNU Parted 工具自身相关的建议,请访问:http://www.gnu.org/software/soc-projects/guidelines.html#projects-parted
New ports
新的移植计划
GRUB 2 is already ported to a few architectures, including PC/BIOS, Open Firmware (on both PowerPC and x86), EFI (x86) and coreboot/LinuxBIOS (on x86). A sparc64 port is in progress. The student is free to suggest a new port with a rough estimation what it will look like on this architecture. In this case the student should have such system to be able to work on this.
A new port is often a lot of work. Other suggestions can be working on ports for which some work already has been done. Like the Old World Apple, sparc, etc.
GRUB 2 已经移植到一些硬件架构上,包含 PC/BIOS ,开源固件(PowerPC 与 x86)?,EFI (x86) ,coreboot/LinuxBIOS (on x86)。
并且正在进行移植到 sparc64 平台上的工作,学生还建议将其移植到一个大致与该架构相同的平台上,因为这些学生有一种能在这个硬件平台上运行的操作系统(所以这些学生需要一个支持该平台的 bootloader 来引导该操作系统 ?)
一个新的移植计划,通常意味着大量的工作,其它的建议还包括将 GRUB 2 移植到那些老旧的硬件平台上,例如 Old World Apple ,sparc 等等 ??
《源码分析部分》
分析源码之前,需要从 https://ftp.gnu.org/gnu/grub/ 下载 grub 2.00 的 tar.gz 或 tar.xz 格式压缩的源码包,进入下载目录,建议将其解压至 /usr/src/
目录下:
[root@centos6-5vm tmp]# pwd /tmp [root@centos6-5vm tmp]# tar -zxvf grub-2.00.tar.gz -C /usr/src/ [root@centos6-5vm init.d]# cd /usr/src/ [root@centos6-5vm src]# ls debug grub-2.00 kernels linux-3.7.4 [root@centos6-5vm src]# cd ./grub-2.00/grub-core/ [root@centos6-5vm grub-core]# ls boot efiemu gdb_grub.in genmod.sh.in gettext hello lib Makefile.core.def modinfo.sh.in parttool unidata.c bus font genemuinitheader.sh gensyminfo.sh.in gfxmenu hook loader Makefile.gcry.def net script video commands fs genemuinit.sh gensymlist.sh gmodule.pl.in io Makefile.am Makefile.in normal term disk gdb genmoddep.awk gentrigtables.c gnulib kern Makefile.core.am mmap partmap tests [root@centos6-5vm grub-core]# cd ./kern/ [root@centos6-5vm kern]# pwd /usr/src/grub-2.00/grub-core/kern [root@centos6-5vm kern]# ls command.c device.c dl.c elf.c env.c file.c generic ia64 list.c main.c~ misc.c parser.c powerpc rescue_reader.c term.c vga_init.c corecmd.c disk.c efi emu err.c fs.c i386 ieee1275 main.c mips mm.c partition.c rescue_parser.c sparc64 time.c x86_64 [root@centos6-5vm kern]# cd ../../include/grub [root@centos6-5vm grub]# pwd /usr/src/grub-2.00/include/grub [root@centos6-5vm grub]# ls acorn_filecore.h cache.h dl.h fbutil.h gui_string_util.h libusb.h msdos_partition.h pciutils.h setjmp.h usb.h acpi.h charset.h efi file.h hfs.h list.h multiboot.h powerpc smbus.h usbserial.h aout.h cmos.h efiemu fontformat.h i18n.h loader.h multiboot_loader.h priority_queue.h sparc64 usbtrans.h *****此处省略其它头文件*****
总结上面简单的目录结构分析:
grub-2.00/grub-core/kern/ 目录下的 main.c 源文件,实现了 grub 引导菜单主界面的用户交互功能;其中的 grub_main(),它是 main.c 的主函数,当我们看到 grub 的引导菜单主界面时,CPU 执行的正是这个函数中的代码逻辑;
main.c 也就是编译后生成的 kernel.img 的核心部分;
至于整个 grub 2.00 的启动流程,它与 0.9x 版本的启动流程区别,以及包括 CPU
是如何执行到 kernel.img 中的逻辑(对 CPU 控制权的逐次转移),后面会详细解释,这里先将注意力集中在 main.c ,以及它调用的外部函数所在的其它源文件上。
grub 源码目录的组织结构,采用下述规则来分离函数的声明,定义,以及调用:
grub-2.00/grub-core/kern/main.c 调用其它外部函数(没有在 main.c 中定义的);
grub-2.00/grub-core/kern/ 目录下的其它源文件,分别提供这些外部函数的定义(实现了这些函数的具体功能);
grub-2.00/include/grub/ 目录下的头文件,则对应这些外部函数的声明;
另外, grub-2.00/grub-core/ 目录下的其它子目录,除了 kern 子目录外,有一些是 grub 2.00 的“模块”目录,例如 normal 子目录,loader 子目录等等,后面在代码中涉及具体源文件所在子目录时,再作介绍。
下面使用 gedit 文本编辑器来依序分析 main.c 文件内容,在实际分析时,应打开显示行号功能,方便引用代码所在行号,根据 gedit 的文档统计信息可知,这个 main.c ,其代码量为 250 行左右,对于源码分析而言完全可以“逐语句”详细分析:
[root@centos6-5vm kern]# pwd /usr/src/grub-2.00/grub-core/kern [root@centos6-5vm kern]# gedit main.c
下面省略了 main.c 源文件最开头的一些注释信息,包括 GNU 的 GPL (通用公共许可证)引用,以及 GRUB 免责声明等信息:
#include <grub/kernel.h>
#include <grub/misc.h>
#include <grub/symbol.h>
#include <grub/dl.h>
#include <grub/term.h>
#include <grub/file.h>
#include <grub/device.h>
#include <grub/env.h>
#include <grub/mm.h>
#include <grub/command.h>
#include <grub/reader.h>
#include <grub/parser.h>
main.c 包含的这 12 个头文件,全部位于 grub-2.00/include/grub/ 目录下,
并且,这意味着,main.c 中调用的所有外部函数,都是在
grub-2.00/grub-core/kern/ 目录下的与这些头文件同名的 C 源文件中定义;
今后我们要分析的其它 grub 模块源文件,它们多数也是引用这个目录下的头文件;对于这些头文件的功能和内容不一一介绍,当需要解释某个符号常量,宏定义,以及函数声明时,我们会给出相应的头文件内容摘录(如果有的话);
如前所述,在分析源码时,为了简化叙述的工作量,我们省略前面一连串引导并加载 GRUB 内核的代码与过程,跟随 grub 内核的执行逻辑,直接来到 main.c 中的 grub_main() ,通过这个小巧精简的函数(其背后隐藏着庞大复杂的函数调用层次结构),实现了基本的用户界面;解析配置文件;类 bash shell 的交互式命令行;实时编辑并传递操作系统内核引导参数。。。等等众多关键特性,而分析 main.c 的用意就在于窥探这些功能的内幕。
(文中引用到的函数调用层次图表,以及一些 GRUB 数据结构的示意图,来自于
http://www.sourcecodebrowser.com/grub2/1.99/index.html ,有兴趣的童鞋可以自行前往挖宝)
/* The main routine. */
void __attribute__ ((noreturn))
grub_main (void)
{
/* First of all, initialize the machine. */
grub_machine_init ();
/* Hello. */
grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
grub_printf ("Welcome to GRUB!\n\n");
grub_setcolorstate (GRUB_TERM_COLOR_STANDARD);
/* Load pre-loaded modules and free the space. */
grub_register_exported_symbols ();
#ifdef GRUB_LINKER_HAVE_INIT
grub_arch_dl_init_linker ();
#endif
grub_load_modules ();
/* It is better to set the root device as soon as possible,
for convenience. */
grub_set_prefix_and_root ();
grub_env_export ("root");
grub_env_export ("prefix");
grub_register_core_commands ();
grub_load_config ();
grub_load_normal_mode ();
grub_rescue_run ();
}
/* This is actualy platform-independant but used only on loongson and sparc. */
#if defined (GRUB_MACHINE_MIPS_LOONGSON) || defined (GRUB_MACHINE_MIPS_QEMU_MIPS) || defined (GRUB_MACHINE_SPARC64)
grub_addr_t
grub_modules_get_end (void)
{
struct grub_module_info *modinfo;
modinfo = (struct grub_module_info *) grub_modbase;
/* Check if there are any modules. */
if ((modinfo == 0) || modinfo->magic != GRUB_MODULE_MAGIC)
return grub_modbase;
return grub_modbase + modinfo->size;
}
#endif
上面这个代码段的起始注释信息表明,原则上 grub 是“平台无关”(platform-independant,即跨平台),但是如果 CPU 的体系结构是 loongson (龙芯; 由中科院计算技术研究所研制的、具有自主知识产权的高性能通用计算机CPU芯片)
或者 sun sparc ,则使用条件编译,定义一个叫做 grub_modules_get_end 的函数;
第6行声明一个结构体 grub_module_info
类型指针 modinfo
;
第8行将 modinfo
初始化为指向变量 grub_modbase;
(grub_modbase
是一个以关键字 extern 声明的 grub_addr_t 类型的外部变量,其在 grub-2.00/include/grub/kernel.h 中定义,如下所示:
extern grub_addr_t EXPORT_VAR (grub_modbase);
所有的 grub_*_t 类型变量,绝大多数都在 grub-2.00/include/grub/types.h 中定义;
grub_modbase
被强制类型转换为 grub_module_info
结构类型,然后将 modinfo指向这个结构变量的起始内存地址)
第11行判断,如果 modinfo 指向的结构体大小为零,或者
modinfo 指向的结构体的 magic 成员不等于符号常量
GRUB_MODULE_MAGIC
(还没有加载模块)
则 grub_modules_get_end() 返回 grub_modbase;与
符号常量
GRUB_MODULE_MAGIC
相关的宏定义,在 grub-2.00/include/grub/kernel.h 中,如下所示:
/* "gmim" (GRUB Module Info Magic). */
#define GRUB_MODULE_MAGIC 0x676d696d
这意味着,任何已加载的 grub 模块,其“魔术数”均为 0x676d696d
反之,第13行返回已有模块
+ 模块大小的信息;grub_modbase
继续分析 main.c 源文件:
/* Load all modules in core. */
static void
grub_load_modules (void)
{
struct grub_module_header *header;
FOR_MODULES (header)
{
/* Not an ELF module, skip. */
if (header->type != OBJ_TYPE_ELF)
continue;
if (! grub_dl_load_core ((char *) header + sizeof (struct grub_module_header),
(header->size - sizeof (struct grub_module_header))))
grub_fatal ("%s", grub_errmsg);
if (grub_errno)
grub_print_error ();
}
}
上面这个代码段定义一个叫做 grub_load_modules 的函数,该函数仅允许在 main.c 中使用,其它外部模块不能调用该函数(由关键字 static 限定)
后面我们将看到,main.c 的主要执行逻辑 grub_main() 中,将会调用这个函数;
第5行声明一个结构体 grub_module_header 类型指针 header,
第6行的 FOR_MODULES 是定义在
的grub-2.00/include/grub/kernel.h 中
代码段,
如下所示:
#define FOR_MODULES(var) for (\
var = (grub_modbase && ((((struct grub_module_info *) grub_modbase)->magic) == GRUB_MODULE_MAGIC)) ? (struct grub_module_header *) \
(grub_modbase + (((struct grub_module_info *) grub_modbase)->offset)) : 0;\
var && (grub_addr_t) var \
< (grub_modbase + (((struct grub_module_info *) grub_modbase)->size)); \
var = (struct grub_module_header *) \
((grub_uint32_t *) var + ((struct grub_module_header *) var)->size / 4))
由此可知,FOR_MODULES 在一个 for 循环中,依序读取要加载模块头部的“魔术数”,大小,偏移量等信息,并且将 header 指向存储这些信息的结构体的起始地址;
第8~10行通过读取 header 指向的结构成员 type 的值 ,判断模块类型,注释写的很清楚,如果模块类型不是 ELF 模块(对应的符号常量为 OBJ_TYPE_ELF),则跳过该模块,检查下一个模块,不执行后面的流程加载该模块;
第12行调用 grub_dl_load_core() 加载 grub-2.00/grub-core/ 目录下,除了 kern 子目录之外,所有其它子目录中的模块(编译后这些模块的位置可能发生改变,这里就是指加载所有 grub 核心模块);
grub_dl_load_core() 在
中声明,如下所示:grub-2.00/include/grub/dl.h
grub_dl_t grub_dl_load_core (void *addr, grub_size_t size)
grub_dl_load_core() 在 grub-2.00/grub-core/kern/dl.c 中定义,其中负责具体的 grub 核心模块加载工作,限于篇幅,这里不详细介绍。
调用 grub_dl_load_core() 时,
第一个实参 (char *) header + sizeof (struct grub_module_header) 匹配形参
void *addr;
第二个实参 (header->size - sizeof (struct grub_module_header) 匹配形参
grub_size_t size;
如果 grub_dl_load_core() 执行失败,返回的 grub_dl_t 类型的值为 0,经逻辑非运算后为 1,则第14行调用 grub_fatal() ,显示错误信息,无法加载模块;
第16~17行判断,如果 grub_errno 的值为 1,则调用 grub_print_error() 显示错误信息;
总之,grub_load_modules() 在一个 for 循环中,读取并记录每个可加载的模块的头部信息,然后调用 grub_dl_load_core() 加载这些负责 grub 核心功能的模块;
继续分析 main.c 源文件:
static void grub_load_config (void) { struct grub_module_header *header; FOR_MODULES (header) { /* Not an embedded config, skip. */ if (header->type != OBJ_TYPE_CONFIG) continue; grub_parser_execute ((char *) header + sizeof (struct grub_module_header)); break; } }
上面这个代码段定义一个叫做 grub_load_config 的函数,它的执行逻辑与 grub_load_modules() 类似,从其名称来看,应该是加载与配置信息相关的模块;判断模块类型是否为嵌入的配置模块,如果不是,则读取下一个;反之,则调用函数 grub_parser_execute;从该函数的字串 “parser” ,以及结合 grub 2.00 的目录结构规范来推测,
这个函数应该是在 grub-2.00/include/grub/parser.h 中声明;
grub-2.00/grub-core/kern/parser.c 中定义;确实如此。在
继续分析 main.c 源文件:
/* Write hook for the environment variables of root. Remove surrounding
parentheses, if any. */
static char *
grub_env_write_root (struct grub_env_var *var __attribute__ ((unused)),
const char *val)
{
/* XXX Is it better to check the existence of the device? */
grub_size_t len = grub_strlen (val);
if (val[0] == '(' && val[len - 1] == ')')
return grub_strndup (val + 1, len - 2);
return grub_strdup (val);
}
上面这个代码段定义一个叫做 grub_env_write_root 的函数,该函数以 static 关键字限定只能在 main.c 中调用;并且返回 char 型指针;从源码的注释来看,这个函数应该是写入 root 环境变量的钩子程序;该函数第一个参数为指向结构体 grub_env_var 起始地址的指针 var;第二个参数为指向传入的环境变量(字符型常量)的指针 val,注释提到,如果带圆括号, 应该将其移除(这样理解是否正确 ?)
结构体 grub_env_var 定义在 grub-2.00/include/grub/env.h 中;
第8行调用 grub_strlen() 取得 val 指向的字符串常量的长度(即传入的环境变量实参,组成该字符串常量的每个成员为 char 型,因此定义形参 val 指针时,仅能指向第一个成员字符),将 grub_strlen() 返回的值赋给 grub_size_t 类型的变量 len; grub_size_t 类型等同于 grub_uint64_t 类型,如前文所述,其宏定义在 grub-2.00/include/grub/types.h
中;
第10行判断,如果环境变量的第一个字符为左圆括号,并且倒数第二个字符为右圆括号(最后一个字符为 null 字符,即 '\0'),则返回以 val + 1 为第一个参数(这里是对指针进行加法操作,实质为引用环境变量的第二个字符);以 len - 2
为第二个参数,调用 grub_strndup() 的结果;从上下文语义来推测,不难看出,grub_strndup() 返回的字符串不带左右侧圆括号;
反之,如果传入的字符串本来就没有圆括号,那么直接将其作为参数调用 grub_strdup(), grub_strndup() 以及 grub_strdup(),定义在 grub-2.00/grub-core/kern/misc.c 中,这两个函数都会先调用 grub_malloc() 分配内存空间,然后调用 grub_memcpy() 将环境变量复制并存储到该区域;
最终,grub_env_write_root() 将返回指向调用 grub_strndup()
或者 grub_strdup() 返回的字符串的第一个字符的指针;
综上所述,grub_env_write_root() 的功能就是将环境变量作简单处理(截去左右侧圆括号与 null 字符),然后分配一块内存区域来存储环境变量;
继续分析 main.c 源文件:
static void
grub_set_prefix_and_root (void)
{
char *device = NULL;
char *path = NULL;
char *fwdevice = NULL;
char *fwpath = NULL;
char *prefix = NULL;
struct grub_module_header *header;
FOR_MODULES (header)
if (header->type == OBJ_TYPE_PREFIX)
prefix = (char *) header + sizeof (struct grub_module_header);
grub_register_variable_hook ("root", 0, grub_env_write_root);
if (prefix)
{
char *pptr = NULL;
if (prefix[0] == '(')
{
pptr = grub_strrchr (prefix, ')');
if (pptr)
{
device = grub_strndup (prefix + 1, pptr - prefix - 1);
pptr++;
}
}
if (!pptr)
pptr = prefix;
if (pptr[0])
path = grub_strdup (pptr);
}
if ((!device || device[0] == ',' || !device[0]) || !path)
grub_machine_get_bootlocation (&fwdevice, &fwpath);
if (!device && fwdevice)
device = fwdevice;
else if (fwdevice && (device[0] == ',' || !device[0]))
{
/* We have a partition, but still need to fill in the drive. */
char *comma, *new_device;
for (comma = fwdevice; *comma; )
{
if (comma[0] == '\\' && comma[1] == ',')
{
comma += 2;
continue;
}
if (*comma == ',')
break;
comma++;
}
if (*comma)
{
char *drive = grub_strndup (fwdevice, comma - fwdevice);
new_device = grub_xasprintf ("%s%s", drive, device);
grub_free (drive);
}
else
new_device = grub_xasprintf ("%s%s", fwdevice, device);
grub_free (fwdevice);
grub_free (device);
device = new_device;
}
else
grub_free (fwdevice);
if (fwpath && !path)
path = fwpath;
else
grub_free (fwpath);
if (device)
{
char *prefix_set;
prefix_set = grub_xasprintf ("(%s)%s", device, path ? : "");
if (prefix_set)
{
grub_env_set ("prefix", prefix_set);
grub_free (prefix_set);
}
grub_env_set ("root", device);
}
grub_free (device);
grub_free (path);
grub_print_error ();
}
上面这个代码段定义一个叫做 grub_set_prefix_and_root 的函数,该函数仅能在
main.c 中调用,而且没有返回值;没有参数;它是 main.c 中代码行最多的函数,
我们尝试对它逐行分析,进一步揣测开发者的意图:
第4~8行声明一系列初始化为 NULL 的指针,即没有指向任何内存地址;从这些指针的名字可以推测其含义;第9行声明一个 grub_module_header 结构类型指针 header;grub_module_header 结构类型定义在 grub-2.00/include/grub/kernel.h
中,如下所示:
/* 此处是 kernel.h 中的内容 */
/* The module header. */
struct grub_module_header
{
/* The type of object. */
grub_uint32_t type;
/* The size of object (including this header). */
grub_uint32_t size;
};
该结构体包含两个成员,一个 grub_uint32_t 型变量 type;另一个也是 grub_uint32_t 型变量 size;前者存储模块类型信息;后者存储模块大小信息;
第11行的 FOR_MODULES 是利用宏定义的代码段,参考前文;其实就是在一个 for 循环中,依序读取要加载的模块头部某个信息;
例如,第12行读取模块头部的类型信息并判断,如果类型是“前缀”(OBJ_TYPE_PREFIX),则第13行将 header 指向 grub_module_header 的结束地址,然后强制转换成 char 型指针,赋给 prefix;
第15行以 grub_env_write_root() 为参数调用函数 grub_register_variable_hook
,从后者的名称不难看出,这是一个用于注册 root 环境变量的钩子函数,它利用了 grub_env_write_root() 实现的存储环境变量的功能,进行注册;与环境变量有关的函数,例如 grub_register_variable_hook(),都定义在 grub-2.00/grub-core/kern/env.c 中;
第17行开始,通过 prefix 进行各种检查与操作,
首先在第19行,声明一个 char 型指针 pptr,将其初始化为 NULL,
第20行检查,如果 prefix 指向的第一个字符为左圆括号,
则在第22行将以 prefix 与右圆括号为参数调用 grub_strrchr() 的返回结果赋给 pptr,
第23行判断,如果 grub_strrchr() 成功返回,
则第25行将调用 grub_strndup() 对 prefix 与 pptr 的相关操作结果赋给 device 指针;
并且第26行将 pptr 自增1;如果 grub_strrchr() 返回错误,
则第30行将 pptr 指向与 prefix 相同的地址;
第31行检查,如果可以读取 pptr 指向的第一个字符,
第32行将以 pptr 为参数调用 grub_strdup() 返回的结果赋给 path 指针;
第34行继续检查前面执行的操作正确性:如果第25行调用 grub_strndup() 操作 prefix 与 pprt 时发生错误;或者 device 指向的第一个字符为逗号,或者无法读取 device 指向的第一个字符,或者第32行调用 grub_strdup() 发生错误(无法读取 path 指向的内容);
则第35行以 fwdevice 与 fwpath 指针的地址(指针的地址与指针指向的地址是两码事)为参数调用 grub_machine_get_bootlocation(),
第37行判断,如果第25行调用 grub_strndup() 出错,并且第36行调用 grub_machine_get_bootlocation() 成功返回(意味着可以读取 fwdevice 指向的内容),则第38行将 device 指向与 fwdevice 相同的地址;
如果不满足第37行的条件,那么第39行再判断,如果可以读取 fwdevice 指向的内容,并且 device 指向的第一个字符为逗号;或者不能读取 device 指向的第一个字符,则执行第42行~第66行的操作:
首先,通过第41行的注释可以推测出,指针 fwdevice 指向存储“启动分区”信息的内存区域;并且需要获取该分区所在磁盘(由 device 指针引用)的信息,这正是到第66行为止代码的意图;
限于篇幅,grub_set_prefix_and_root() 剩余的代码大家可以自行分析,
总结一下该函数的目的,无非就是解析 grub.conf 文件中,或者在 grub 交互式命令行中,用户指定的操作系统内核文件所在的磁盘与分区,在语法上有无错误,并且处理所有可能的错误情况,例如可以解析磁盘驱动器字串,但是无法解析磁盘分区字串,或两者都不能解析;从这个函数就可以看出, grub 的设计者尝试考虑并涵盖所有可能遇到的情况,并增加程序的健壮性;
继续分析 main.c 源文件:
转载于:https://blog.51cto.com/shayi1983/1582288