x86下 BIOS之后linux内核启动之前

阅读数:108 评论数:0

跳转到新版页面

分类

Linux

正文

GRUB

1、BIOS最后会读取引导设备第一个扇区的前 512 字节(MBR),将其读入到内存 0x0000:7C00,并跳转至此处执行。而安装在MBR的一般是GRUB stage1。

2、stage1位于stage1/stage1.S,功能是将0面0道第2扇区上的512字节读到内存中的0x00007000处,然后调用COPY_BUFFER将其复制到0x00008000的位置上,然后跳转到0x00008000处执行,这512字节来自stage2/start.S,作用是加载stage1_5或才stage2。(编译时决定)

/* start.S */
blocklist_default_start:
.long 2	 /* 从第3扇区开始*/
blocklist_default_len:
/* 需要读取多少个扇区 */
#ifdef STAGE1_5
.word 0	 /* 如果是 STAGE1_5,则不读入 */
#else
.word (STAGE2_SIZE + 511) >> 9 /* 读入 Stage2 所占的所有扇区 */
#endif
blocklist_default_seg:
#ifdef STAGE1_5
.word 0x220 /* 将 stage1.5 加载到 0x2200 */
#else
.word 0x820	/* 将 stage2 加载到 0x8200 */
#endif

3、stage1不具备文件系统识别功能,而stage1_5是具有读取文件系统的能力,而GRUB的核心映像是stage2。

4、stage2将系统切换到保护模式,设置C运行环境,寻找config文件,执行sehll接受用户命令,载入选定的操作系统内核。

5、stage2的入口点是asm.c

#ifdef STAGE1_5
# define	ABS(x)	((x) - EXT_C(main) + 0x2200)
#else
# define	ABS(x)	((x) - EXT_C(main) + 0x8200)
#endif

6、随后进入stage2.c,执行GRUB的主要功能

(1)cmain()主函数,载入配置文件 menu.lst(GRUB 1)或 grub.cfg(GRUB 2),如果成功载入就进入 run_menu(),显示菜单,进入循环倒计时,如果超时就进入第一个,如果用户按了键就停止倒计时。用户作出选择后,跳转到 boot_entry(),清空屏幕、获取入口,通过 find_command 找到的函数指针调用相应的命令。

(2)如果没有成功载入配置文件,就 enter_cmdline(),也是通过 find_command 调用相应的命令。

x86体系结构相关-内核映像结构

根据 arch/x86/boot/Makefile,bzImage 大内核映像由 setup.elf 和 vmlinux 组成,而 vmlinux 又由 setup.bin 和 vmlinux.bin 组成。vmlinux.bin 会进行压缩存储,变成 vmlinux.bin.gz。因此 bzImage 由 setup.elf、setup.bin、vmlinux.bin.gz 三部分组成。

Line 28: targets         := vmlinux.bin setup.bin setup.elf zImage bzImage
Line 29: subdir-         := compressed
Line 30: 
Line 31: setup-y         += a20.o cmdline.o copy.o cpu.o cpucheck.o edd.o
Line 32: setup-y         += header.o main.o mca.o memory.o pm.o pmjump.o
Line 33: setup-y         += printf.o string.o tty.o video.o video-mode.o version.o

其中setup-y就是setup.elf,其中引用的header.o是从header.S汇编而来的。

大内核情况下的内存分布图:

        |  vmlinux               |   
100000  +------------------------+
        |  setup.elf的setup部分   |
090200  +------------------------+
        |  setup.elf的启动扇区     |
090000  +------------------------+
        |  GRUB           |
007c00  +------------------------+
        |                        |
000000  +------------------------+

我们先看看用于控制 arch/x86/boot 下代码进行链接的 setup.ld。

ld 文件用于控制 ld 的链接过程:

(1)描述输入文件的各节如何对应到输出文件的各节

(2)控制输入文件各节及符号的内存布局

每个对象文件有一个节(section)列表、一个符号列表,一个符号可以是已定义或未定义的。每个已定义的符号有地址。未定义的符号则要在链接时从其他文件中寻找其定义。

1、指定输出文件格式

OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")

2、指定目标体系结构

