+ -
当前位置:首页 → 问答吧 → Grub 源代码分析

Grub 源代码分析

时间:2005-01-11

来源:互联网

这个主题源自westroom兄的意见。

我是第一次写这种“标题庄重”的技术文章,感觉好怕怕的,大家一起来写这个吧!
版本就用0.93的吧,我手头正好有这个版本。

其实总体上我们可以把grub看成一个mini os,他有shell,支持script,有文件系统……
我们可以把stage1 stage1-5看成一个boot loader,而stage2则是一个os,只不过这个os是专门load其他os的os,为此,stage2支持像kernel,initrd,chainloader等等为此目的而设置的内部“命令”……

(自己看书的时候常常跳过前言,觉得很多书的前言绪论实在让人厌烦,等看到正文的时候已经头晕目眩了,所以写这么多的介绍性文字已经是我的极限了,权当作抛砖引玉的作用,后面会陆续写些详细的分析文章,大家有兴趣可以一起写写)

作者: phyma   发布时间: 2005-01-11

1.简略流程
(如果你不熟悉grub,建议您先看完这这一节再向后)
a,Bios执行int 0x19,加载MBR至0x7c00并跳转执行。
如果你安装grub到mbr,grub的安装程序会把stage1(512B)拷贝到mbr。
视stage2的大小,安装程序会在stage1中嵌入stage1-5或者stage2的磁盘位置信息。
b,stage1开始执行,它在进行直接加载stage1-5或者stage2并跳转执行(还有些额外的工作,这里忽略先)
stage1-5很无辜,除了加载stage2以外它没有其他任何作用。
所以,不论是哪种情况,这一步的结果都是stage2开始运行了。
c,stage2这个mini os终于开始正式运行了!它会把系统切入保护模式,设置好c运行环境(主要是bss)
他会找config文件先(就是我们的menulist),如果没有的话就执行一个shell,等待我们输入命令。
然后grub的工作就是输入命令-解析命令-执行命令的循环,当然stage2本身也很无辜,他是为加载其他os
而存在的,所以如果情况允许,在他执行boot命令以后就会把控制权转交出去!


(已经尽量控制篇幅,还是超过预算,作为骨架性的一节,这应该是要常驻读者的“内存”的,所以还是显得太多了)

作者: phyma   发布时间: 2005-01-11

版主可以将此版本的Grub 源代码给放上来吗?或者给出下载的连接好吗
关注。

作者: officerlinux   发布时间: 2005-01-12

grub0.93
上传的附件
grub-0.93.part01.rar (150.0 KB, 1489 次查看)
grub-0.93.part02.rar (150.0 KB, 990 次查看)
grub-0.93.part03.rar (150.0 KB, 983 次查看)
grub-0.93.part04.rar (150.0 KB, 969 次查看)
grub-0.93.part05.rar (150.0 KB, 975 次查看)

作者: phyma   发布时间: 2005-01-12

grub0.93
上传的附件
grub-0.93.part06.rar (150.0 KB, 982 次查看)
grub-0.93.part07.rar (43.9 KB, 889 次查看)

作者: phyma   发布时间: 2005-01-12

顶一个先哈!

作者: westroom   发布时间: 2005-01-12

a,调用接口

类似系统调用,在stage2/disk_io.c中定义了grub_open,grub_close,grub_read,grub_dir
全局函数用于stage2的文件操作:打开,关闭,读,切换目录。

为了简化文件系统驱动的编写,grub不支持磁盘写(对于一个loader来说也没有必要去写磁盘)


b,文件系统驱动接口

任何一个文件系统驱动必须在fsys_table(stage2/disk_io.c)数组中去放置一个
struct fsys_entry的结构体,定义如下(stage2/filesys.h):
struct fsys_entry
{
char *name;//文件系统名称
int (*mount_func) (void);//初始化函数
int (*read_func) (char *buf, int len);//文件读函数
int (*dir_func) (char *dirname);//目录/文件打开函数
void (*close_func) (void);//文件关闭函数
int (*embed_func) (int *start_sector, int needed_sectors);//?大部分驱动设置此项为NULL
};


