实模式与保护模式切换(一)

本文详细介绍了从实模式切换到保护模式的过程,包括GDT(全局描述符表)的定义、段选择子的配置、数据段和堆栈段的初始化,以及关键的跳转指令和寄存器设置。通过具体的汇编指令解析,展示了如何在操作系统启动过程中实现模式切换。

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

基于网上的一些博客的基础上,我对操作系统中实模式与保护模式的切换,做了一点整理,仅供参考,这篇写的是从实模式进入到保护模式。
在从实模式进入到保护模式之前,要先对GDT(全局描述符表),GDT段选择子,数据段,全局堆栈段进行定义。
对全局描述符表定义

org    0100h              ;加载到偏移地址0100处
jmp    LABEL_BEGIN        ;跳入到16位代码段(实模式)进行各个数据段,代码段,堆栈段的初始化,最后跳入到保护模式
; 对GDT定义
;SECTION.gdt
;                            段基址    ,        段界限  ,   属性
    LABEL_GDT:         Descriptor    0,              0, 0         ; 空描述符
    LABEL_DESC_NORMAL: Descriptor    0,         0ffffh, DA_DRW    ; Normal 描述符
    LABEL_DESC_CODE32: Descriptor    0, SegCode32Len-1, DA_C+DA_32; 非一致代码段, 32
    LABEL_DESC_CODE16: Descriptor    0,         0ffffh, DA_C      ; 非一致代码段, 16
    LABEL_DESC_DATA:   Descriptor    0,      DataLen-1, DA_DRW    ; Data
    LABEL_DESC_STACK:  Descriptor    0,  TopOfStack, DA_DRWA+DA_32; Stack, 32 位
    LABEL_DESC_TEST:   Descriptor 0500000h,     0ffffh, DA_DRW
    LABEL_DESC_VIDEO:  Descriptor  0B8000h,     0ffffh, DA_DRW    ; 显存首地址
; GDT 结束

GdtLen   equ    $ - LABEL_GDT  ; GDT长度
GdtPtr   dw  GdtLen - 1  ; GDT界限
  dd    0    ; GDT基地址

对段选择子定义

SelectorNormal   equ    LABEL_DESC_NORMAL   - LABEL_GDT
SelectorCode32   equ    LABEL_DESC_CODE32   - LABEL_GDT
SelectorCode16   equ    LABEL_DESC_CODE16   - LABEL_GDT
SelectorData     equ    LABEL_DESC_DATA     - LABEL_GDT
SelectorStack    equ    LABEL_DESC_STACK    - LABEL_GDT
SelectorTest     equ    LABEL_DESC_TEST     - LABEL_GDT
SelectorVideo    equ    LABEL_DESC_VIDEO    - LABEL_GDT

对数据段定义

;SECTION.data1
ALIGN   32              ;32位字节对齐
[BITS   32]             ;32位字节
LABEL_DATA:
SPValueInRealMode   dw  0
; 字符串
PMMessage:   db "In Protect Mode now. ", 0   ; 在保护模式中显示 ;(后面的零作为字符串的结束标志,Line189、232的test会用到)
    OffsetPMMessage  equ    PMMessage - $$  ; ($$ == LABEL_DATA 的基地址,相对于 LABEL_DATA 的偏移地址)
    StrTest:     db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0   ;(后面的零作为字符串的结束标志)
OffsetStrTest    equ    StrTest - $$
DataLen  equ    $ - LABEL_DATA

对全局堆栈段定义

;SECTION.gs
ALIGN   32
[BITS   32]
LABEL_STACK:
times 512 db 0
TopOfStack  equ $ - LABEL_STACK - 1      ;堆栈顶

以上是为了从实模式切换到保护模式的准备,接下来才是重点。
为从实模式跳转到保护模式所做的准备工作

;SECTION.s16
[BITS   16]
LABEL_BEGIN:
 mov    ax, cs
 mov    ds, ax
 mov    es, ax
 mov    ss, ax
 mov    sp, 0100h  

让cs=ds=es=ss,让各个段处于相同的64KB空间,便于代码和数据处理;sp栈顶指针指向0100h段基地址。

mov    [LABEL_GO_BACK_TO_REAL+3], ax
mov    [SPValueInRealMode], sp   ; 保存sp,然后返回实模式后,再将sp取出来

mov [label_backto_real + 3], ax, 这是在代码已经加载到了内存之后再执行的,为什么加上3,一位jmp指令占用3个字节, 而jmp后面跟着跳转地址;他的作用是在段基地址存储单元中重新设置新值去覆盖原先的段基地址。
初始化 16 位代码段描述符