OUTPUT_ARCH(i386)
3、设置入口点
ENTRY(_start)
4、输入文件各节到输出文件的映射
SECTIONS
{
. = 0				// 从 0 开始
.bstext : { *(.bstext) }	// 所有输入文件的 .bstext 节组合成输出文件的 .bstext 节
.bsdata : { *(.badata) }	// 所有输入文件的 .bsdata 节...
. = 497				// 填充 512 字节的 bootloader( header.S)
.header : { *(.header) }

在每一部分(header、rodata、data、bss、end)之间,对齐 16 字节内存边界:

. = ALIGN(16);

最后用断言保证链接后的目标文件不太大,且偏移量正确。

x86体系结构相关-header.S

start2:
	movw	%cs, %ax        # CS = 0x7c00
	movw	%ax, %ds	# 初始化段寄存器
	movw	%ax, %es
	movw	%ax, %ss
	xorw	%sp, %sp
	sti			# 开中断
	cld			# di++, si++
................................
msg_loop:			# 打印字符例程
................................
bs_die:				# 错误处理例程
        .ascii  "Direct booting from floppy is no longer supported.\r\n"
        .ascii  "Please use a boot loader program instead.\r\n"
        .ascii  "\n"
        .ascii  "Remove disk and press any key to reboot . . .\r\n"
        .byte   0

这段代码编译链接后,会生成 512 字节的 bootsector(setup.elf的启动扇区),其中 .section ".header", "a" 中的变量共 15 字节。注意到 setup.ld (Linker script for the i386 setup code) 中加入了 497 字节的空白,事实上恰好凑够 512 字节。

GRUB 将 setup.elf 读到 0x90000 处,将 vmlinux 读到 0x100000 处,然后跳转到 0x90200 开始执行(恰好跳过了 512 字节的 bootsector),执行最后是跳转到start_of_setup。

 # Part 2 of the header, from the old setup.S
................................
# End of setup header #####################################################

上面这两行之间的代码是一个庞大的数据结构,与 include/asm/bootparam.h 中的 struct setup_header 一一对应。这个数据结构定义了启动时所需的默认参数,其中一些参数可以通过命令选项 overwrite。下表列出了一些参数的意义。

名称 偏移 大小(字节) 意义
root_flags 0x1f2 2 根目录是否只读,可用 ro 或 rw 选项指定
root_dev 0x1fc 2 默认的 root 设备,即 /boot 所在目录,可用 root= 选项指定
boot_flag 0x1fe 2 0xAA55,即主引导扇区结束标志
header 0x202 4 HdrS (0x53726448),内核标志
version 0x206 2 启动协议版本号: major * 64 + minor
kernel_version 0x20e 2 内核版本号
type_of_loader 0x210 1 Boot loader ID: Boot loader ID * 64 + Version No.
Boot loader IDs:
0 LILO
1 Loadlin
2 bootsect-loader
3 SYSLINUX
4 EtherBoot
5 ELILO
7 GRuB
8 U-BOOT
9 Xen
A Gujin
B Qemu
loadflags 0x211 1 启动选项的掩码。
  • Bit 0: LOADED_HIGH (1表示保护模式代码加载到 0x100000)
  • Bit 7: CAN_USE_HEAP (为1表示 heap_end_ptr 有效)
code32_start 0x214 4 内核解压缩前立即跳转到的 32 位 flat-mode 入口
ramdisk_image 0x218 4 initramfs 的 32 位线性地址
cmd_line_ptr 0x228 4 内核命令行的 32 位线性地址

从start_of_setup,跳转到arch/x86/boot/main.c中,暂时离开汇编,主要作用是初始化boot_params。

include/asm/bootparam.h 中定义的 boot_params 结构体 (即 zeropage) 在此完成初始化。

下面进入 boot/pmjump.S 中的 protected_mode_jump。

29 protected_mode_jump:
 30         movl    %edx, %esi              # Pointer to boot_params table
 31 
 32         xorl    %ebx, %ebx
 33         movw    %cs, %bx                # 将实模式的代码段放入 bx
 34         shll    $4, %ebx                # 转换为线性地址
 35         addl    %ebx, 2f                # 将 in_pm32 的实模式地址转换为线性地址
 36 
 37         movw    $__BOOT_DS, %cx         # ds 段选择子
 38         movw    $__BOOT_TSS, %di        # tss 段选择子
 39 
 40         movl    %cr0, %edx
 41         orb     $X86_CR0_PE, %dl        # Protected mode
 42         movl    %edx, %cr0              # 将 cr0 的0位置0是进入保护模式的标志
 43         jmp     1f                      # Short jump to serialize on 386/486
 44 1:
 45         # 下面这段作用是跳转到 in_pm32,由于已经在保护模式,所以需要考虑段的问题
 46         # Transition to 32-bit mode
 47         .byte   0x66, 0xea              # ljmpl opcode
 48 2:      .long   in_pm32                 # offset
 49         .word   __BOOT_CS               # segment
 50 
 51         .size   protected_mode_jump, .-protected_mode_jump
 52 
 53         .code32
 54         .type   in_pm32, @function
 55 in_pm32:        # 下面的注释挺清楚,就不翻译了
 56         # Set up data segments for flat 32-bit mode
 57         movl    %ecx, %ds
 58         movl    %ecx, %es
 59         movl    %ecx, %fs
 60         movl    %ecx, %gs
 61         movl    %ecx, %ss
 62         # The 32-bit code sets up its own stack, but this way we do have
 63         # a valid stack if some debugging hack wants to use it.
 64         addl    %ebx, %esp
 65 
 66         # Set up TR to make Intel VT happy
 67         ltr     %di                     # 这个比较有意思
 68 
 69         # Clear registers to allow for future extensions to the
 70         # 32-bit boot protocol
 71         xorl    %ecx, %ecx
 72         xorl    %edx, %edx
 73         xorl    %ebx, %ebx
 74         xorl    %ebp, %ebp
 75         xorl    %edi, %edi
 76 
 77         # Set up LDTR to make Intel VT happy
 78         lldt    %cx                     # 又是一个骗 CPU 的东西

 79         # eax 是 protected_mode_jump 的第一个参数,即 header.S 中定义的 boot_params.hdr.code32_start,即 vmlinux 的入口地址
 80         jmpl    *%eax                   # Jump to the 32-bit entrypoint
 81 
 82         .size   in_pm32, .-in_pm32

末尾的 jmpl 指令把我们带入了 vmlinux 的世界。注意到,vmlinux 是压缩存储的,因此内核首先的工作就是把真正的内核解压出来。

根据 Makefile,linux 内核文件有以下几种:

  • vmlinux: 原始的 linux 内核
  • zImage: 经过 gzip 压缩后的 vmlinux,解压到 640KB 内存位置
  • bzImage: 大内核版的 zImage,解压到 1MB 内存位置,现在我们一般都用这个
  • vmlinuz: 指向 zImage 或 bzImage 的链接
  • initrd: init ram disk,用于引导 vmlinuz

循着 Makefile 的踪迹,我们找到了 arch/x86/boot/compressed/head_32.S,这就是大内核模式下 0x100000 开始的内存内容。

至此,arch/x86/boot 下的流程基本分析完毕。

然后跳转到真正的内核入口是 arch/x86/kernel/head_32.S

 





 




相关推荐

mainline 、longterm、stable、linux-next、snapshot这些名词,都是linux kernel sou

__CONCAT,连接两个参数,##用于粘贴两个参数,#用于替换参数 #define __CONCAT(a, b) a ##

基本概念 1、BIOS BIOS即基本输入/输出系统,它是被固化在计算机ROM芯片上的一组程序。它是微机系统软、硬件之间的一个可编程接口,通过跳线开关和系统配带的

<img src="http://123.56.17.129:2177/images/linux_kernel.png" alt="" /

在编程中,一个符号symbol是一个程序的创建块,它是一个变量名或一个函数名。 内核符号表(Kernel Symbol Table) 内核并不使用符号名。它是通过

在Kernel_path/Makefile中可以查看到 # SPDX-License-Identifier: GPL

一、概述 在 Linux 内核中,printk 是一个类似于用户空间 C 语言中的 printf 函数的内核级日志记录功能。它用于内核代码中输出调试信息到控制台或系统日志。printk 可以在几乎任何

一、概述  在日常工作中,经常会需要对内核进行Debug、或者进行优化工作。一些简单的问题,可以通过dmesg/printk查看,优化借助一些工具进行。 但是当问题逻辑复杂,优化面宽泛的时候,往往无从

一、概述 在 Linux 内核编程中,asmlinkage 是一个关键字,用于告诉编译器该函数将通过堆栈而不是寄存器接收所有的参数。这个宏主要用于那些需要从用户空间通过系统调用接口调用的函数。在系统调

上图是Intel手册中对标志寄存器的图示,几个