浅析 GRUB 如何加载 linux kernel
前言
对于 GRUB 的加载流程,网上绝大部分都是写对 menu.lst, grub.cfg 这些 GRUB 配置文件的编写流程,就像是写脚本语言一样,用些关键字就能让 PC机能正确启动桌面 Linux 了。但这只是 GRUB 的使用,而不是GRUB的分析。
本来是没有想要探究 GRUB 的想法,直到我在自制toy kernel 的学习中进入了 “虚拟内存管理”这一章节。很多介绍虚拟内存管理的时候都会说到 linux 的内存管理,linux 内核会加载到系统 3G~4G 的虚拟内存中, 但 GRUB 是没有开启虚拟内存的,linux 内核的加载是被谁,又是如何加载相应段到 3G~4G 区的呢。
分析 kernel
vmlinux
我们看下内核源码编译后的最原始文件 vmlinux。该文件是 ELF 文件,使用 readelf 读下该文件的 Section header.

这里只截了几个段显示,后面的段都类似. 可以看到这些需要加载的段的地址的确是在 0xC0000000 之后。但 vmlinux 并不是可引导的linux 内核文件。
linux启动的相关信息一般都在 /boot 下,我们看下里面的内容.

可以看到 grub 文件夹,grub 就是引导 linux 进行启动的 bootloader,我们看下 /boot/grub/grub.cfg 文件的内容.
menuentry 'Linux Mint 17 {
recordfail
gfxmode $linux_gfx_mode
insmod gzio
insmod part_msdos
insmod ext2
set root='hd0,msdos1'
linux /boot/vmlinuz-3.13.0-24-generic
initrd /boot/initrd.img-3.13.0-24-generic
}
带有 linux 的一行就指定了启动的内核,可以看到不是 vmlinux 文件,而是 vmlinuz 文件。
vmlinuz
搜索后可以看到 vmlinuz 是可引导的,压缩的内核。 initrd 是"initial ramdisk" 的简写,是临时的虚拟磁盘,暂时不讨论。 因为我电脑上 vmlinuz 是64 bit的,对 64bit不太了解,所以找了个 32bit 的vmlinuz 文件来作解析。先试试readelf命令。
# readelf -S vmlinuz
readelf:错误: Unable to read in 0x7269 bytes of 节头
readelf:错误: 不是 ELF 文件 - 它开头的 magic 字节错误
不是 ELF 文件, 那试试 objdump 吧。
# objdump -afh vmlinuz
objdump: vmlinuz: 不可识别的文件格式
还是不行。
这个时候之所以会相当用这些命令看 vmlinuz 文件的段信息,因为在我的 toy kernel 中使用的是 ELF 文件,而且是使用 grub 加载的,对于 ELF 文件来说内部保护若干 section, 执行时这些 section 必须要在特地的内存地址上. 使用 readelf 查看toy kernel 的 section header 信息如下.