mov    ax, cs     ;代码带赋值给ax
movzx  eax, ax    ;eax是32位,而这是16位代码段,所以用零扩展传送,就是屏蔽eax的高16位再赋值
shl    eax, 4     ;eax逻辑左移4位
add    eax, LABEL_SEG_CODE16     ;LABEL_SEG_CODE16即偏移地址,即物理地址=段地址*16+offset
mov    word [LABEL_DESC_CODE16 + 2], ax
shr    eax, 16
mov    byte [LABEL_DESC_CODE16 + 4], al
mov    byte [LABEL_DESC_CODE16 + 7], ah

以上代码就是在根据代码段描述符,并且初始化它。详情可见初始化代码段描述符
初始化32位代码段描述符

 xor    eax, eax        ;异或清零,等同于mov eax,0 但比它的效率高
 mov    ax, cs
 shl    eax, 4
 add    eax, LABEL_SEG_CODE32
 mov    word [LABEL_DESC_CODE32 + 2], ax
 shr    eax, 16
 mov    byte [LABEL_DESC_CODE32 + 4], al
 mov    byte [LABEL_DESC_CODE32 + 7], ah

以上代码类似于上段。
初始化数据段描述符

 xor    eax, eax
 mov    ax, ds
 shl    eax, 4
 add    eax, LABEL_DATA ; 所以 OffsetStrTest 既是字符串相对于 LABEL_DATA 的偏移地址,也是其在数据段的偏移 
 mov    word [LABEL_DESC_DATA + 2], ax
 shr    eax, 16
 mov    byte [LABEL_DESC_DATA + 4], al
 mov    byte [LABEL_DESC_DATA + 7], ah

类似于上段的初始化。
初始化堆栈段描述符

 xor    eax, eax
 mov    ax, ds
 shl    eax, 4
 add    eax, LABEL_STACK
 mov    word [LABEL_DESC_STACK + 2], ax
 shr    eax, 16
 mov    byte [LABEL_DESC_STACK + 4], al
 mov    byte [LABEL_DESC_STACK + 7], ah

类似于以上。
为加载 GDTR 作准备,填充GDT基地址的数据结构

 xor    eax, eax
 mov    ax, ds
 shl    eax, 4
 add    eax, LABEL_GDT 
 mov    dword [GdtPtr + 2], eax 

将GDT的地址送到eax中,再将eax送给GDTR(全局描述符寄存器)的前32位。
加载 GDTR

lgdt   [GdtPtr]

关中断

cli    ;禁止中断发生

打开地址线A20

 in al, 92h       ;从92h号端口读入一个字节
 or al, 00000010b ;将从端口0x92读入数据的二进制码的第二位置1,从而实现开启A20地址。因为A20信号的第二位就是用于控制开启/禁止A20地址的
 out    92h, al   ;向92h号端口写入一个字节

准备切换到保护模式, PE位置1

 mov    eax, cr0
 or eax, 1
 mov    cr0, eax

打开cr0控制寄存器的PE位,也就是将PE位置1,这是保护模式的开关。
进入保护模式

jmp   dword  SelectorCode32:0  ; 把 SelectorCode32 装入cs, 并跳转Code32Selector:0  处(双字节跳转到32位代码段第一条指令开始执行)

保护模式下,是通过段选择子:偏移量的方式寻址的;不同于在实模式下:段寄存器×16+偏移量得到32位物理地址。
进入到保护模式后,可以对屏幕进行输出:

[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS    32]

LABEL_SEG_CODE32:
    mov    ax, SelectorVideo
    mov    gs, ax            ; 视频段选择子
    mov    edi, (80 * 10 + 0) * 2    ; 屏幕第 10 行, 第 0 列。
    mov    ah, 0Ch           ; 0000: 黑底    1100: 红字
    mov    al, 'P'           ;输出P
    mov    [gs:edi], ax
    ; 到此停止
    jmp    $

SegCode32Len    equ    $ - LABEL_SEG_CODE32
; END of [SECTION .s32]

综上所述,进入保护模式的步骤:1.准备GDT 2.用lgdt加载gdtr 3.打开A20地址线 4.跳转

以上部分参考:实模式+保护模式
关于以上出现的一些名词如GDT,GDTR,段选择子可见保护模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Chrisyyl

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

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

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

打赏作者

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

抵扣说明:

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

余额充值