/* First, copy the boot header into the "zeropage" */
copy_boot_params();
复制启动参数到hdr中
static void copy_boot_params(void)
{
struct old_cmdline {
u16 cl_magic;
u16 cl_offset;
};
const struct old_cmdline * const oldcmd =
(const struct old_cmdline *)OLD_CL_ADDRESS;// 0x020
BUILD_BUG_ON(sizeof boot_params != 4096);
memcpy(&boot_params.hdr, &hdr, sizeof hdr);//hdr在header.S中定义
if (!boot_params.hdr.cmd_line_ptr &&//cmd_line_ptr == 0x99000
oldcmd->cl_magic == OLD_CL_MAGIC) {
/* Old-style command line protocol. */
u16 cmdline_seg;
/* Figure out if the command line falls in the region
of memory that an old kernel would have copied up
to 0x90000... */
if (oldcmd->cl_offset < boot_params.hdr.setup_move_size)
cmdline_seg = ds();
else
cmdline_seg = 0x9000;
boot_params.hdr.cmd_line_ptr =
(cmdline_seg << 4) + oldcmd->cl_offset;
}
}
有用的就两行
BUILD_BUG_ON(sizeof boot_params != 4096);
memcpy(&boot_params.hdr, &hdr, sizeof hdr);//hdr在header.S中定义
BUILD_BUG_ON是一个宏
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
如果条件为真则引起一个编译时错误。
因为(void)sizeof(char[-1])将触发 error: size of unnamed array is negative编译错误
(void)的作用是避免触发warning: statement with no effect [-Wunused-value]编译警告
hdr的声明在boot.h中
extern struct setup_header hdr;
定义在header.S中
接下来调用init_heap
main->init_heap
static void init_heap(void)
{
char *stack_end;
//header.S中movw $GRUB_LINUX_SETUP_STACK, %sp #0x9000,一直到后面的0x9100处是command line
if (boot_params.hdr.loadflags & CAN_USE_HEAP) {//条件为true,参考grub
asm("leal %P1(%%esp),%0"
: "=r" (stack_end) : "i" (-STACK_SIZE));// 512栈大小
// leal -512(%esp),%eax
//没有P,则会生成leal $-512(%esp),%eax,出错
//stack_end指向栈底
heap_end = (char *)
((size_t)boot_params.hdr.heap_end_ptr + 0x200);// heap_end_ptr=0x9000-0x200,见grub
if (heap_end > stack_end)//超过栈底,
heap_end = stack_end;
} else {
/* Boot protocol 2.00 only, no heap available */
puts("WARNING: Ancient bootloader, some functionality "
"may be limited!\n");
}
}
到此,可以归纳出实模式下的内存组织
0x9000:x00000----------------------
bootsect.S
0x9000:0x0200----------------------_start
setup.S
-----------------------------__bss_start
未初始化区
------------------------------_end
临时堆
------------------------------heap_end
临时栈(512B)
0x9000:0x9000---------------------- 栈底
cmdline(256B)
0x9000:0x9100______________________
main->detect_memory->detect_memory_e820
使用系统中断获得系统内存配置情况
#define SMAP 0x534d4150 /* ASCII "SMAP" */
static int detect_memory_e820(void)
{
int count = 0;
struct biosregs ireg, oreg;
struct e820entry *desc = boot_params.e820_map;
static struct e820entry buf; /* static so it is zeroed */
/*
Input:
EAX 功能代码 0XE820
EBX 继续信息 包含获取下一块物理内存的继续值,这一值是由上一次调用返回的,如果是第一次调用,则为0
ES:DI 缓冲区指针 指向 BIOS 要填写的地址描述符结构体
ECX 缓冲区大小 传入 BIOS 结构体字节数,BIOS 和调用者支持最小为 20 字节。
EDX 标识符 'SMAP' - 核实 BIOS 正确版本的标识
Output:
CF 进位标志 无进位标志表明没有错误
EAX 标识符 'SMAP' - 核实 BIOS 正确版本的标识
ES:DI 缓冲区指针 返回地址描述符缓冲区指针,与输入相同
ECX 缓冲区大小 传入 BIOS 结构体字节数,与输入相同
EBX 继续信息 包含得到下一地址描述符的继续值。为了迭代调用E820 获得下一地址描述符,调用者必须传入
不经改变的继续值。返回值为 0 表示这是最后一个地址描述符。
*/
initregs(&ireg);
ireg.ax = 0xe820;
ireg.cx = sizeof buf;
ireg.edx = SMAP;
ireg.di = (size_t)&buf;
/*
* Note: at least one BIOS is known which assumes that the
* buffer pointed to by one e820 call is the same one as
* the previous call, and only changes modified fields. Therefore,
* we use a temporary buffer and copy the results entry by entry.
*
* This routine deliberately does not try to account for
* ACPI 3+ extended attributes. This is because there are
* BIOSes in the field which report zero for the valid bit for
* all ranges, and we don't currently make any use of the
* other attribute bits. Revisit this if we see the extended
* attribute bits deployed in a meaningful way in the future.
*/
do {
intcall(0x15, &ireg, &oreg);//在bioscall.S中
ireg.ebx = oreg.ebx; /* for next iteration...下次循环需要这个值 */
/* BIOSes which terminate the chain with CF = 1 as opposed对照
to %ebx = 0 don't always report the SMAP signature on
the final, failing, probe. */
if (oreg.eflags & X86_EFLAGS_CF)//出错
break;
/* Some BIOSes stop returning SMAP in the middle of
the search loop. We don't know exactly how the BIOS
screwed up the map at that point, we might have a
partial map, the full map, or complete garbage, so
just return failure. */
if (oreg.eax != SMAP) {
count = 0;
break;
}
*desc++ = buf;//复制到boot_params
count++;
} while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map));
return boot_params.e820_entries = count;//赋值
}
main的最后一步是调用go_to_protected_mode,进入保护模式,不再返回
/*
* Actual invocation sequence
*/
void go_to_protected_mode(void)
{
/* Hook before leaving real mode, also disables interrupts */
realmode_switch_hook();
/* Enable the A20 gate */
if (enable_a20()) {
puts("A20 gate not responding, unable to boot...\n");
die();
}
/* Reset coprocessor (IGNNE#) */
reset_coprocessor();
/* Mask all interrupts in the PIC */
mask_all_interrupts();
/* Actual transition to protected mode... */
setup_idt();
setup_gdt();
//在pmjump.S中
protected_mode_jump(boot_params.hdr.code32_start,
(u32)&boot_params + (ds() << 4));
}
setup_idt,中断描述符表指向空描述符
/*
* Set up the IDT
*/
static void setup_idt(void)
{
static const struct gdt_ptr null_idt = {0, 0};
asm volatile("lidtl %0" : : "m" (null_idt));
}
static void setup_gdt(void)
{
/* There are machines which are known to not boot with the GDT
being 8-byte unaligned. Intel recommends 16 byte alignment. */
static const u64 boot_gdt[] //__attribute__((aligned(16)))
= {
/* CS: code, read/execute, 4 GB, base 0 */
[GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff),
/* DS: data, read/write, 4 GB, base 0 */
[GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff),
/* TSS: 32-bit tss, 104 bytes, base 4096 */
/* We only have a TSS here to keep Intel VT happy;
we don't actually use it for anything. */
[GDT_ENTRY_BOOT_TSS] = GDT_ENTRY(0x0089, 4096, 103),
};
/* Xen HVM incorrectly stores a pointer to the gdt_ptr, instead
of the gdt_ptr contents. Thus, make it static so it will
stay in memory, at least long enough that we switch to the
proper kernel GDT. */
static struct gdt_ptr gdt;
gdt.len = sizeof(boot_gdt)-1;
gdt.ptr = (u32)&boot_gdt + (ds() << 4);
asm volatile("lgdtl %0" : : "m" (gdt));
}
临时GDT表中有四个描述符,NULL,BOOT_CS,BOOT_DS,BOOT_TSS
最后执行protected_mode_jump
//在pmjump.S中
protected_mode_jump(boot_params.hdr.code32_start,
(u32)&boot_params + (ds() << 4));
code32_start值为0x100000,后一个表达式计算boot_params的线性地址
/* ----------------------------------------------------------------------- *
*
* Copyright (C) 1991, 1992 Linus Torvalds
* Copyright 2007 rPath, Inc. - All Rights Reserved
*
* This file is part of the Linux kernel, and is made available under
* the terms of the GNU General Public License version 2.
*
* ----------------------------------------------------------------------- */
/*
* The actual transition into protected mode
*/
#include
#include
#include
#include
.text
.code16 #现在还是16位模式
/*
* void protected_mode_jump(u32 entrypoint, u32 bootparams);
*/
GLOBAL(protected_mode_jump)
movl %edx, %esi # Pointer to boot_params table
xorl %ebx, %ebx
movw %cs, %bx
shll $4, %ebx #0x90000
addl %ebx, 2f#计算in_pm32的线性地址
jmp 1f # Short jump to serialize on 386/486
1:
movw $__BOOT_DS, %cx
movw $__BOOT_TSS, %di
movl %cr0, %edx
orb $X86_CR0_PE, %dl # Protected mode 置保护位
movl %edx, %cr0
# Transition to 32-bit mode 进入32位保护模式
.byte 0x66, 0xea # ljmpl opcode
2: .long in_pm32 # offset
.word __BOOT_CS # segment
ENDPROC(protected_mode_jump)
.code32 #现在是32位模式
.section ".text32","ax"
GLOBAL(in_pm32)
# Set up data segments for flat 32-bit mode
movl %ecx, %ds #都指向BOOT_DS
movl %ecx, %es
movl %ecx, %fs
movl %ecx, %gs
movl %ecx, %ss
# The 32-bit code sets up its own stack, but this way we do have
# a valid stack if some debugging hack wants to use it.
addl %ebx, %esp #调整esp,高位怎么办? 保证是0? header.S中 movzwl %dx, %esp # Clear upper half of %esp 0x9000
# Set up TR to make Intel VT happy
ltr %di #记载TR
# Clear registers to allow for future extensions to the
# 32-bit boot protocol
#eax中存放code32_start
#esi存放boot_params
xorl %ecx, %ecx #清空寄存器
xorl %edx, %edx
xorl %ebx, %ebx
xorl %ebp, %ebp
xorl %edi, %edi
# Set up LDTR to make Intel VT happy
lldt %cx #ldt执行NULL描述符
jmpl *%eax # Jump to the 32-bit entrypoint 跳到0x100000
ENDPROC(in_pm32)
main的任务结束