Linux内核启动过程解析(二):深入保护模式与初始化流程

Linux内核启动过程解析(二):深入保护模式与初始化流程

linux-insides 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内存
  • 完善的内存保护机制
  • 支持虚拟内存和分页
  • 多任务支持

保护模式内存管理

保护模式下的内存管理分为两个独立部分:

  1. 分段机制:通过段描述符定义内存段的属性
  2. 分页机制:将线性地址转换为物理地址(本文暂不讨论)

全局描述符表(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:段类型属性

段类型详解

段描述符可以分为两大类:

  1. 数据段

    • 可读/写属性控制
    • 扩展方向控制(向上/向下扩展)
    • 访问标志
  2. 代码段

    • 可执行/只读属性
    • 一致性控制(能否从低特权级调用)
    • 访问标志

实模式到保护模式的切换流程

切换到保护模式需要以下步骤:

  1. 禁用中断:使用CLI指令
  2. 加载GDT:使用LGDT指令
  3. 设置CR0.PE标志:开启保护模式
  4. 远跳转:跳转到保护模式代码段

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 linux-insides 项目地址: https://gitcode.com/gh_mirrors/li/linux-insides

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

裘韶同

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值