Edi:从保护模式进入实模式函数的地址 Ecx:从实模式进入保护模式函数的地址 eax:实模式中断描述符表的地址
7.3.2.2. 核心代码Startup
这部分代码位于grub-core/kern/i386/pc/startup.S
/*
* 这段代码Startup.S必须加载到0x00100000. */
/* 调用 grub_main进入grub的主函数*/ jmp EXT_C(grub_main)
/* initialize the stack */
movl $GRUB_MEMORY_MACHINE_PROT_STACK, %esp
7.3.3. GRUB主函数即GRUB主要功能
到这来我们已经来到了grub的核心代码部分,这部分代码位于grub-core/kern/main.c
正式开始进入C语言工作,在main函数之后,GRUB2主要工作正式展开,其工作大致如下 - 是grub的模块化框架的初始
- 各种命令的注册 - 各种模块的加载
- 读取/boot/grub2/grub.cfg,显示启动菜单 - 根据菜单配置加载linux kernel
有关怎么配置/etc/grub.d脚本与/etc/default/grub下变量,怎么运行
grub2-mkconfig命令等,已经有很多资料可以查阅,在这里不作描述,有兴趣的读者可以自行研究。
7.3.4. 加载Linux Kernel
我们看看centos7中被加载的内核条目的配置(/boot/grub2/grub.cfg):
menuentry 'CentOS Linux (3.10.0-327.22.2.el7.x86_64) 7 (Core)' --class centos --class gnu-linux --class gnu --class
load_video set gfxpayload=keep insmod gzio insmod part_msdos insmod xfs
set root='hd0,msdos1'
if [ x$feature_platform_search_hint = xy ]; then search else
search --no-floppy --fs-uuid --set=root 149ea7b1-0a88-47aa-ad0a-446b224dd84c fi linux16
/vmlinuz-3.10.0-327.22.2.el7.x86_64
root=/dev/mapper/centos-root
ro
crashkernel=auto
rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet LANG=zh_CN.UTF-8 initrd16 /initramfs-3.10.0-327.22.2.el7.x86_64.img }
--no-floppy
--fs-uuid
--set=root
--hint-bios=hd0,msdos1
--hint-efi=hd0,msdos1
--hint-baremetal=ahci0,msdos1 --hint='hd0,msdos1' 149ea7b1-0a88-47aa-ad0a-446b224dd84c
os
--unrestricted
$menuentry_id_option
'gnulinux-3.10.0-327.el7.x86_64-advanced-a09bce3c-411c-40f4-9487-dcfbe471f022' {
该菜单我们只关心最后两项
linux16 /vmlinuz-3.10.0-327.22.2.el7.x86_64
initrd16 /initramfs-3.10.0-327.22.2.el7.x86_64.img
他们的意思GRUB2会用linux16加载Linux内核vmlinuz-3.10.0-327.22.2.el7.x86_64 然后会用initrd16加载initramfs-3.10.0-327.22.2.el7.x86_64.img
注意,后者被称为:initial RAM disk: The initial RAM disk (initrd) is an initial root file system that is mounted prior to when the real root file system is available.
可以参考:http://www.ibm.com/developerworks/library/l-initrd/index.html
加载的这两个文件都在/boot目录下,可以用file命令查看他们类型
[root@controller boot]# file vmlinuz-3.10.0-327.22.2.el7.x86_64 vmlinuz-3.10.0-327.22.2.el7.x86_64:
Linux
kernel
x86
boot
executable
bzImage,
version
3.10.0-327.22.2.el7.x86_64 (builder@kbuilder.dev.centos.org) #1, RO-rootFS, swap_dev 0x4, Normal VGA [root@controller boot]# file initramfs-3.10.0-327.22.2.el7.x86_64.img
initramfs-3.10.0-327.22.2.el7.x86_64.img: ASCII cpio archive (SVR4 with no CRC)
可以知道内核的类型为bzImage.
可以肯定的是,这个时候GRUB已经有了文件系统(CentOS7的XFS)驱动,不会去调用BIOS INT13去加载这两个文件,而是直接在XFS文件系统/boot目录下加载。
在接下来的讲述之前,看看一个内存的结构
(下面是现代bzImage类型 kernel(version >= 2.02)结构)
~ ~ | Protected-mode kernel | 100000 +------------------------+ | I/O memory hole | 0A0000 +------------------------+ | Reserved for BIOS | Leave as much as possible unused ~ ~ | Command line | (Can also be below the X+10000 mark) X+10000 +------------------------+ | Stack/heap | For use by the kernel real-mode code. X+08000 +------------------------+ | Kernel setup | The kernel real-mode code. | Kernel boot sector | The kernel legacy boot sector. X +------------------------+ | Boot loader | <- Boot sector entry point 0000:7C00 001000 +------------------------+ | Reserved for MBR/BIOS | 000800 +------------------------+ | Typically used by MBR | 000600 +------------------------+ | BIOS use only | 000000 +------------------------+
7.3.4.1. Step1 linux16
/*
* 相关代码在grub-core/loader/i386/pc/linux.c
* 当处理菜单选项”linux16 /vmlinuz-3.10.0-327.22.2.el7.x86_64 ...”时,下面方法会被调用 *
* 在这里只讲述关键代码
* 要理解这部分代码只能去读《THE LINUX/x86 BOOT PROTOCOL》: * https://www.kernel.org/doc/Documentation/x86/boot.txt */
grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), { ......
/* 打开内核文件
The first step in loading a Linux kernel should be to load the real-mode code (boot sector and setup code) and then examine the following header at offset 0x01f1. The real-mode code can total up to 32K, although the boot loader may choose to load only the first two sectors (1K) and then examine the bootup sector size. */
int argc, char *argv[])
file = grub_file_open (argv[0]); ......
/*
* 把文件vmlinuz-3.10.0-327.22.2.el7.x86_64头部读入内存structure1h * 该结构在include/grub/i386/linux.h定义:linux_kernel_header * 里面包含一些内核基本信息,在后面加载内核的时候需要用到
* 该头部与https://www.kernel.org/doc/Documentation/x86/boot.txt一致 */
if (grub_file_read (file, &lh, sizeof (lh)) != sizeof (lh)) { ...... goto fail;
} ......
/*
*计算实模式代码地址,该地址一般0x90000 */
grub_linux_real_target = grub_find_real_target (); ......
/*
*计算需要载入linux内核大小,包含两个部分,分别载入: * 是模式大小,在内核头部定义
* 保护模式大小,内核文件大小在减去实模式大小,在减去1个扇区0x200 512Byte */
real_size = setup_sects<< GRUB_DISK_SECTOR_BITS; grub_linux16_prot_size = grub_file_size (file) ......
/*
*读取实模式(Real_mode)代码到内存0x90000地址
* Unfortunately, under the following circumstances the 0x90000 memory segment has to be used:
- When loading a zImage kernel ((loadflags & 0x01) == 0). - When loading a 2.01 or earlier boot protocol kernel. -> For the 2.00 and 2.01 boot protocols, the real-mode code can be loaded at another address, but it is internally relocated to 0x90000. For the \ real-mode code must be loaded at 0x90000.
When loading at 0x90000, avoid using memory above 0x9a000. */
- real_size - GRUB_DISK_SECTOR_SIZE;