c,情景:读入文件

假设在grub的Menulist中有:kernel (hd0,0)/boot/vmlinuz(或者我们在grub shell中执行此命令)

stage2会先调用grub_open("(hd0,0)/boot/vmlinuz")来打开文件。
在执行这个函数中,grub会先在fsys_table中循环调用fsys_entry::mount_func去发现一个返回值为真的文件系统,即为当前的文件系统。然后利用当前文件系统驱动的fsys_entry::dir_func去打开/boot/vmlinuz
然后stage2会调用grub_read(buf,0)读入全部的/boot/vmlinuz文件至内存中的buf地址
grub_read是fsys_entry::read_func的封装。
最后stage2回调用grub_close关闭文件,跟前面一样,这个调用仅仅是当前文件系统fsys_entry::close_func的简单封装。

d,文件系统驱动

你可以在stage2/fsys_*.c找到各个文件系统驱动,当然你如果 对某个文件的结构熟悉,而grub中又没有相应的驱动,你可以安装b节中方法向grub中添加此文件系统的支持,比如NTFS,你只要实现mount(判断是否是你所支持的文件系统),dir(打开文件或者目录),read(读),close(关闭文件)功能就好。

作者: phyma   发布时间: 2005-01-12

顶一个先,希望能够继续下去

作者: redjing   发布时间: 2005-01-13

大部分情况我们是通过一个menulist文件来控制grub,这样grub会分析此文件的内容,然后显示一个菜单让我们选择。但是我不打算介绍grub stage2时怎样显示这个菜单的,这不是grub特殊的地方,这一节将对菜单选择执行的底层机制做小小的分析。(在grub没有找到menulist的时候,他就会执行一个shell,你完全可以通过这里内置的命令来控制grub,实际上你可以把menulist当作一个脚本文件,它也完全是通过内置命令执行的)

a,stage2流程

stage1或者stage1-5加载完stage2后,会跳转至stage2执行,stage2的入口是stage2/asm.S
asm.S在设置好c运行环境之后,会调用第一个c函数init_bios_info(stage2/common.c),这个函数在执行一些底层的初始化之后,会调用stage2的main函数cmain(stage2/stage2.c),这样stage2这个 mini os正式开始运行了!

针对menu.lst和shell这两种情况,cmain将:
menu.lst: run_menu()(stage2.c)->run_script()(cmdline.c)->find_command->执行命令函数
shell: enter_cmdline()(cmdline.c)->find_command->执行命令函数

殊途同归,最后都归结为命令行的解释执行
find_command(stage2/cmdline.c)按照menu.lst中或者shell用户输入的命令字符串,在一个全局性struct builtin *builtin_table[](stage2/builtin.c)变量中去找到内置命令的函数,然后执行。

值得一提的是grub的shell类似bash的命令补全和命令历史纪录。



b,内部数据结构
struct builtin
{
/* 命令名称,重要,是搜索命令时的依据 */
char *name;
/* 命令函数,重要,是搜索匹配后调用的函数 */
int (*func) (char *, int);
/* 功能标示,一般未用到. */
int flags;
/* 简短帮助信息 */
char *short_doc;
/* 完整帮助信息 */
char *long_doc;
};
struct builtin *builtin_table[];(stage2/builtin.c)



c,Hack 提示

编写一个自己的grub命令。只需按照b节所述,填充一个struct builtin结构,按后将其指针放入builtin_table指针数组,你就可以在menu.lst或者grub shell中使用这个命令了。

作者: phyma   发布时间: 2005-01-13

顶,

作者: redspider   发布时间: 2005-01-13

