这次该进入main函数了,在arch/x86/boot/main.c中。
void main(void)
{
/* First, copy the boot header into the "zeropage" */
copy_boot_params(); # 将hdr的参数拷贝到结构体boot_params.hdr中,在文件boot/bootparam.h里
/* Initialize the early-boot console */
console_init();
if (cmdline_find_option_bool("debug"))
puts("early console in setup code/n");
/* End of heap check */
init_heap();
/* Make sure we have all the proper CPU support */
if (validate_cpu()) {
puts("Unable to boot - please use a kernel appropriate "
"for your CPU./n");
die();
}
/* Tell the BIOS what CPU mode we intend to run in. */
set_bios_mode();
/* Detect memory layout */
detect_memory();
/* Set keyboard repeat rate (why?) */
keyboard_set_repeat();
/* Query MCA information */
query_mca();
/* Query Intel SpeedStep (IST) information */
query_ist();
/* Query APM information */
#if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
query_apm_bios();
#endif
/* Query EDD information */
#if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
query_edd();
#endif
/* Set the video mode */
set_video();
/* Do the last things and invoke protected mode */
go_to_protected_mode();
}
这个函数都可以看懂,至于各个函数具体实现有兴趣可以深入研究,这里主要还是要看这个函数go_to_protected_mode()。
来自boot/pm.c:
/*
* Actual invocation sequence
*/
void go_to_protected_mode(void)
{
/* Hook before leaving real mode, also disables interrupts */
realmode_switch_hook(); # 这里就是调用了实模式下设置的函数地址,可以看hdr的realmode_swtch
/* 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(); # 可以看看gdt的布局
protected_mode_jump(boot_params.hdr.code32_start,
(u32)&boot_params + (ds() << 4)); # 注意进入保护模式后会采用段式管理,而cs,ds这些段寄存器选择子指向的首地址都
# 是0,因此要将传入的参数转换为线性地址
}
protected_mode_jump这个函数是用汇编写的。
来自boot/pmjump.S:
GLOBAL(protected_mode_jump)
movl %edx, %esi # Pointer to boot_params table
# 指向了传进来的第二参数,这里好像违背了C调用约定,关于调用约定这块一直还是个疑问,
# 由于对分析代码没有什么障碍,于是可以先搁着,后面会找到相关资料说明的,
# 这里呢,eax=第一个参数,edx=第二个参数
xorl %ebx, %ebx # 清0
movw %cs, %bx # 将实模式下的代码段放入bx中
shll $4, %ebx /*left move*/ # 左移4为转换为线性地址
addl %ebx, 2f /*[2f]=$in_pm32*/
# 注意这里的2f指的是下标指向的内存,可以看到下面它指向了in_pm32,也就是说,2f指向的long型内存保存了
# 了in_pm32的地址,这里的addl指令就是将in_pm32实模式地址转换成线性地址,于是要加上原来的段地址
jmp 1f # Short jump to serialize on 38**86
1:
movw $__BOOT_DS, %cx # ds段选择子
movw $__BOOT_TSS, %di # tss段选择子
movl %cr0, %edx
orb $X86_CR0_PE, %dl # Protected mode /*X86_CR0_PE=0x00000001*/
movl %edx, %cr0
# 进入保护模式的标志就是将cr0寄存器的位0置位即可,然后进入保护模式
# Transition to 32-bit mode
.byte 0x66, 0xea # ljmpl opcode
2: .long in_pm32 # offset
.word __BOOT_CS # segment
# 整个就是一个长跳转指令,也就是跳到in_pm32下执行,注意现在已经在保护模式了
ENDPROC(protected_mode_jump)
.code32
.section ".text32","ax"
GLOBAL(in_pm32)
# Set up data segments for flat 32-bit mode
movl %ecx, %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
# 设置栈底,ebx保存是原来实模式下的cs段
# Set up TR to make Intel VT happy
ltr %di # 加载TR,实际只是个伪装
# Clear registers to allow for future extensions to the
# 32-bit boot protocol
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,实际只是个伪装
jmpl *%eax # Jump to the 32-bit entrypoint
# eax保存着protected_mode_jump函数传进来的第一个参数,也就是boot_params.hdr.code32_start,这里存着vmlinux的首地址
ENDPROC(in_pm32)
这里一个长跳转直接将我们带到了另外一个世界,因为这个世界很孤独,它虽然是内核的一部分,但是不是内核的身体,要想靠近内核的更核心,必须进入vmlinux。即将我们将到带自解压的内核中去旅行。这里,我还是得再次说明,启动部分的代码涉及很多内容,如果你连最基本的什么是GDT、LDT、TSS,那些最好去看看关于CPU和操作系统的原理性的书籍,里面有讲。你会问,这里为什么不直接说说呢,恐怕你还不晓得,说这些估计得说到明年都说不完,知识点很多,其实这些内容不会困难,主要是有些多。再次强调了,我们关注的重点,熟练掌握内核分析方法,寻找设备模型。
还要提醒一点,我们已经进入保护模式,但只是启动了分段模式,分页还没启动呢。