首页 > 代码库 > GNU GRUB 2.00 源码分析笔记,持续更新
GNU GRUB 2.00 源码分析笔记,持续更新
前言
很多运维类书籍或文章仅从系统管理者的角度讲解了 grub 的安装以及使用,
本篇博文则从 gnu grub 2.00 的源码入手,从开发者,以及系统底层运行机制的角度,分析 grub 是如何作为跨平台的“全面统一的引导加载程序”,来引导操作系统,加载 Linux 内核的过程等等,
部分内容参考了《深度探索 Linux 操作系统》一书中相关的内容(ISBN 978-7-11143901-1 )以及 gnu grub 项目官方站点的文档,并且加入自己分析源码时的笔记。
(由于个人 C 语言以及汇编语言知识水平有限,博文中的错误还请提出指正,不胜感激)
分析源码之前,需要从 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 源码目录的组织结构,采用下述规则来分离函数的声明,定义,以及调用:
grub-2.00/grub-core/kern/main.c 调用其它外部函数(没有在 main.c 中定义的)
grub-2.00/grub-core/kern/ 目录下的其它源文件,分别提供这些外部函数的定义;
grub-2.00/include/grub/ 目录下的头文件,则对应这些外部函数的声明;
下面使用 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 模块源文件,它们多数也是引用这个目录下的头文件;对于这些头文件的功能和内容不一一介绍,当需要解释某个符号常量,宏定义,以及函数声明时,我们会给出相应的头文件内容摘录(如果有的话);
/* 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 限定)
第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 核心模块加载工作,限于篇幅,这里不详细介绍
第一个实参 (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() 类似,从其名称来看,应该是加载与配置信息相关的模块;判断模块类型是否为嵌入的配置模块,
本文出自 “自由,平等,共享,互助” 博客,请务必保留此出处http://shayi1983.blog.51cto.com/4681835/1582288
GNU GRUB 2.00 源码分析笔记,持续更新