Tsinghua_OS_uCore_Lab1

本文详细介绍了如何从实模式切换到保护模式的过程,包括关闭中断、设置段寄存器、激活A20地址线、设置GDT描述符等关键步骤,并解释了每个操作的目的及其在启动过程中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#包含头文件"asm.h"
#include <asm.h>

# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.
# BIOS会从第一个扇区中加载此代码到物理内存地址 0x7c00中
# 并且在实模式下以 %cs=0, %ip=7c00开始执行

.set PROT_MODE_CSEG,        0x8                     # kernel code segment selector
.set PROT_MODE_DSEG,        0x10                    # kernel data segment selector
.set CR0_PE_ON,             0x1                     # protected mode enable flag

# start address should be 0:7c00, in real mode, the beginning address of the running bootloader
.globl start
start:
.code16                                             # Assemble for 16-bit mode
    # cli命令是bootLoader的第一条指令,该指令把所有的中断都关闭
    cli                                             # Disable interrupts
    # CLD使DF复位,即DF=0
    # 通过执行cld指令可以控制方向标志DF,决定内存地址是增大(DF=0,向高地址增加)还是减小(DF=1,向地地址减小)
    cld                                             # String operations increment

    # Set up the important data segment registers (DS, ES, SS).
    # 这几条命令把三个段寄存器 ds, es 和 ss都清零
    xorw %ax, %ax                                   # Segment number zero
    movw %ax, %ds                                   # -> Data Segment
    movw %ax, %es                                   # -> Extra Segment
    movw %ax, %ss                                   # -> Stack Segment

    # Enable A20:
    #  For backwards compatibility with the earliest PCs, physical
    #  address line 20 is tied low, so that addresses higher than
    # 激活A20地址位。由于需要兼容早起pc,物理地址的第20位绑定为0
    # 因此高于1MB的地址又回到0x00000
    # 激活了A20,就可以访问所有4G内存,就可以使用保护模式

    # A20由键盘控制器芯片8042管理,所以要给8042发送命令激活A20
    # 8042有两个IO端口:0x60和0x64
    # 激活流程为:
    #     1、发送0xd1命令道0x64端口
    #     2、发送0xdf道0x60
seta20.1:
    inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
    testb $0x2, %al
    jnz seta20.1

    movb $0xd1, %al                                 # 0xd1 -> port 0x64
    outb %al, $0x64                                 # 0xd1 means: write data to 8042's P2 port

seta20.2:
    inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
    testb $0x2, %al
    jnz seta20.2

    movb $0xdf, %al                                 # 0xdf -> port 0x60
    outb %al, $0x60                                 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1

    # 至此,A20激活完成

    # Switch from real to protected mode, using a bootstrap GDT
    # and segment translation that makes virtual addresses
    # identical to physical addresses, so that the
    # effective memory map does not change during the switch.
    # 进入保护模式需要指定一个临时的GDT,来翻译逻辑地址
    # 此处使用的GDT通过gdtdesc段定义
    # 它翻译得到的物理地址和虚拟地址相同,因此转换过程中的内存映射不发生改变

    # 下面一条指令把gdtdesc标识符送入GDTR寄存器中
    # 该信息包括GDT表的内存起始地址以及GDT表的长度
    # GDTR寄存器由48个比特位构成,低16位表示长度,高32位表示内存中的起始地址
    # gdtdesc是个标识符,标识着一个内存地址,从这个地址开始后的6个字节存放着GDT表的长度和起始地址
    lgdt gdtdesc

    #打开保护模式的标志位:将cr0寄存器的第0位置1
    movl %cr0, %eax
    orl $CR0_PE_ON, %eax
    movl %eax, %cr0

    # Jump to next instruction, but in 32-bit code segment.
    # Switches processor into 32-bit mode.
    # 上面的代码已经打开了保护模式了,所以这里要使用逻辑地址,而不是之前实模式的地址了。
    # 这里用到了PROT_MODE_CSEG, 他的值是0x8。根据段选择子的格式定义,0x8就翻译成:
    #          INDEX      TI     CPL
    #      0000 0000 0000 1     00      0
    # INDEX代表GDT中的索引,TI代表使用GDTR中的GDT, CPL代表处于特权级。
    # 需要通过长跳转来设置cs寄存器的值
    ljmp $PROT_MODE_CSEG, $protcseg


.code32                                             # Assemble for 32-bit mode
protcseg:
    # Set up the protected-mode data segment registers
    # 重新初始化各个段寄存器
    movw $PROT_MODE_DSEG, %ax                       # Our data segment selector
    movw %ax, %ds                                   # -> DS: Data Segment
    movw %ax, %es                                   # -> ES: Extra Segment
    movw %ax, %fs                                   # -> FS
    movw %ax, %gs                                   # -> GS
    movw %ax, %ss                                   # -> SS: Stack Segment

    # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
    # 栈顶设定在start(即0x7c00处),call函数将返回地址入栈,将控制权交给bootmain
    movl $0x0, %ebp
    movl $start, %esp
    call bootmain

    # If bootmain returns (it shouldn't), loop.
spin:
    jmp spin

# Bootstrap GDT

# #define SEG_NULLASM
#       .word 0, 0;
#       .byte 0,0,0,0
# #define SEG_ASM(type,base,lim)                        \
#       .word (((lim)>>12)& 0xffff), ((base) & 0xffff); \
#       .byte (((base)>>16) & 0xff), (0x90 | (type)),   \
#           (0xC0 | (((lim)>>28) & oxf)), (((base)>>24) & 0xff)
.p2align 2                                          # force 4 byte alignment
gdt:
    SEG_NULLASM                                     # null seg
    SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)           # code seg for bootloader and kernel
    SEG_ASM(STA_W, 0x0, 0xffffffff)                 # data seg for bootloader and kernel

# 上面定义了3项GDT表的描述符,每项为8个字节,因此GDT表的总大小为24个字节(0x18)
# 这里存的是0x17,这是官方的约定
# 紧接着就是GDT的起始地址
gdtdesc:    # 48位,低16位是limit,高32位是GDT表的基地址
    # 此处放一个字(2 bytes)
    .word 0x17                                      # sizeof(gdt) - 1
    # 此处放4 bytes,为gdt的值(即GDT表的基地址)
    .long gdt                                       # address gdt

下面分析代码中所定义的GDT表的表项。GDT表的每一项称作段描述符(Segment Descriptor),每一项的大小为8个字节,段描述符的格式如下图所示:
这里写图片描述
这里写图片描述

而代码中宏SEG_ASM的三个参数type、base和limit对应上图中的三个颜色块。代码定义了3个段描述符,第一个是空描述符,第二个是代码段的段描述符,第三个是数据段的段描述符。
根据参数的值,可以知道代码段的段描述符的值为 0x00 CF 9A 00 00 FF FF,对应于段描述符的格式如下图所示:
这里写图片描述
这里写图片描述
可以看出,SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) 定义的GDT表中代码段的段描述符表示该段的基址为0x0,大小为0xFFFFF。
同理可分析SEG_ASM(STA_W, 0x0, 0xffffffff) 的结果。
参考资料:
https://www.cnblogs.com/fatsheep9146/p/5115086.html
https://www.cnblogs.com/zslhg903/p/5781882.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值