Grub第一阶段总结
一、Grub概念简介
GNU Grub(GRand Unified Bootloader简称“Grub”)是一个来自GNU项目的多操作系统启动程序,允许用户可以在计算机内同时拥有多个操作系统,并在计算机启动时选择希望运行的操作系统。引导加载程序是计算机启动时运行的第一个软件程序,它负责加载和传输控制操作系统内核软件,之后内核依次初始化操作系统的其余部分。
使用Grub启动时,可以使用命令行界面(参见命令行界面),也可以使用菜单界面(参见菜单界面)。在菜单中,可以切换到命令行模式,反之亦然。
使用命令行界面,可以手动输入内核的驱动器规范和文件名。
在菜单界面中,该菜单基于预先准备的配置文件(参见configuration),只需使用方向键选择操作系统。
图1 图形菜单界面
图2 命令行界面
- Grub使用方法
Grub启动阶段,按c进行shell命令行阶段:
www.gnu.org/software/grub/manual/grub/grub.html#Command_002dline-and-menu-entry-commands
2.1 Grub正常启动
Grub正常启动后,将进入“普通模式”,一般是显示一个菜单(找到了'$prefix/Grub.cfg'),或者直接进入Grub SHELL(没找到'$prefix/Grub.cfg')。
在普通模式中,命令模块[command.lst]与加密模块[crypto.lst]会被自动按需载入(无需使用"insmod"命令),并且可使用完整的Grub脚本功能。但是其他模块则可能需要明确使用"insmod"命令来载入。
2.1.1 Grub在BIOS平台
Grub默认安装在第一个硬盘hd0的MBR上,把引导程序boot.img写入硬盘的MBR,启动时根据MBR提示找到启动分区,加载分区内的Grub核心文件core.img和配置文件Grub.cfg。(hd0,mdsom1)
BIOS --> boot.img[MBR] --> core.img --> 设置"prefix root cmdpath"环境变量 --> 加载"normal.mod"模块[同时还包括依赖的 terminal crypto extcmd boot gettext 模块] --> 执行"normal $prefix/Grub.cfg"命令。
2.1.2 Grub在UEFI平台
UEFI --> core.img[BOOTX64.EFI/BOOTX86.EFI] --> 设置"prefix root cmdpath"环境变量 --> 加载"normal.mod"模块[同时还包括它所依赖的 terminal crypto extcmd boot gettext 模块] --> 执行"normal $prefix/Grub.cfg"命令。
2.2 Grub版本查询
查询ubuntu的Grub版本号:
- Grub-install --version
- Grub-install -V
2.3 Grub菜单加上背景图
首先制作一张PNG格式的图片,分辨率最好是"1024x768"以保证较好的兼容性。然后将这张图片放到"$prefix/themes/1024x768.png"("$prefix"是Grub的安装目录)。然后在'Grub.cfg'中加入如下内容:
set gfxmode=1024x768,auto
insmod gfxterm
insmod png
terminal_output gfxterm
background_image $prefix/themes/1024x768.png
2.4 Grub显示中文界面(包括显示中文菜单项)
由于Grub在内部使用UTF-8编码,并且所有文本文件(包括'Grub.cfg')也都被假定为使用UTF-8编码,为了避免乱码,请务必以UTF-8编码保存'Grub.cfg'文件。
set gfxterm_font=unicode
set lang=zh_CN
set locale_dir=$prefix/locale
insmod gfxterm
terminal_output gfxterm
loadfont unicode
2.5 更改Grub的字体
如果默认的unicode字体在1024x768或更高分辨率的屏幕上显得太小,或者默认的字体不好看,可以使用"Grub-mkfont"工具。下面的示例展示了制作一个24px大小的pf2字体:
Grub-mkfont -i1 -n WenQuanYiMicroHeiMono24px -o WenQuanYiMicroHeiMono24px.pf2 -s24 -v wqy-microhei.ttc
将制作好的字体文件(WenQuanYiMicroHeiMono24px.pf2)放到"$prefix/fonts"目录中,修改'Grub.cfg'文件中的两行:
set gfxterm_font=WenQuanYiMicroHeiMono24px
loadfont WenQuanYiMicroHeiMono24px
2.6 Grub-2.0.6版本官方模块
模块间的依赖关系位于"moddep.lst"文件中。
2.6.1 命令模块[command.lst]
提供了各种不同的功能,类似标准Unix命令。
insmod module:载入名为"module"的Grub模块。
set [envvar=value]:将环境变量"envvar"的值设为'value'。如果没有使用参数,则打印出所有环境变量及其值。
help [pattern …]:显示内建命令的帮助信息。如果没有指定"pattern",那么将显示所有可用命令的简短描述。如果指定了"pattern",那么将只显示名字以这些"pattern"开头的命令的详细帮助信息。
halt [--no-apm]:关闭计算机。如果指定了 --no-apm 选项,表示不执行APM BIOS调用。否则,计算机使用APM关闭。
boot:启动已经被载入的OS或链式加载器。仅在运行于交互式命令行的时候才是需要的。在一个菜单项结束时是隐含的。
cat [--dos] file:显示文件"file"的内容。如果使用了"--dos"选项,那么"回车/换行符"将被显示为一个简单的换行符。否则,回车符将被显示为一个控制符(<d>)。
ls [arg …]:如果不使用参数,那么列出所有对Grub已知的设备。
如果参数是包含在括号内的一个设备名,那么列出该设备根目录下的所有文件。
如果参数是以绝对路径给出的目录,那么列出这个目录的内容。
lsmod:列出已经加载的所有模块
normal_exit:退出当前的普通模式。如果这个普通模式实例不是嵌套在另一个普通模式里的话,就会返回到救援模式。
2.6.2 加密模块[crypto.lst]
提供了各种数据完整性校验与密码算法支持,一共20多个。例如:gcry_rijndael crc64 gcry_md5 ...
2.6.3 文件系统模块[fs.lst]
提供了访问各种文件系统的功能,一共30多个。例如:btrfs cpio exfat ext2 fat iso9660 ntfs tar xfs zfs ...
2.6.4 分区模块[partmap.lst]
提供了识别各种分区格式的功能,一共10多个。例如:part_bsd part_gpt part_msdos ...
2.6.5 分区工具[parttool.lst]
提供了操作各种分区格式的功能,目前只有 msdospart 这一个。
2.6.6 终端模块[terminal.lst]
提供了各种不同终端的支持,一共不到10个。例如:serial gfxterm vga_text at_keyboard ...
2.6.7 视频模块[video.lst]
提供了各种不同的视频模式支持,一共6个。例如:vga vbe efi_gop efi_uga ...
2.6.8 其他模块
所有未在上述分类文件中列出的模块都归为这一类,一共将近100个。值得关注的有以下几个:
"all_video"可用于一次性加载当前所有可用的视频模块;
"gfxmenu"可用于提供主题支持;
"jpeg png tga"可用于提供特定格式的背景图片支持;
"xzio gzio lzopio"可用于提供特定压缩格式支持(常配合"initrd"命令使用);
2.7 Grub救援模式Rescue
进入救援模式,这意味着Grub由于某种原因未能加载“模块”或者Grub没有在正确的位置读取。
# Inspect the current prefix (and other preset variables):
set 可以输入set查看一下当前信息
# Find out which devices are available:
ls 查看Grub位置
# Set to the correct value, which might be something like this:
set prefix=(hd0)/Grub
set root=(hd0)
insmod normal
normal 回到引导界面上
- Grub配置文件
3.1 Grub配置文件生成与更新
Grub-mkconfig脚本自动生成 /boot/Grub/Grub.cfg,不需要手动修改。
# Grub-mkconfig -o /boot/Grub/Grub.cfg
/etc/Grub.d/脚本包含 Grub 菜单信息和操作系统引导脚本,运行 update-grub 命令时,它会读取 Grub 文件和 Grub.d 脚本的内容,并更新创建Grub.cfg文件。
3.2 Grub配置文件详解
下图为在Linux系统下,/etc/default/Grub内包含的自定义Grub配置文件。
(1)Grub_DEFAULT
默认的菜单项,默认值为0。其值可为数值N,表示从0开始计算的第N项是默认菜单
也可以指定对应的title表示该项为默认的菜单项。使用数值比较好,因为使用的title可能包含了容易改变的设备名
(2)Grub_SAVEDEFAULT
默认该key的值未设置。如果该key的值设置为true时,如果选定了某菜单项,则该菜单项将被认为是新的默认菜单项。该key只有在设置了"Grub_DEFAULT=saved"时才有效。
(3)Grub_TIMEOUT
在开机选择菜单项的超时时间,超过该时间将使用默认的菜单项来引导对应的操作系统。
默认值为5秒。等待过程中,按下任意按键都可以中断等待。
设置为0时,将不列出菜单直接使用默认的菜单项引导与之对应的操作系统,设置为"-1"时将永久等待选择。
(4)Grub_TIMEOUT_STYLE
如果该key未设置值或者设置的值为"menu",则列出启动菜单项,并等待"Grub_TIMEOUT"指定的超时时间。
如果设置为"countdown"和"hidden",则不显示启动菜单项,而是直接等待"Grub_TIMEOUT"指定的超时时间。如果超时了则启动默认菜单项并引导对应的操作系统
(5)Grub_BACKGROUND
设置背景图片,背景图片必须是Grub可读的,图片文件名后缀必须是".png"、".tga"、".jpg"、".jpeg",在需要的时候,Grub会按比例缩小图片的大小以适配屏幕大小。
(6)Grub_THEME
设置Grub菜单的主题。
四、Grub源码分析
4.1 Grub源代码下载
Grub源代码下载地址:https://ftp.gnu.org/gnu/Grub/,如下图所示根据系统编译环境选择需要的Grub源代码。
4.2 Grub源代码文件简介
本次选择下载Grub-2.06.tar.gz
在终端输入 tar zxvf Grub-2.06.tar.gz解压文件,进入Grub-2.06文件夹
asm-tests:这里面是几个汇编代码文件;
build-aux:包含了编译时可能用到的脚本;
conf:编译需要用的文件,make的时候会用到;
docs:帮助文件,还有Grub.cfg示例模板;
Grub-core:Grub的主体核心代码;
include:Grub主体的头文件;
m4:包含m4文件,configure的时候会使用到;
po:这个目录下主要由两种文件,一种时.po文件,另一种时.gmo文件。它们是用来支持多语言的,这个文件夹内的东西不要管,只是对代码做一些基本的注释;
tests:一些测试的脚本;
themes:Grub界面的主题;
unicode:Unicode数据;
util:一些工具源的文件。
启动时运行的代码在' Grub-core '的子目录中,在启动完整操作系统后运行的代码在顶层的子目录中。
4.3 Grub核心代码详解
Grub内核在' Grub -core/kern/ '中,它包含一些核心工具,比如设备、磁盘和文件框架、环境变量处理、列表处理等等。
Grub-2.06/Grub-core/kern/main.c 调用其它外部函数(没有在 main.c 中定义的);
Grub-2.06/Grub-core/kern/ 目录下的其它源文件,分别提供这些外部函数的定义(实现了这些函数的具体功能);
Grub-2.06/include/Grub/ 目录下的头文件,则对应这些外部函数的声明;
Grub-2.06/Grub-core/kern/ 目录下的与这些头文件与同名的 C 源文件;定义main.c 包含的这 12 个头文件,全部位于Grub-2.06/include/Grub/ 目录下。
终端实现在' Grub-core/term/ '中。
磁盘访问代码分布在' Grub-core/ Disk / '(用于访问磁盘设备本身),' Grub-core/partmap/ '(用于解释分区表数据),和' Grub-core/fs/ '(用于访问文件系统)。注意,除了一些特殊的例外,Grub只包含从文件系统读取的代码,并试图避免包含任何要写入文件系统的代码;这让我们可以放心地向用户保证,Grub不会导致文件系统损坏。
PCI和USB总线处理在' Grub-core/bus/ '中。
视频处理代码在' Grub-core/ Video / '中,图形化菜单系统大量使用这个功能。
大多数命令都是通过' Grub-core/commands/ '中的文件实现的。
在Grub_main之前,有一段汇编代码:
start:
_start:
movq %rcx, EXT_C(Grub_efi_image_handle)(%rip)
movq %rdx, EXT_C(Grub_efi_system_table)(%rip)
andq $~0xf, %rsp
call EXT_C(Grub_main)
进入Grub源代码,执行Grub_main函数。
- Grub编译与安装
5.1 卸载Grub
卸载掉旧的Grub:apt-get purge Grub-pc。
5.2 UEFI模式下编译生成Grub
Grub使用Autoconf和Automake,大部分Automake输入由Python脚本生成。顶级构建规则在' configuration .ac '中,‘Grub-core / Makefile.core.def’,‘Makefile.util.def’。' *.def '文件中的每个块代表一个构建目标,并指定用于在不同平台上构建该目标的源文件。' *.def '文件被' gentpl.py '处理成Automake输入。
使用configure工具生成Makefile文件时:
./autogen.sh 生成configure文件
./configure --prefix=/usr(安装目录) --with-platform=efi(指定平台pc/uefi) --target=x86_64(指定目标的处理器架构类型,或i386) --sysconfdir=/etc(配置文件目录) --disable-werror(不把报警归结于错误)
make -j4让make最多允许4个编译命令同时执行,这样可以更有效的利用CPU资源
make install 安装编译生成的文件
Grub-install /dev/sda 安装到启动盘
sudo update-Grub 更新grub配置文件
5.2.1 制作一个BOOT.EFI可启动镜像
在make install之后
#grub-mkimage -O x86_64-efi -d ./grub-core/ -p /boot/EFI/BOOT/ -o bootx64.EFI boot reboot linux part_gpt part_msdos disk fat exfat ext2 ntfs xfs hfs iso9660 normal search_fs_file configfile chain loopback echo efi_gop file gfxterm gfxterm_background gfxterm_menu halt help ls png true
UEFI启动是通过/boot/EFI/BOOT/xxxx.EFI来启动grub引导。
参数解析如下:
-d 表示指定查找模块目录
-p 设置gurb目标文件的文件夹,cfg文件中会调用。
-o 表示生成的目标文件 bootx64.efi
-O 表示集成的平台模块 写 i386-pc x86_64-efi
BOOTx64.EFI后面全是命令模块,可以根据自己的需求进行添加。
拷贝到指定位置
# cp BOOTx64.EFI /boot/EFI/BOOT/
# reboot
重启就可以使用新编译的BOOTx64.EFI进行引导内核启动了。
grub.cfg修改启动主要配置项解析
配置项 解析
default 指grub启动时默认菜单项,表示默认从哪个菜单启动。默认配置为0。
timeout 指菜单到自动启动系统前停留时间,单位时间为sec。
title 指一个启动操作系统的名称。
root 指相应内核镜像所在目录boot所在的磁盘分区 如:root=‘hd0,msdos1’。
linux 指boot目录下内核镜像的名称。
initrd 指linux的initial ramdisk在boot目录下的名称。
boot 指引导内核进行启动。
gurb2命令说明
命令 说明
grub-editenv 编译环境块工具
grub-mkfont 设置grub使用的字体
grub-mkimage 生成一个gurb的可启动镜像
grub-mkrescue 生成一个适用于软盘的grub的可启动镜像
grub-install 在磁盘上安装grub
grub-mkconfig 生成grub配置文件
grub-mkdevicemap 生成一个新的device map文件
grub-probe 扫描计算机收集磁盘和分区信息
grub-set-default 配置默认启动项
grub-reboot 配置重启之后的默认启动项
5.3 Grub中Makefile的指令
5.3.1 clean
清除当前目录下在 make 过程中产生的文件。它不能删除软件包的配置文件,也不能删除 build 时创建的那些文件。
5.3.2 distclean
类似于"clean",删除当前目录下的的配置文件、build 过程产生的文件。
5.3.3 info
产生必要的 Info 文档。
5.3.4 check 或 test
完成所有的自检功能。在执行检查之前,应确保所有程序已经被创建(但可以尚未安装)。为了进行测试,需要实现在程序没有安装的情况下被执行的测试命令。
5.3.5 install
完成程序的编译并将最终的可执行程序、库文件等拷贝到指定的目录。此种安装一般不对可执行程序进行 strip 操作。
5.3.6 install-strip
和"install"类似,但是会对复制到安装目录下的可执行文件进行 strip 操作。
5.3.7 uninstall
删除所有由"install"安装的文件。
5.3.8 installcheck
执行安装检查。在执行安装检查之前,需要确保所有程序已经被创建并且被安装。
5.3.9 installdirs
创建安装目录及其子目录。它不能更改软件的编译目录,而仅仅是创建程序的安装目录。
5.4 Grub编译
Grub源代码不需要修改makefile文件,添加新的.c和.h文件后,需要修改配置文件。如果添加一个新的模块遵循现有的模式,比如一个新的命令或一个新的文件系统,只需要修改两个文件内容。
此处修改Hello模块命令代码,新增对硬盘文件读取与SM3度量,文件内容为abc。
5.4.1 Grub-core/Makefile.core.am
Platfor_PROGRAMS
*************
*.marker:**
5.4.2 Grub-core/Makefile.core.def
添加新的模块
Module = {
Name = cmd;
Common = cmd/cmd.c;
};
5.4.3 hello模块
Grub源代码在发布时候,给出了一个测试指令模块,hello.mod。如果在Grub界面,按c进入命令模式,输入hello,会得到下图:
添加类似hello这类命令时,按照上述方式,在Grub-core/目录下添加ptest文件夹,加入ptest.c文件,修改Makefile文件即可添加命令模块。
5.4.4 在main函数中新增功能
需要在makefile.core.def中,在126行以后添加到kern中
common = kern/模块.c
会自动将文件与main.c一起编译
- Grub文件介绍
6.1 /Boot目录
vmlinuz是可引导的、压缩的内核。“vm”代表“Virtual Memory”。Linux支持虚拟内存,
zImage(vmlinuz)和bzImage(vmlinuz)都是用gzip压缩的。它们不仅是一个压缩文件,而且在这两个文件的开头部分内嵌有 gzip解压缩代码。
vmlinux是未压缩的内核,vmlinuz是vmlinux的压缩文件。
initrd是文件系统,“initial ramdisk”的简写,initrd一般被用来临时地引导硬件到实际内核vmlinuz能够接管并继续引导的状态。
System.map是内核符号映射表,顾名思义就是将内核中的符号(也就是内核中的函数)和它的地址能联系起来的一个列表。
6.2 /Boot/Grub目录
[root@study ~]# ls -l /boot/Grub
-rw-r--r--. device.map <==Grub 的设备对应文件(下面会谈到)
drwxr-xr-x. fonts <==启动过程中的画面会使用到的字体数据
-rw-r--r--. Grub.cfg <==Grub 的主配置文件!相当重要!
-rw-r--r--. Grubenv <==一些环境区块的符号
drwxr-xr-x. i386-pc <==针对一般 x86 PC所需要的 Grub 的相关模块
drwxr-xr-x. locale <==就是语言相关的数据啰
drwxr-xr-x. themes <==一些启动主题画面数据
[root@study ~]# ls -l /boot/Grub/i386-pc
-rw-r--r--. acpi.mod <==电源管理有关的模块
-rw-r--r--. ata.mod <==磁盘有关的模块
-rw-r--r--. chain.mod <==进行 loader 控制权移交的相关模块
-rw-r--r--. command.lst <==一些指令相关性的列表
-rw-r--r--. efiemu32.o <==下面几个则是与 uefi BIOS 相关的模块
-rw-r--r--. efiemu64.o
-rw-r--r--. efiemu.mod
-rw-r--r--. ext2.mod <==EXT 文件系统家族相关模块
-rw-r--r--. fat.mod <==FAT 文件系统模块
-rw-r--r--. gcry_sha256.mod <==常见的加密模块
-rw-r--r--. gcry_sha512.mod
-rw-r--r--. iso9660.mod <==光盘文件系统模块
-rw-r--r--. lvm.mod <==LVM 文件系统模块
-rw-r--r--. mdraid09.mod <==软件磁盘阵列模块
-rw-r--r--. minix.mod <==MINIX 相关文件系统模块
-rw-r--r--. msdospart.mod <==一般 MBR 分区表
-rw-r--r--. part_gpt.mod <==GPT 分区表
-rw-r--r--. part_msdos.mod <==MBR 分区表
-rw-r--r--. scsi.mod <==SCSI 相关模块
-rw-r--r--. usb_keyboard.mod <==下面两个为 USB 相关模块
-rw-r--r--. usb.mod
-rw-r--r--. vga.mod <==VGA 显示适配器相关模块
-rw-r--r--. xfs.mod <==XFS 文件系统模块
-rw-r--r--. hello.mod <==hello模块,用来做基本的测试
6.3 文件介绍
boot.img
相当与 GRUB 的 stage1 它被写入 MBR或 boot分区,它不能识别任何文件系统
在GRUB2安装时GRUB2内核镜像在磁盘中到位置写入到 boot.img 中
这就使得 boot.img 能够在不能识别文件系统的情况下加载内核镜像
cdboot.img
当从CD引导情况下被写入内核镜像第一个扇区到内容,它负责加载其余的内核镜像到内存。
diskboot.img
当从磁盘引导情况下被写入内核镜像第一个扇区到内容,它负责加载其余的内核镜像到内存。
pxeboot.img
当从网络启动时使用到的。
kernel.img
此镜像包含GRUB2运行时包含的基本工具,框架驱动、文件句柄、环境变量、安全模式命令行解析器等
他可以直接使用但是通常它会被编译进所有的内核镜像中使用。
core.img
这是GRUB2的内核镜像,它由grub-mkimage程序将kernel.img和一些模块动态编译而成,一般情况下他已经包含足够的模块去访问/boot/grub,模块机制使得内核镜像能保持很小的尺寸。在某种程度上,它可以被视为 GRUB 中的 stage2。
*.mod
这是一些可以动态加载的一些模块,当我们需要时,可以将它们可以被动态加载编译进内核镜像
也可以使用insmod手动加载。他们就代替 GRUB 中的 stage1_5 之类到镜像
- Grub启动解析
UEFI将Bootx64.efi文件Load到内存中加载,第一个运行的函数grub_main(),代码路径位于grub/grub-core/kern/main.c中,整个grub的功能可以说基本上都是在这个函数中完成的。
其主要的代码原理是注册一些命令和对应的回调函数,然后解析grub.cfg去执行对应的命令,一次执行其中的mod。在执行linux命令的时候去加载内核,将内核加载到一个内存地址上,执行initrd命令的时候去加载initrd,将initrd也加载到一个内存地址上,然后执行boot命令,开始解压并解析内核,这个时候拿到内核运行的基地址,并将整个内核拷贝到对应的地址上,并开始运行,这样就开时了操作系统的启动过程。
Grub编程解析
GRUB_MOD_INIT:加载该模块时执行的函数,通常用于初始化模块,注册模块命令,设置环境变量。
GRUB_MOD_FINI:释放该模块时执行的函数,主要用于清理。
grub_register_command函数:用于注册命令,第一个参数为命令名称,第二个参数为该命令执行的函数,第三个参数为该命令的注释。
Grub启动解析
Grub-core/kenner/main.c
grub_machine_init:设备初始化;
这个函数是和体系架构相关的,在Arm、MIPS、x86上的实现都不同,这个函数主要的作用就是初始化在本平台上需要做的必要的操作,例如获取后面grub要执行的每个moudule的基地址,初始化控制台,初始化内存管理系统,grub内部有自己的一套机制来管理内存。社会看门狗,挂载硬盘设备等操作都是在这个函数内做的。
内部函数grub_efi_loongson_init ()函数主要的作用就是获取了内核和固件传参结构中的SMBIOS的那个表,然后通过函数为后面需要传递的整个参数申请内存空间,来存放这数据。
grub_boot_time ("After machine init."); 挂起
grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT); 打印信息高亮
grub_printf ("Welcome to GRUB!\n\n"); 打印信息
grub_setcolorstate (GRUB_TERM_COLOR_STANDARD); 打印信息正常
grub_load_config
这个函数的作用就是找到后面要执行的其moudule类型为OBJ_TYPE_CONFIG的moudule,然后将这个moudule拷贝到这里申请的一段内存中。
grub_load_modules
这个函数就是去执行Grub中的命令对应的moudule的入口函数。每一个moudule函数是通过GRUB_MOD_INIT(linux)类似与内核编译的形式编译成每一个section中。然后当运行的时候,就运行对应的这里编译的代码,这里面都是注册的命令的代码,这里面的命令包含Linux、initrd两个命令。这两个命令是在grub.cfg中使用的。当运行这两个命令的时候,就会调用这里面注册的命令的回调函数去运行。这里面只是去注册对应的命令,最后通过boot命令来真正的运行内核。linux命令就是去加载内核,initrd就是其加载initrd。所以grub_load_modules()的作用就是去调用注册的命令的函数,为后面使用的命令做准备。这里面 (mod->init) (mod);这行代码才是真正去执行上面提到的GRUB_MOD_INIT(linux)函数。
函数grub_set_prefix_and_root ()
这个函数是设置环境变量prefix和boot这两个环境变量的,后期我们使用效果如下:
Const char* prefix_env_path = grub_env_get(“prefix”); //(hd0,gpt2)/boot/grub
Const char* root_env_path = grub_env_get(“root”); // hd0,gpt2
Grub_load_measurement_mode()函数
执行度量功能,度量grub.cfg配置文件以及启动可选择的所有内核文件和初始化文件系统。
函数reclaim_module_space ()
这个函数是回收前面运行每个moudule的时候,将moudule加载到内存所占用的内存的地址。
函数grub_register_core_commands ()
这个函数就是注册set命令、unset命令、ls命令和insmod命令的回调函数。具体源代码在corecmd.c文件中,四个命令的回调函数分别为grub_core_cmd_set/unset/ls/insmod。
函数if(load_config) grub_parser_execute(load_config)
这个函数的作用就是解析grub.cfg中的内容,并运行对应的命令。
在grub_rescue_parse_line函数中,(cmd->func) (cmd, n - 1, &args[1])去执行对应命令的回调函数。
grub_load_normal_mode ()
这个函数就是执行normal这个命令对应的mod
函数grub_rescue_run
这个函数是防止如果上面的函数grub_parser_execute没有执行,那么就在这里去解析grub.cfg中的命令。
Grub_linux_boot函数
在不同的平台下对应着不同的Grub_linux_boot函数,主要完成了BIOS向内核传递参数和开始运行内核的功能。
这个函数最后return grub_relocatorXX_boot (relocator, state, 0);是跳转到内核开始执行内核代码的函数,前面的几个函数主要是完成给内核传递参数。
在command.c文件中,有注册命令和注销命令两个函数。
函数grub_register_command_prio才是最终将命令注册到全局变量grub_command_t grub_command_list这个链表中的。linux命令注册的时候对应的回调函数就是grub_cmd_linux,而initrd对应的回调函数就是grub_cmd_initrd,这就是在使用这两个命令的时候,去执行的函数就是这两个函数。
grub_register_exported_symbols:导入系统函数符号;
grub_load_modules:导入需要镜像中mod;
reclaim_module_space:释放模块占用的内存空间;
grub_register_core_commands:注册内核命令,set,unset,ls,insmod;
grub_load_normal_mode:执行normal命令,进入normal模块开始执行,grub_cmd_normal(Grub-core/normal/main.c)函数,如果没有配置文件或运行参数,该函数会退出,否则不退出。
grub_rescue_run:进入命令行操作界面
Grub-core/normal/main.c
grub_env_get ("prefix"):获取制作镜像是指定配置文件路径,生成grub.cfg文件的绝对路径,例如:TFTP的为(tftp,192.168.5.1)grub2/grub.cfg;
grub_enter_normal_mode:进入normal模块的grub_normal_execute;
grub_normal_execute:执行read_config_file,读取并执行配置文件;
获取需要从外部导入的命令列表,文件为command.lst,导入对应的模块;
获取需要从外部导入的文件系统列表,文件为fs.lst,导入对应的模块;
获取需要从外部导入的密码列表,文件为crypto.lst,导入对应的模块;
获取需要从外部导入的终端列表,文件为terminal.lst,导入对应的模块;
read_config_file:执行grub_file_open,打开配置文件,文件名为grub.cfg;
grub_file_open:打开配置文件为内存文件;
grub_bufio_open:打开内存文件;
按行执行配置文件;
网络文件系统解析
网络文件操作相关操作
grub_file_open(grub-core/kernel/file.c):第一个参数为文件路径,第二个参数为grub文件类型,函数调用grub_device_open,参数为“tftp,192.168.5.1”;
获取设备名称,调用grub_device_open打开设备,并将返回的设备赋值给grub_file的device字段;
调用grub_fs_probe探测文件系统,赋值给grub_file的fs字段;
调用文件系统fs-open字段函数打开文件,实为grub_net_fs_open函数;
grub_device_open(grub-core/kernel/device.c):
如果设备名称为空,打开环境变量root指定的路径;
首先尝试打开个该文件名的disk,如果打开,则返回该设备;
调用grub_net_open(),打开网络,并将返回的grub_net填入device的net字段。
返回device;
grub_net_open:为全局函数变量,在include/grub/net.h中使用extern进行引用,在net模块初始化函数中使用grub_net_open_real进行赋值。
grub_net_open_real(grub-core/net/net.c):
首先根据参入的名称,查找server的ip地址和协议(protname);
从grub_net_app_level_list链表中查找那个协议与需要的相同(protname),并将找到的protocol赋值给grub_net的protocol字段,同时将grub_net_fs赋值给grub_net的fs字段。
grub_net_app_level_list(全局变量):tftp和http模块在初始化时,调用grub_net_app_level_register(include/grub/net.h)函数将各自的grub_net_app_protocol结构体注册进来。
grub_net_fs_open:
初始化ile->device->net->packs;
调用file->device->net->protocol->open打开文件,实为tftp_open函数;
read_config_file_getline:从grub_file中获取一行数据,调用grub_file_getline;
grub_file_getline:调用grub_file_read读取数据;
grub_file_read:调用file->fs->fs_read读取数据,实际为grub_net_fs_read;
grub_net_fs_read:调用grub_net_fs_read_real执行读取;
grub_net_fs_read_real:从已接收的网络缓冲区中读取数据,如果文件数据不足,调用net->protocol->packets_pulled(实际为tftp_packets_pulled)继续通过网络读取数据。
TCP操作解析
grub_net_tcp_open(grub-core/net/tcp.c):创建一个TCP连接,构建一个SYNC包,并调用grub_net_send_ip_packet发送;
grub_net_send_ip_packet:根据IP类型,选择grub_net_send_ip4_packet发送;
grub_net_send_ip4_packet:添加IP头,调用send_ethernet_packet发送;
send_ethernet_packet:添加MAC头,调用inf->card->driver->send发送,实际为grub_pxe_send(grub-core/net/drivers/i386/pc/pxe.c);
grub_pxe_send:调用grub_pxe_call发送数据包;
grub_net_poll_cards:接收调用receive_packets网络数据包
receive_packets:调用card->driver->recv获取网络数据包,实际为grub_pxe_recv。
receive_packets:调用grub_net_recv_ethernet_packet解析接收的数据包;
grub_net_recv_ethernet_packet:解析出MAC包头,调用grub_net_recv_ip_packets解析IP数据包;
grub_net_recv_ip_packets:调用grub_net_recv_ip4_packets解析IPV4数据包;
grub_net_recv_ip4_packets:解析Ipv4数据包,调用handle_dgram执行;
handle_dgram:根据数据包协议,调用grub_net_recv_tcp_packet解析TCP数据;
grub_net_recv_tcp_packet:处于TCP协议相关交互,如果接收到数据,调用sock->recv_hook返回给客户,该函数为调用grub_net_tcp_open该函数时赋值的接收到数据的回调函数。