Kernel源码分析-系统启动(一)
前几天沉迷调试SeaBIOS无法自拔,调了两天终于跟踪完了BIOS和boot loader的过程进入内核启动阶段了,之后再整理一下可能会写一下SeaBIOS和boot loader的源码分析和调试(立一个flag)
之所以调试SeaBIOS是因为想从头开始调试内核但找不到入口,然后支线任务做着做着就偏离主线了(ˉ▽ˉ;)…,现在终于回归主线了
ps:主要关注内存相关的部分,页表和段之类的
内核版本4.15.8,x86_64
Setup
kernel加载到了物理地址0x10000,源码可以看出来
1 | |
或者gdb调试也可以看出来,此时cs寄存器的值为0x1020
进入内核的第一条指令是一个跳转至start_of_setup,地址为0x10268
1 | |
start_of_setup的主要作用有
- 保证所有段寄存器值相等
- 建立栈
- 建立bss段
- 跳转至main函数
1 | |
这段代码结束后
- sp:0xfff0
- __bss_start:0x13c00
- _end:0x14f10
main
main函数的主要功能为
- 初始化console、heap
- 检测内存、CPU验证、键盘初始化
- 进入保护模式
1 | |
copy_boot_params
copy_boot_params主要完成:
- 将hdr的内容复制给boot_params:0x13ef0
- 如果使用的是旧的command line则将其复制到0x9000
- 更新boot_params.cmd_line_ptr
1 | |
ps:
hdr的地址为0x1f1,hdr和boot_params的地址都可以通过调试得到(定位内存复制的命令)
hdr结构定义在header.S文件中
1
2
3
4
5
6
7
8
9.globl hdr
hdr:
setup_sects: .byte 0 /* Filled in by build.c */
root_flags: .word ROOT_RDONLY
syssize: .long 0 /* Filled in by build.c */
ram_size: .word 0 /* Obsolete */
vid_mode: .word SVGA_MODE
root_dev: .word 0 /* Filled in by build.c */
boot_flag: .word 0xAA55md这破玩意我纠结了一下午
init_heap
调试看boot_params结构体可以看出heap_end:0xfe00
1 | |
go_to_protected_mode
1 | |
gdt基址都为0
1 | |
进入保护模式:
1 | |
之后函数流程跳转到0x100000:startup_32
startup_32
1 | |
一些变量和宏定义
1 | |
建立栈
建立栈的代码如下:
1 | |
boot_stack_end的定义如下:
1 | |
- 将boot_stack_end的链接地址放入eax
- 加ebp得到boot_stack_end的实际地址
- 把栈移过去
更新gdt
gdt的定义如下:
- gdt大小:.word
- gdt物理地址:.long
1 | |
更新gdt的过程如下:
1 | |
- gdt+2的内容+=程序基址,更新gdt物理地址
- 更新lgdt寄存器(lgdt寄存器内容为gdt大小:gdt物理地址)
初期页表初始化
建立映射4G内存的页表
Linux内核使用4级页表,建立6个页表
- 1个PML4或称为4级页映射表,包含1个项
- 1个PDP或称为页目录指针表,包含4个项
- 4个页目录表,一共包含2048个项,一页2MB
先清理一块内存,每个表都是4096字节,所以需要24KB内存
1 | |
pgtable的定义如下:
1 | |
分配空间后先建立PML4:
1 | |
- &PML4+0x1000是PDP的地址
- 7是页表的标记,表示PRESENT+RW+USER但UNACCESSED(开启分页后访问一次就会变成ACCESSED,0x27)
页表标记定义如下(简化过的):
1 | |
初始化4个PDP:
1 | |
初始化4个PDP的共2048个项:
1 | |
- 0x183标记表示_PAGE_PRESENT+_PAGE_RW+_PAGE_PSE+_PAGE_GLOBAL
- PSE表示2M或4M的页
startup_64
1 | |
relocated
1 | |
startup_64(长模式)
1 | |
开始使用虚拟地址了,结束*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。
__startup_64
1 | |
64位页表
x86_64使用4级页表:
- 全局页目录
- 上层页目录
- 中间页目录
- 页表项
地址的第1-16位(高到低)用于区分用户态和内核态
- 全0为用户空间
- 全1为内核空间
分页的具体位数如下:
- 1-16:不使用
- 17-25:全局页目录(pgd)
- 26-34:上层页目录(pud)
- 35-43:中间页目录(pmd)
- 44-52:页表项
- 53-64:页内偏移
以下源码只有三级页表,页大小为0x200000(PAE)
__startup_64中使用的页表相关定义如下:
1 | |
1 | |