感谢支持
我们一直在努力

浅析 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 的加载是特殊对待的, 但具体怎么特使对待,只能从源代码里看了。

如何在Ubuntu12.04/12.10中重装或修复Grub2引导 http://www.linuxidc.com/Linux/2012-11/74901.htm

Linux启动引导过程 grub和mbr http://www.linuxidc.com/Linux/2013-07/87923.htm

grub 的安装与使用 http://www.linuxidc.com/Linux/2013-07/87682.htm

grub引导程序配置文件分析 http://www.linuxidc.com/Linux/2013-07/87547.htm

CentOS 6.4 grub加密码 http://www.linuxidc.com/Linux/2013-07/87124.htm

CentOS GRUB引导错误无法进入系统解决办法 http://www.linuxidc.com/Linux/2014-11/108835.htm

更多详情见请继续阅读下一页的精彩内容: http://www.linuxidc.com/Linux/2015-02/113021p2.htm

分析 GRUB 源码
从[GRUB 官网](http://www.gnu.org/software/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 https://www.kernel.org/doc/Documentation/x86/boot.txt  中看到该结构体更多信息。 

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 文件内的参数加载了。

本文永久更新链接地址:http://www.linuxidc.com/Linux/2015-02/113021.htm

赞(0) 打赏
转载请注明出处:服务器评测 » 浅析 GRUB 如何加载 Linux kernel
分享到: 更多 (0)

听说打赏我的人,都进福布斯排行榜啦!

支付宝扫一扫打赏

微信扫一扫打赏