版大,在grub的命令行,有两个命令,一个是root(hdx,y),一个是setup(hdx),能讲一讲其内部吗?也就是如何实现的?整个磁盘的分区信息是怎么被加载的,以及放在什么地方呢?是在stage1,还是stage2呢?

作者: westroom   发布时间: 2005-01-14

引用:
作者: westroom
版大,在grub的命令行,有两个命令,一个是root(hdx,y),一个是setup(hdx),能讲一讲其内部吗?也就是如何实现的?整个磁盘的分区信息是怎么被加载的,以及放在什么地方呢?是在stage1,还是stage2呢?
要真正讲清楚这些问题可能每个命令都需要不只一小节,而且牵连很多,特别像是disk bios,int 0x 13 hook,等等,所以得先说说disk raw io才行,估计这个一节也不能讲清楚,我整理先!

作者: phyma   发布时间: 2005-01-14

版大,我们已等待了二天了,还未有更新吗?期待着。

作者: officerlinux   发布时间: 2005-01-15

Good![quote]

作者: stonone   发布时间: 2005-01-17

引用:
作者: officerlinux
版大,我们已等待了二天了,还未有更新吗?期待着。
不好意思,周末被GF管制中……呵呵

作者: phyma   发布时间: 2005-01-17

引用:
作者: phyma
a,调用接口
为了简化文件系统驱动的编写,grub不支持磁盘写(对于一个loader来说也没有必要去写磁盘)
既然这样, 那 Grub 的 save default 是怎么实现的?

作者: yongq   发布时间: 2005-01-19

引用:
作者: yongq
既然这样, 那 Grub 的 save default 是怎么实现的?
准确说是不支持磁盘文件系统的读写,但是对磁盘的raw write/read 是支持(主要用于mbr,stage1-5所在的sector,partition的开头2个sector的访问)

作者: phyma   发布时间: 2005-01-19

期待更新。

作者: officerlinux   发布时间: 2005-01-22

好教程
期待ing

作者: nait   发布时间: 2005-01-22

版大,期待中。

作者: officerlinux   发布时间: 2005-01-23

请教一个问题,当grub把内核文件bzlinux装入mem后,grub将跳转到内核哪部分执行了?

感觉应该是setup.S的start,做一些BOIS检测收集硬件参数过程,但grub已将cpu切换到保护模式,而setup.S应该是实模式下执行,这不是有冲突了吗?
是不是grub在装入内核后又转入实模式然后开始执行内核?

作者: redjing   发布时间: 2005-01-25

引用:
作者: redjing
请教一个问题,当grub把内核文件bzlinux装入mem后,grub将跳转到内核哪部分执行了?

感觉应该是setup.S的start,做一些BOIS检测收集硬件参数过程,但grub已将cpu切换到保护模式,而setup.S应该是实模式下执行,这不是有冲突了吗?
是不是grub在装入内核后又转入实模式然后开始执行内核?
对,grub在设置好内核后,然后切换至实模式,具体跳向哪里取决于kernel header里面的一个字段

作者: phyma   发布时间: 2005-01-28

楼主还写不写下去?

作者: luckyroot8   发布时间: 2005-02-14

引用:
作者: phyma
视stage2的大小,安装程序会在stage1中嵌入stage1-5或者stage2的磁盘位置信息。
b,stage1开始执行,它在进行直接加载stage1-5或者stage2并跳转执行(还有些额外的工作,这里忽略先)
stage1-5很无辜,除了加载stage2以外它没有其他任何作用。
针对上面,我来修正一下。

stage1的代码文件,是源码目录下stage1/stage1.S,汇编后便成了一个512字节的img,被写在硬盘的0面0道第1扇区,作为硬盘的主引导扇区。

注意,硬盘主引导扇区 = 硬盘主引导记录(MBR)+ 硬盘分区表(DPT)

