欢迎光临
我们一直在努力

linux内核2.6.32源码系列01-从加电到start_kernel的调用链

一.引言

一台机器从“加电”到“进入 Linux 内核 C 世界(start_kernel)”到底经历了哪些阶段,每一层代码是怎么一步步跳转的?本文以 x86 + Linux 2.6.32 为例,理清 从加电到start_kernel的调用链。

二.从加电到start_kernel的调用链

阶段1:加电→ BIOS

1. CPU 初始状态
加电后:
CPU 从固定地址开始执行(x86):0xFFFF0
这其实是 BIOS ROM 映射地址。
CPU 做的第一件事:jmp far F000:E05B ; 跳转到 BIOS 入口
2. BIOS 做了什么?
BIOS 主要做三件事:
1.硬件初始化(内存、键盘、磁盘等)
2.建立中断向量表(IVT)
3.找启动设备(硬盘 / U盘)
然后读取:磁盘 第0扇区 → 内存 0x7C00
并跳转:jmp 0x0000:0x7C00

阶段2:MBR → Bootloader(GRUB)

1. MBR(512字节)MBR 非常小,只做一件事:把 GRUB stage 2加载进来
2. GRUB 做的事情(关键)
GRUB 是关键,它负责:
1.解析文件系统
2.找到 /boot/vmlinuz-2.6.32
3.加载内核到内存
4.准备 boot params(很重要)
3. GRUB 最终跳转
GRUB 最后会跳到:内核加载地址 + 0x200
也就是:arch/x86/boot/header.S

阶段3:进入 Linux 内核(实模式)

1.入口文件:arch/x86/boot/header.S
关键代码:calllmain 跳转到 arch/x86/boot/main.c的main函数。
2. main函数最后调用go_to_protected_mode函数,开始要进入保护模式。

void main(void)
{
...
go_to_protected_mode();
}

阶段4:进入保护模式

arch/x86/boot/pm.c
void go_to_protected_mode(void)
{
...
/* Enable the A20 gate */
if (enable_a20()) {
puts("A20 gate not responding, unable to boot…\\n");
die();
}
...
setup_idt();
setup_gdt();
protected_mode_jump(boot_params.hdr.code32_start,
(u32)&boot_params + (ds() << 4));
}

arch/x86/boot/pmjump.S
.text
.code16 ##16位实模式
GLOBAL(protected_mode_jump)
movl%edx, %esi# Pointer to boot_params table
...
movl%cr0, %edx
orb$X86_CR0_PE, %dl# Protected mode
movl%edx, %cr0
...
.byte0x66, 0xea# ljmpl opcode
2:.longin_pm32# offset
.word__BOOT_CS# segment
ENDPROC(protected_mode_jump)

.code32
.section ".text32","ax" ##32位保护模式
GLOBAL(in_pm32)
...
jmpl*%eax# Jump to the 32bit entrypoint
ENDPROC(in_pm32)

enable_a20() 打开 A20 地址线(访问 >1MB 内存)
setup_idt() 建立IDT。 IDT是Interrupt Descriptor Table是 x86/x86_64 架构 CPU 用于管理中断和异常的核心数据结构
setup_gdt() 建立GDT。 GDT是Global Descriptor Table的缩写。在 Intel x86 处理器进入保护模式后,通过段选择子索引 GDT 中的描述符,完成逻辑地址到线性地址的转换,并实施内存保护与特权级控制。
protected_mode_jump函数通过将cr0寄存器的PE位置为1开启保护模式。该函数最后long jump到in_pm32,in_pm32已经是32位保护模式下的函数。in_pm32最后通过jmpl *%eax跳转到原先protected_mode_jump函数接受到的参数entrypoint。

阶段5:压缩内核入口(head_32.S)

arch/x86/boot/compressed/head_32.S
...
calldecompress_kernel
...
jmp*%ebp
...

arch/x86/boot/compressed/misc.c
asmlinkage void decompress_kernel(void *rmode, memptr heap,
unsigned char *input_data,
unsigned long input_len,
unsigned char *output)
{
...
decompress(input_data, input_len, NULL, NULL, output, NULL, error);
parse_elf(output);
if (!quiet)
putstr("done.\\nBooting the kernel.\\n");
return;
}

head_32.S的任务是解压内核压缩包,然后 jmp*%ebp 跳转到 真正内核入口(head_32.S)

阶段6:真正内核入口(head_32.S)

arch/x86/kernel/head_32.S
...
jmp *(initial_code)
...
ENTRY(initial_code)
.long i386_start_kernel
...
arch/x86/kernel/head32.c
void __init i386_start_kernel(void)
{
...
start_kernel();
}

head_32.S跳转到 i386_start_kernel函数。i386_start_kernel函数调用start_kernel函数。

三.源码级调用链总结

BIOS
0x7c00 (MBR)
→ GRUB
→ arch/x86/boot/header.S
→ main.c
go_to_protected_mode()
→ arch/x86/boot/pm.c
→ protected_mode_jump
→ arch/x86/boot/pmjump.S
→ arch/x86/boot/compressed/head_32.S
→ arch/x86/kernel/head_32.S
→ arch/x86/kernel/head32.c
→ i386_start_kernel
start_kernel()

赞(0)
未经允许不得转载:171主机测评 » linux内核2.6.32源码系列01-从加电到start_kernel的调用链
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址