Grub 源代码分析
时间:2005-01-11
来源:互联网
我是第一次写这种“标题庄重”的技术文章,感觉好怕怕的,大家一起来写这个吧!
版本就用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
(如果你不熟悉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
关注。
作者: officerlinux 发布时间: 2005-01-12
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
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
类似系统调用,在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
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
作者: westroom 发布时间: 2005-01-14
作者: westroom
版大,在grub的命令行,有两个命令,一个是root(hdx,y),一个是setup(hdx),能讲一讲其内部吗?也就是如何实现的?整个磁盘的分区信息是怎么被加载的,以及放在什么地方呢?是在stage1,还是stage2呢?
|
作者: phyma 发布时间: 2005-01-14
作者: officerlinux 发布时间: 2005-01-15
作者: stonone 发布时间: 2005-01-17
作者: officerlinux
版大,我们已等待了二天了,还未有更新吗?期待着。
|
作者: phyma 发布时间: 2005-01-17
作者: phyma
a,调用接口
为了简化文件系统驱动的编写,grub不支持磁盘写(对于一个loader来说也没有必要去写磁盘) |
作者: yongq 发布时间: 2005-01-19
作者: yongq
既然这样, 那 Grub 的 save default 是怎么实现的?
|
作者: phyma 发布时间: 2005-01-19
作者: officerlinux 发布时间: 2005-01-22
期待ing
作者: nait 发布时间: 2005-01-22
作者: officerlinux 发布时间: 2005-01-23
感觉应该是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在装入内核后又转入实模式然后开始执行内核? |
作者: 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
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文件系统。
作者: home_king 发布时间: 2005-03-11
1.jpg (23.5 KB, 387 次查看) | |
2.jpg (96.8 KB, 425 次查看) |
作者: home_king 发布时间: 2005-03-11
3.jpg (26.5 KB, 197 次查看) | |
4.jpg (119.8 KB, 255 次查看) |
作者: home_king 发布时间: 2005-03-11
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
作者: home_king 发布时间: 2005-03-12
作者: utstar 发布时间: 2005-03-12
作者: utstar
grub项目已经停止了,取而代之的是grub 2,一个完全重写的项目
|
我们这里的源码分析,主要是揭开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是用什么方法来处理这个问题? |
注意,第一个主分区也是从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
但是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
热门阅读
-
office 2019专业增强版最新2021版激活秘钥/序列号/激活码推荐 附激活工具
阅读:74
-
如何安装mysql8.0
阅读:31
-
Word快速设置标题样式步骤详解
阅读:28
-
20+道必知必会的Vue面试题(附答案解析)
阅读:37
-
HTML如何制作表单
阅读:22
-
百词斩可以改天数吗?当然可以,4个步骤轻松修改天数!
阅读:31
-
ET文件格式和XLS格式文件之间如何转化?
阅读:24
-
react和vue的区别及优缺点是什么
阅读:121
-
支付宝人脸识别如何关闭?
阅读:21
-
腾讯微云怎么修改照片或视频备份路径?
阅读:28