浅析 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 ...
随机推荐
- c基础补充
一:字符串在C语言中 字符串一般是动态分配和字符数组两种形式 动态分配的时候字符串字符串指针是4个字节 字符串的大小根据自己的需要来申请 比如: char * c://C为字符串指针 占用四个字节 c ...
- Use Spring Insight Developer to Analyze Code, Install it with Tomcat, and Extend it with Plugins--转载
原文地址:http://www.tomcatexpert.com/blog/2012/12/05/use-spring-insight-developer-analyze-code-install-i ...
- DOS命令如何进入指定的下一级目录?
如cd E:\java workspace\test\src 如何进入E盘 ,直接 E:
- iOS H5容器的一些探究(二):iOS 下的黑魔法 NSURLProtocol
来源:景铭巴巴 链接:http://www.jianshu.com/p/03ddcfe5ebd7 iOS H5 容器的一些探究(一):UIWebView 和 WKWebView 的比较和选择 一.前言 ...
- C# 代码生成工具 Millennials
Millennials 是一个可定制的 C# 代码生成工具,支持 MVC 和三层架构.ADO.NET.Nhibernate 和 LINQ. 项目主页:http://www.open-open.com/ ...
- WPF 之 鼠标双击事件
由于WPF中没有鼠标的双击事件,因而只能通过MouseDown事件来模拟.当连续的两次MouseDown事件的时间间隔,没有超过一个设定的时间阈值时,就计算为一个双击事件,并作相应的处理. 利用WPF ...
- WPF 之 布局(三)
六.DockPanel DockPanel定义一个区域,在此区域中,您可以使子元素通过描点的形式排列,这些对象位于 Children 属性中.停靠面板其实就是在WinForm类似于Dock属性的元 素 ...
- ASP.NET 批量更新
ASP.NET三种常用批量操作: 一.SqlBulkCopy copy = new SqlBulkCopy("");(优先考虑 性能最优) SqlBulkCopy可以将一个Data ...
- [JavaEE] Hibernate ORM
一. Hibernate的简要介绍 Hibernate是轻量级Java EE应用的持久层解决方案,Hibernate不仅管理者Java类到数据库表的映射(包括Java 数据类型到SQL数据类型的映射) ...
- extjs采用fileupload进行文件上传后台实现
前台js: form: Ext.define("GS.base.BasicImportForm",{ extend:"Ext.form.Panel", ...