可以看到 Addr 段就是内核运行时这些段在内存中的地址。而加载我的内核的 grub 的配置如下
title toy kernel
root (fd0)
kernel /zkernel
module /initrd
vmlinuz 之所以叫做压缩的内核,是因为它是使用 gzip 压缩后得来,而且不单单是个纯数据包,在文件开头部分内嵌有 gzip 解压缩代码,相当于"自解压"。我的内核需要由grub加载好相应的 section, 但 vmlinuz 都读不出来段如何让 grub 加载?
其实答案就在上面的 grub 配置文件里,在 linux 中声明内核使用的是 linux 关键字,在我的配置中声明内核使用的却是 kernel. 可以明确看出,grub对 linux 的加载是特殊对待的, 但具体怎么特使对待,只能从源代码里看了。
分析 GRUB 源码
从GRUB 官网上下载的是 GRUB2 的代码,结构更清晰。因为有关键字 "linux", 我们就用 "linux" 来搜索,可以搜到如下代码.
grub-core/loader/i386/pc/linux.c
cmd_linux = grub_register_command("linux", grub_cmd_linux, 0, N_("Load Linux."));
从名称应该能看出就是这个, 注册了一个命令,关键字是 “linux”。进入 grub_cmd_linux 函数进行分析。
struct linux_kernel_header lh;
file = grub_file_open (argv[0]);//打开 vmlinuz
//从 vmlinuz 开头处读取 linux header 结构
grub_file_read (file, &lh, sizeof (lh)) != sizeof (lh);
//校验合法性
if(lh.boot_flag != grub_cpu_to_le16 (0xaa55));
goto fail;
//拷贝 linux header 结构体到特地地址
grub_memmove (grub_linux_real_chunk, &lh, sizeof (lh));
//拷贝 vmlinux 的数据到特地地址
grub_file_read (file, grub_linux_prot_chunk, grub_linux16_prot_size)
大部分和讨论无关的代码都被省略了,只保护加载相关代码。可以看到 vmlinuz 在文件头部提供了一个结构体给 bootloader使用来判断相关信息,使用 hexedit 打开vmlinuz 也确实可以看到这些数据,而且该结构体是双向的,即在vmlinuz 中提供给 bootloader 相关信息, bootloader 也会将内核需要的信息填到其中。可以在THE LINUX/x86 BOOT PROTOCOL中看到该结构体更多信息。
grub 并没有更详细的分析 vmlinuz 相关信息,加载后就会跳到内核部分进行下一步动作。详细分析可以看下该链接.
再回头看下 grub-core/loader/multiboot_elfxx.c ,这个应该就是加载 ELF 内核时执行的动作了。
static grub_err_t CONCAT(grub_multiboot_load_elf, XX) (grub_file_t fileconst char *filename, void *buffer)
{
Elf_Ehdr *ehdr = (Elf_Ehdr *) buffer;
char *phdr_base;
int i;
if (ehdr->e_ident[EI_MAG0] != ELFMAG0
|| ehdr->e_ident[EI_MAG1] != ELFMAG1
|| ehdr->e_ident[EI_MAG2] != ELFMAG2
|| ehdr->e_ident[EI_MAG3] != ELFMAG3
|| ehdr->e_ident[EI_DATA] != ELFDATA2LSB)
return grub_error(GRUB_ERR_UNKNOWN_OS, N_("invalid arch-independent ELF magic"));
/* Load every loadable segment in memory. */
for (i = 0; i < ehdr->e_phnum; i++)
{
}
可以看到 grub 对 ELF 内核的加载的确是分析了 ELF 段构成并按照 ELF 文件内的参数加载了。至于之后 kernel 如何自己配置虚拟内存等就要留到以后再说了。
浅析 GRUB 如何加载 linux kernel的更多相关文章
- 浅析dex文件加载机制
我们可以利用DexClassLoader来实现动态加载dex文件,而很多资料也只是对于DexClassLoader的使用进行了介绍,没有深入讲解dex的动态加载机制,我们就借助于Android4.4的 ...
- 浅析golang shellcode加载器
最近也是学习了一下有关shellcode进程注入的操作,简单分享一下通过golang进行实现shellcode加载器的免杀思路. 杀软的查杀方式 静态查杀:查杀的方式是结合特征码,对文件的特征段如Ha ...
- uboot启动正常,加载内核kernel启…
先说现象吧:uboot能够正常启动,不过在kernel启动时却出现起不了的现象,停在这里 Uncompressing Linux.................................... ...
- 引导加载程序:GRUB
计算机在启动的时候,首先由BIOS中的程序执行自检,自检通过后,就根据CMOS的配置找到第一个可启动磁盘的MBR中的Boot Loader程序(一般在启动盘的第一个物理扇区,占446字节),并把控制权 ...
- 引导加载程序之争: LILO 和 GRUB
在不考虑他们的工作或专业情况下,所有 Linux 用户都会使用的是哪个工具?引导加载程序.通过本文了解引导加载程序的工作原理,认识两个流行的引导加载程序 LILO(LInux LOader)和 GNU ...
- Linux设备驱动程序加载/卸载方法 insmod和modprobe命令
linux加载/卸载驱动有两种方法. 1.modprobe 注:在使用这个命令加载模块前先使用depmod -a命令生成modules.dep文件,该文件位于/lib/modules/$(uname ...
- bootrom/spl/uboot/linux逐级加载是如何实现的?
关键词:bootrom.spl.uboot.linux.mksheader.sb_header.mkimage.image_header_t等等. 首先看一个典型的bootrom->spl-&g ...
- Win7下安装双系统Centos,并修复Centos引导加载程序安装在U盘上的问题
1.使用U盘安装Centos时,磁盘分区划分要注意:系统(包含Win7)只能4个主分区,所以只能在删除一个主分区或者在扩展分区的空闲分区内建立目录. 2.Centos在安装步骤的最后,引导加载程序的选 ...
- Linux kernel学习-内存管理【转】
转自:https://zohead.com/archives/linux-kernel-learning-memory-management/ 本文同步自(如浏览不正常请点击跳转):https://z ...
随机推荐
- 项目源码--Android类似酷狗音乐播放器
下载源码 知识技能概要: 1.音乐文件的扫描与管理 2.音频流的解码 3. UI控件的综合使用 4.播放列表方式管理 5.随机播放方式 6.源码带详细的中文注释 ...... 详细介绍 1. 音乐文件 ...
- 关于Android中获取Intent里的数据
Intent获取数据和发送数据的办法: //直接通过Intent发送 intent.putExtra("name","wytings"); //直接通过Inte ...
- solr中竞价排名实现
转载:http://mxsfengg.iteye.com/blog/308335 通常,lucene只返回与用户查询相关的文档,搜索的结果,跟lucene对文档评分有关.而在现实的查询中,我们有些时候 ...
- 通过pinyin4j将汉字转换为全拼 和 拼音首字母
/** * 汉字转换为拼音 包含多音字,包含生母zh,ch,sh的 */ public void toPinYinAll(){ String initials = "zh,ch,sh&quo ...
- IOS plist轻量级操作
plist,全名PropertyList,即属性列表文件,它是一种用来存储串行化后的对象的文件.这种文件,在ios开发过程中经常被用到.这种属性列表文件的扩展名为.plist,因此通常被叫做plist ...
- C#三大支柱之继承
1.使用base 若子类需要使用父类公开或受保护的成员则需要是base class Manager : Employee { public int StockOptions { get; set; } ...
- 12. Android框架和工具之 StringUtils(字符串操作)
1. StringUtils介绍: StringUtils是apache commons lang库(http://commons.apache.org/proper/commons-lang/dow ...
- FormsAuthenticationTicket学习笔记
FormsAuthenticationTicket ticket = , ), true, string.Format("{0}:{1}", "username" ...
- Android之旅:梦想、学习、坚持、自信、淡定
前段时间参加了2012年度IT博客大赛,进了前十强,写了一篇获奖感言,不过还没正式在CSDN发表出来.眼看2012年就要结束了,刚好借这个机会将2012年度IT博客大十强获奖感言发表出来,也算是对20 ...
- UITabbar的简单操作和实际应用
简易编辑Tabbar //**标签栏控制器的初始化 UITabBarController * tabbarC = [[UITabBarController alloc] init]; //设置tab ...