前言

对于 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服务器之redis

    linux环境下安装redis服务器: redis下载地址:http://redis.io/download $ wget http://download.redis.io/releases/redi ...

  2. The Kernel Newbie Corner: Kernel Debugging Using proc "Sequence" Files--Part 1

    转载:https://www.linux.com/learn/linux-career-center/37985-the-kernel-newbie-corner-kernel-debugging-u ...

  3. ES6新特性以及一些规范

    1.let:使变量成为块级变量,类似于C++,java类的变量 b = 2 if (b == 2) { let c = 2; } console.log(c) // 报错,因为c是一个块级变量,只存在 ...

  4. c# checked unchecked 关键字

    checked 和 unchecked关键字用来限定检查或者不检查数学运算溢出的:如果使用了checked发生数学运算溢出时会抛出OverflowException:如果使用了unchecked则不会 ...

  5. entity framework 连接 oracle 发布后出现的问题(Unable to find the requested .Net Framework Data Provider)

    用entity framework 搭建的一个windows 程序,在vs中用oracle 的ODT 工具连接oracle数据库,昨天发布后出现下面一个错误, System.ArgumentExcep ...

  6. FVDI Commander products be replaced SVDI tools,really?

    You may have heard that some FVDI Commander products are being replaced by the new SVDI tools. This ...

  7. 『创意欣赏』30幅逼真的 3D 虚拟现实环境呈现

    又到周末了,给大家分享30幅漂亮的 3D 虚拟现实环境呈现,放松一下.这些创造性的场景都是通过 3D 图形设计软件,结合三维现实环境渲染制作出来的.一起欣赏:) 您可能感兴趣的相关文章 20幅温馨浪漫 ...

  8. iOS平常注意1

    在写oc代码时的注意有一些错误看看各位朋友在平常注意了编写是的小错误我也会不断总结的 1. [NSTimer scheduledTimerWithTimeInterval:1 target:self ...

  9. javascript,jquery(闭包概念)

    (function($){     $("div p").click(...);})(jQuery);    就是等于function tempFunction($){  //创建 ...

  10. mysql的相关操作

    查看当前登录用户: mysql> select USER(); +----------------+ | USER() | +----------------+ | root@localhost ...