stage1的工作并不是加载什么stage1_5或者stage2,而是加载0面0道第2扇区上的512字节代码至0x8000,然后跳至0x8000执行。这里我们提及的另一个512字节代码,是来自源码目录下stage2/start.S文件的,而start.S的作用是作为stage1_5或者stage2(视乎编译grub时的指定)的总入口,它才是stage1_5或者stage2的真正加载器。

总结起来,那么就是stage1加载start,然后将执行权交给start,由start来加载stage1_5或者stage2。所以,斑竹说的“安装程序会在stage1中嵌入stage1-5或者stage2的磁盘位置信息”这句话是错误的。

大家想验证这些关系,请使用以下命令(这里假设你把grub安装在第一个IDE硬盘):
代码:
1. 倾印stage1.S的机器代码映像
dd if=/dev/hda of=BOOT.img bs=1 count=512
2. 倾引start.S的机器代码映像
dd if=/dev/hda of=START.img skip=512 bs=1 count=512
3. 用emacs打开这些映像,并将它们转换为16进制格式来查看
emacs xxx.img
M-x hexlify-buffer

同时对照一下源码中的内容,尤其是那些"GRUB"、"Loading stage1.5"等静态显示数据,你就会验证了上面我的说法,并且在start.S中找到更多的内幕。

作者: home_king   发布时间: 2005-03-10

stage2的内幕并不神奇,正如斑竹所言,是一个mini OS,但我们觉得GRUB神奇的地方在于文件系统的识别功能。这种神奇色彩也就是"鸡蛋与鸡谁先有"的矛盾体。

PC上电后,就会执行BIOS的代码,BIOS将加载硬盘主引导扇区,总共512字节的二进制代码,这些代码就是stage1,然后BIOS将执行stage1。这一点大家都不会感到奇怪,这是很自然的流程。然而,stage2的体积比较大(因为它实现的功能比较全面嘛),所以一般stage2不会被放在固定的磁盘扇区中以供stage1只使用BIOS例程便可对其raw read,那么,stage2就会作为一个文件被放在文件系统里。

大家都知道,stage1_5就是文件系统的支撑代码,在stage1_5没有被加载以前,stage2是不能被stage1找到的,所以我们研究GRUB,关键看看究竟stage1是怎么加载stage1_5的,而stage1_5又被放在哪里。

待续...

作者: home_king   发布时间: 2005-03-11

好帖
期待更新中

作者: Herro_tales   发布时间: 2005-03-11

接着上面的话题。

stage1_5究竟被放在哪呢?很多兄弟可能以为它就是/boot/grub/底下的哪些xxfs_stage1_5文件,但试想一下,要找到boot分区所在的stage1_5文件,那么就必须使得stage1具备文件系统识别功能,而stage1_5本身就是文件系统的支撑代码,它必须加载stage1_5才能具备这种功能。那么,我们又回到了那种矛盾体的悖论──要加载stage1_5来找到stage1_5? 呵呵。

所以用来识别boot分区文件系统的stage1_5不能作为文件来被stage1读取,它只能被存放在固定的扇区中。这里强调"用来识别boot分区文件系统",那是因为并不是所有的stage1_5文件都被放在固定扇区的,只有boot分区的文件系统对应的stage1_5才会被放在固定的扇区中去!比如说,你的boot分区的文件系统是ext2,那么在安装GRUB的stage1的时候,e2fs_stage1_5就会被存放至一个固定的扇区集,而其他的如reiserfs_stage1_5就依然作为文件来存放,以供GRUB使用root()命令来识别其他的boot分区(那时候,stage2已经被加载了,所以这个不成问题)。

那么,如何验证我上面的说法呢?还是使用dd命令。
代码:
1. dd if=/dev/hda of=STAGE1_5.img bs=1k skip=1 count=20
将STAGE1_5.img用emacs打开,转换为hex格式查看。

