前言

对于 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的更多相关文章

  1. 浅析dex文件加载机制

    我们可以利用DexClassLoader来实现动态加载dex文件,而很多资料也只是对于DexClassLoader的使用进行了介绍,没有深入讲解dex的动态加载机制,我们就借助于Android4.4的 ...

  2. 浅析golang shellcode加载器

    最近也是学习了一下有关shellcode进程注入的操作,简单分享一下通过golang进行实现shellcode加载器的免杀思路. 杀软的查杀方式 静态查杀:查杀的方式是结合特征码,对文件的特征段如Ha ...

  3. uboot启动正常,加载内核kernel启…

    先说现象吧:uboot能够正常启动,不过在kernel启动时却出现起不了的现象,停在这里 Uncompressing Linux.................................... ...

  4. 引导加载程序:GRUB

    计算机在启动的时候,首先由BIOS中的程序执行自检,自检通过后,就根据CMOS的配置找到第一个可启动磁盘的MBR中的Boot Loader程序(一般在启动盘的第一个物理扇区,占446字节),并把控制权 ...

  5. 引导加载程序之争: LILO 和 GRUB

    在不考虑他们的工作或专业情况下,所有 Linux 用户都会使用的是哪个工具?引导加载程序.通过本文了解引导加载程序的工作原理,认识两个流行的引导加载程序 LILO(LInux LOader)和 GNU ...

  6. Linux设备驱动程序加载/卸载方法 insmod和modprobe命令

    linux加载/卸载驱动有两种方法. 1.modprobe 注:在使用这个命令加载模块前先使用depmod -a命令生成modules.dep文件,该文件位于/lib/modules/$(uname ...

  7. bootrom/spl/uboot/linux逐级加载是如何实现的?

    关键词:bootrom.spl.uboot.linux.mksheader.sb_header.mkimage.image_header_t等等. 首先看一个典型的bootrom->spl-&g ...

  8. Win7下安装双系统Centos,并修复Centos引导加载程序安装在U盘上的问题

    1.使用U盘安装Centos时,磁盘分区划分要注意:系统(包含Win7)只能4个主分区,所以只能在删除一个主分区或者在扩展分区的空闲分区内建立目录. 2.Centos在安装步骤的最后,引导加载程序的选 ...

  9. Linux kernel学习-内存管理【转】

    转自:https://zohead.com/archives/linux-kernel-learning-memory-management/ 本文同步自(如浏览不正常请点击跳转):https://z ...

随机推荐

  1. Linux 下五个顶级的开源命令行 Shell

    这个世界上有两种 Linux 用户:敢于冒险的和态度谨慎的. 其中一类用户总是本能的去尝试任何能够戳中其痛点的新选择.他们尝试过不计其数的窗口管理器.系统发行版和几乎所有能找到的桌面插件. 另一类用户 ...

  2. Linux下的lds链接脚本基础

    转载:http://soft.chinabyte.com/os/104/12255104.shtml   今天在看uboot引导Linux部分,发现要对链接脚本深入了解,才能知道各个目标文件的内存分布 ...

  3. View Controller 视图管理总结

    View controller是iOS中顶层的视图载体和控制器,它需要对view负责,管理view的生命周期,相关处室话以及交互事件,除此以外还需要为view提供合适的数据,以供view使用. Vie ...

  4. PHP面向对象(二)--构造函数与析构函数

    一.构造方法: 构造方法又称为构造函数,是对象被创建时自动调用的方法,用来完成类初始化的工作. 1.构造方法和其他函数一样,可以传递参数,可以设定参数默认值. 2.构造方法可以调用属性,也可以调用方法 ...

  5. 一些英文面试题(Android)

    Describe the APK format. The APK file is compressed the AndroidManifest.xml file, application code ( ...

  6. Hadoop学习笔记(5) ——编写HelloWorld(2)

    Hadoop学习笔记(5) ——编写HelloWorld(2) 前面我们写了一个Hadoop程序,并让它跑起来了.但想想不对啊,Hadoop不是有两块功能么,DFS和MapReduce.没错,上一节我 ...

  7. 写一个函数,将一个int型的数组做为参数传入,使用指针返回两个结果:最大值和最小值

    今日下午研究了一下c语言中的指针问题,c语言的核心是指针,指针的核心是地址,地址的核心是内存. #include <stdio.h> void hanshu(int *arry,int s ...

  8. shell配置环境变量

    我们在使用Xshell开发机时往往会遇到一些问题,比如使用某些命令他会报command not found,,出现这种情况的原因是我们必须要配置一下环境变量,这样就可以在任何path中访问了. 这就需 ...

  9. dfa最小化,修正了上个版本的一些错误。

    上个版本测试的时候,只用了两个非常简单的测试用例,所以好多情况有问题却没有测试出来 bug1:在生成diff_matrix的时候,循环变量少循环了一次,导致最后一个节点在如果无法与其他点合并的情况下, ...

  10. vue-cli 发布(译)

    如果你现在正在使用Vue.js,当你构建一个原型的时候,你所需要做的通常就是通过<script>把Vue.js引入进来,然后就完事了.但是真实情况往往不是这样的.当我们真正开发一个应用的时 ...