Linux内核启动过程解析(二):深入保护模式与初始化流程
linux-insides 项目地址: https://gitcode.com/gh_mirrors/li/linux-insides
前言
在上一篇文章中,我们分析了Linux内核启动的第一阶段,从BIOS加载引导扇区到执行16位实模式代码的过程。本文将深入探讨内核如何从实模式切换到保护模式,并详细解析这一过程中的关键步骤。
保护模式基础
为什么需要保护模式?
x86处理器最初只有实模式(Real Mode),这种模式下CPU只能访问1MB内存(实际可用通常只有640KB),且缺乏内存保护机制。随着计算机发展,这种限制变得难以接受,于是Intel在80286处理器中引入了保护模式(Protected Mode)。
保护模式的主要优势包括:
- 32位地址总线,可访问4GB内存
- 完善的内存保护机制
- 支持虚拟内存和分页
- 多任务支持
保护模式内存管理
保护模式下的内存管理分为两个独立部分:
- 分段机制:通过段描述符定义内存段的属性
- 分页机制:将线性地址转换为物理地址(本文暂不讨论)
全局描述符表(GDT)
段描述符结构
在保护模式下,内存分段不再像实模式那样使用固定的64KB段。取而代之的是段描述符,每个描述符64位,详细定义了段的属性:
63 56 51 48 45 39 32
------------------------------------------------------------
| | |B| |A| | | | |0|E|W|A| |
| BASE 31:24 |G|/|L|V| LIMIT |P|DPL|S| TYPE | BASE 23:16 |
| | |D| |L| 19:16 | | | |1|C|R|A| |
------------------------------------------------------------
31 16 15 0
------------------------------------------------------------
| | |
| BASE 15:0 | LIMIT 15:0 |
| | |
------------------------------------------------------------
关键字段解析:
- Base:32位,定义段的起始物理地址
- Limit:20位,定义段长度(实际长度=Limit+1)
- G:粒度位,0表示字节粒度,1表示页粒度(4KB)
- D/B:默认操作数大小,0表示16位,1表示32位
- L:64位代码段标志
- P:段存在标志
- DPL:描述符特权级(0-3)
- S:描述符类型(0=系统,1=代码/数据)
- Type:段类型属性
段类型详解
段描述符可以分为两大类:
-
数据段:
- 可读/写属性控制
- 扩展方向控制(向上/向下扩展)
- 访问标志
-
代码段:
- 可执行/只读属性
- 一致性控制(能否从低特权级调用)
- 访问标志
实模式到保护模式的切换流程
切换到保护模式需要以下步骤:
- 禁用中断:使用CLI指令
- 加载GDT:使用LGDT指令
- 设置CR0.PE标志:开启保护模式
- 远跳转:跳转到保护模式代码段
Linux内核的具体实现
启动参数复制
在内核的main.c中,首先调用copy_boot_params()
函数:
void copy_boot_params(void)
{
memcpy(&boot_params.hdr, &hdr, sizeof(hdr));
// 更新命令行指针(如果使用旧协议)
}
这个函数将内核设置头从汇编代码定义的hdr
复制到boot_params
结构体中。memcpy
的实现使用了高效的rep movsl指令进行32位复制,剩余字节则使用movsb指令。
控制台初始化
控制台初始化通过console_init()
函数完成:
void console_init(void)
{
// 解析earlyprintk参数
// 初始化串口
if (cmdline_find_option_bool("debug"))
puts("early console in setup code\n");
}
puts
函数逐字符输出,遇到换行符时会先输出回车符。实际的字符输出通过BIOS中断0x10实现:
static void bios_putchar(int ch)
{
struct biosregs ireg;
initregs(&ireg);
ireg.bx = 0x0007; // 页号0,属性07h
ireg.cx = 0x0001; // 重复次数
ireg.ah = 0x0e; // 功能号:电传打字输出
ireg.al = ch; // 要打印的字符
intcall(0x10, &ireg, NULL);
}
总结
本文详细分析了Linux内核从实模式切换到保护模式的过程,重点讲解了保护模式的基本概念、GDT的结构以及内核初始化的关键步骤。在下一篇文章中,我们将继续探讨内核如何完成保护模式的切换,并初始化更多关键子系统。
通过理解这些底层机制,我们能够更深入地把握操作系统启动过程的核心原理,为后续学习内存管理、进程调度等高级主题打下坚实基础。
linux-insides 项目地址: https://gitcode.com/gh_mirrors/li/linux-insides
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考