2. 将/boot/grub/e2fs_stage1_5拷贝一份到当前目录,用emacs打开,转换为hex格式查看。
注意,如果你的boot分区是别的文件系统,应该打开对应的stage1_5文件来查看。我这里的boot分区为ext2文件系统。
查找这两个文件中相同的字符串,如"Loading stage1.5","GRUB"等,同时注意到它们交集的行数数量,你会发现,原来e2fs_stage1_5被放在0面0道的第3个扇区开始往后10多K的扇区集里。

作者: home_king   发布时间: 2005-03-11

这里是e2fs_stage1_5文件的hex倾印片断:
上传的图像
1.jpg (23.5 KB, 387 次查看)
2.jpg (96.8 KB, 425 次查看)

作者: home_king   发布时间: 2005-03-11

这里是从0面0道的第3个扇区倾印片断:
上传的图像
3.jpg (26.5 KB, 197 次查看)
4.jpg (119.8 KB, 255 次查看)

作者: home_king   发布时间: 2005-03-11

从中可见,e2fs_stage1_5的确是被放在了0面0道的第3个扇区开始的扇区集里!

stage1加载了start后,start便加载随后的扇区总共10多K的扇区到内存,随后执行这些stage1_5代码,有了stage1_5,那么识别文件系统中的stage2文件,就不是什么难事了。

关于stage1、start、stage1_5的位置关系,我们从下面的倾印片断中,可得知一二;注意,这个片断处于stage1_5倾引的开头,而它正是start.S的代码(从字符串可得知):
上传的图像
mysnap.jpg (43.5 KB, 288 次查看)

作者: home_king   发布时间: 2005-03-11


最后得出结论,stage1.S被放在0面0道的第1扇区,start.S被放在0面0道的第2扇区,而与boot分区相关的文件系统的xxfs_stage1_5被放在0面0道第3扇区开始的扇区里,其占据的扇区数目与该stage1_5文件的大小有关。


代码:

+------------+
| stage1 | <-- 0面0道第1扇区 
+------------+
| start | <-- 0面0道第2扇区
+------------+
| stage1_5 | <-- 0面0道第3扇区
| ... |
+------------+



而其余的stage1_5以及stage2都作为文件被存放在boot分区里。

作者: home_king   发布时间: 2005-03-11

好了。补充完了stage1以及stage1_5的执行流程以及位置关系后,就轮到stage2这个大约110KB左右的mini OS了,前面phyma斑竹为此开了个头,我觉得phyma斑竹写得很好,他对于stage2的内幕有着很深刻的理解。还请继续,中途打扰了,不好意思,请原谅。

作者: home_king   发布时间: 2005-03-12

grub项目已经停止了,取而代之的是grub 2,一个完全重写的项目

作者: utstar   发布时间: 2005-03-12

引用:
作者: utstar
grub项目已经停止了,取而代之的是grub 2,一个完全重写的项目
虽然GRUB2在开发中,并在未来替代GRUB,但我个人认为,在RMS所提倡的FreeBIOS出现之前,一个Boot Loader的底层原理是差不多的(最显著的一点就是严重依赖BIOS例程),而且GRUB2的所谓重写,估计也只是Boot模型的重构优化而已,是代码层面的清理。如果x86等硬件平台的接口标准不变的话,GRUB2也并非凌驾于GRUB之上的空中楼阁。

我们这里的源码分析,主要是揭开GRUB的内幕,阐明Boot Loader的原理,例如,划分为stage1和stage2的出发点、filesystem-aware的实现方法等。个人认为,这些思想在GRUB2里应该也会存在的。

当然,很盼望兄弟能为我们讲解一下GRUB2的新亮点。呵呵。

作者: home_king   发布时间: 2005-03-12

我有个问题:
grub中的stage1。5处在硬盘的第二和第三扇区,这样的话,
会不会覆盖掉原本的一些放在此处的有用信息;

如果有,那么,grub是用什么方法来处理这个问题?

作者: lyy9505   发布时间: 2005-03-17

引用:
作者: lyy9505
我有个问题:
grub中的stage1。5处在硬盘的第二和第三扇区,这样的话,
会不会覆盖掉原本的一些放在此处的有用信息;

如果有,那么,grub是用什么方法来处理这个问题?
0面0道的所有扇区都是保留的,BIOS不会放置任何数据在此。
注意,第一个主分区也是从1面0道的第1扇区开始的。

作者: home_king   发布时间: 2005-03-18

明白,谢谢

作者: lyy9505   发布时间: 2005-03-18

菜鸟来看一下 ,为什么不继续了?

作者: k8k   发布时间: 2005-03-28

引用:
作者: k8k
菜鸟来看一下 ,为什么不继续了?
最近工作太忙,不好意思各位,估计这个主题要难产了(!!!我要休假!!!!)

但是我会尽量抽时间来和大家探讨一些grub的问题,见谅

作者: phyma   发布时间: 2005-06-07

楼主加油~

作者: gradetwo   发布时间: 2005-06-07

呵呵,谢谢home_king兄弟的补充

但是stage1_5确实是不能固定在某某地方的,会根据情况处理(lilo的处理办法)

参见stage2/builtins.c 中间的static int install_func()

代码:
 /* Check for the Stage 2 id. */
 if (stage2_second_buffer[STAGE2_STAGE2_ID] != STAGE2_ID_STAGE2)
 is_stage1_5 = 1;

 /* If INSTALLADDR is not specified explicitly in the command-line,
 determine it by the Stage 2 id. */
 if (! installaddr)
 {
 if (! is_stage1_5)
        /* Stage 2. */
        installaddr = 0x8000;
 else
        /* Stage 1.5. */
        installaddr = 0x2000;
 }

 *((unsigned long *) (stage1_buffer + STAGE1_STAGE2_SECTOR))
 = stage2_first_sector;
 *((unsigned short *) (stage1_buffer + STAGE1_STAGE2_ADDRESS))
 = installaddr;
 *((unsigned short *) (stage1_buffer + STAGE1_STAGE2_SEGMENT))
 = installaddr >> 4;

因为grub需要不只是安装到mbr,而且需要安装到某个partition

作者: phyma   发布时间: 2005-06-07

引用:
作者: phyma
呵呵,谢谢home_king兄弟的补充

但是stage1_5确实是不能固定在某某地方的,会根据情况处理(lilo的处理办法)

参见stage2/builtins.c 中间的static int install_func()

代码:
 /* Check for the Stage 2 id. */
 if (stage2_second_buffer[STAGE2_STAGE2_ID] != STAGE2_ID_STAGE2)
 is_stage1_5 = 1;

 /* If INSTALLADDR is not specified explicitly in the command-line,
 determine it by the Stage 2 id. */
 if (! installaddr)
 {
 if (! is_stage1_5)
        /* Stage 2. */
        installaddr = 0x8000;
 else
        /* Stage 1.5. */
        installaddr = 0x2000;
 }

 *((unsigned long *) (stage1_buffer + STAGE1_STAGE2_SECTOR))
 = stage2_first_sector;
 *((unsigned short *) (stage1_buffer + STAGE1_STAGE2_ADDRESS))
 = installaddr;
 *((unsigned short *) (stage1_buffer + STAGE1_STAGE2_SEGMENT))
 = installaddr >> 4;

因为grub需要不只是安装到mbr,而且需要安装到某个partition
目前这些情况我自己还不是很清楚,期待大家进一步讨论

作者: phyma   发布时间: 2005-06-07

看完了,还想看,再看看。

作者: linuxli   发布时间: 2005-06-07

建议地址:
ftp://ftp.debian.org/debian/pool/main/g/grub/grub_0.95+cvs20040624.orig.tar.gz

作者: crquan   发布时间: 2005-06-10

好啊,不错的东东。期待更新!

作者: pangshaoguang   发布时间: 2005-06-16

热门下载

更多