[自己动手写操作系统]的学习实践【第三章】

本文介绍了一种从实模式转换到保护模式的方法,并详细解释了相关代码。通过修改描述符和寄存器,使处理器进入保护模式,实现对更大内存空间的访问。

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

因为第二章主要是讲系统的一些配置,所以我们在这里就不准备怎么费多少笔墨描述了,

第三章 保护模式

第一节  认识保护模式

本章我们要学习计算机如何由实模式转入保护模式。实模式向保护模式的转变需要更改一系列的参数。实质是通过一个数据结构来使得16位寄存器访问了4GB的空间。我们下面的程序就显示了这个例子。我们对此的分析将以旁注的方式表现。

; ==========================================
; pmtest1.asm
;
编译方法:nasm pmtest1.asm -o pmtest1.com
; ==========================================

%include "pm.inc" ; 常量, , 以及一些说明

org 0100h   ;编译成com文件
 jmp LABEL_BEGIN

[SECTION .gdt]
; GDT
;                                        
段基址,      段界限     , 属性
LABEL_GDT:  Descriptor        0,                0, 0       ;
空描述符
LABEL_DESC_CODE32: Descriptor        0, SegCode32Len - 1, DA_C + DA_32 ;
非一致代码段, 32
LABEL_DESC_VIDEO: Descriptor  0B8000h,           0ffffh, DA_DRW  ;
显存首地址[J1] 
; GDT
结束

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

; GDT 选择子
SelectorCode32  equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo  equ LABEL_DESC_VIDEO - LABEL_GDT
[J4] ; END of [SECTION .gdt]

 ; 初始化 16 位代码段描述符,在这里仅仅是对段寄存器做了设置

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

 ; 初始化 32 位代码段描述符,在这里我们对段的基址做了初始化,不再是原先定义的0
 xor eax, eax
 mov ax, cs
 shl eax, 4
 add eax, LABEL_SEG_CODE32
[J5] 
 mov word [LABEL_DESC_CODE32 + 2], ax
[J6] 
 shr eax, 16
 mov byte [LABEL_DESC_CODE32 + 4], al
[J7] 
 mov byte [LABEL_DESC_CODE32 + 7], ah
[J8] 

 ; 为加载 GDTR 作准备
 xor eax, eax
 mov ax, ds
 shl eax, 4
 add eax, LABEL_GDT  ; eax <- gdt
基地址[J9] 
 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt
基地址

 ; 加载 GDTR
 lgdt [GdtPtr]
[J10] 

 ; 关中断
 cli

 ; 打开地址线A20
 in al, 92h
 or al, 00000010b
 out 92h, al

 ; 准备切换到保护模式
 mov eax, cr0
 or eax, 1
 mov cr0, eax
[J11] 

 ; 真正进入保护模式
 jmp dword SelectorCode32:0 ;
执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 
; END of [SECTION .s16]


[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'
 mov [gs:edi], ax
[J12] 

 ; 到此停止
 jmp $

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

pm.inc的代码如下:

 

 

; 图示一
;
描述符图示

;
;  ------
┏━━┳━━┓高地址
;        

;        
┣━━┫   
;                 
字节 7        
;                 

;        
┣━━┫
;        
   
;  ------
┣━━╋━━┫
;        

;        
┣━━╉──┨
;        

;        
┣━━╉──┨
;        

;        
┣━━╉──┨
;        
AVL
字节 6 ┣━━╉──┨
;        
   
;        
┣━━┫
;        

;        
┣━━┫
;        
   
;        
┣━━┫
;        
   
;  ------
┣━━╋━━┫
;        

;        
┣━━╉──┨
;        
   
;        
┣━━┫ DPL
;        
   
;        
┣━━╉──┨
;        

字节 5 ┣━━╉──┨
;        
   
;        
┣━━┫
;        

;        
┣━━┫
;        

;        
┣━━┫   
;        
   
;  ------
┣━━╋━━┫
;
         23    
;        
┣━━┫   
;        
22    
;        
┣━━┫
;
;  
字节     
; 2, 3, 4
;        
┣━━┫
;        

;        
┣━━┫   
;        
   
;  ------
┣━━╋━━┫
;        
15    
;        
┣━━┫   
;
         14    
;        
┣━━┫
;
;
字节 0,1   
;
;        
┣━━┫
;        

;        
┣━━┫   
;        
   
;  ------
┗━━┻━━┛低地址
;


;
图示二

; 高地址………………………………………………………………………低地址

; |   7   |   6   |   5   |   4   |   3   |   2   |   1   |   0    |
; |7654321076543210765432107654321076543210765432107654321076543210| <-
8 字节
; |--------========--------========--------========--------========|
;
┏━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓
;
31..24   (见下图)        段基址(23..0)    段界限(15..0)
;
                                                       
;
基址2┃③│②│    ①┃基址1b   基址 1a          段界限1  
;
┣━━━╋━━━┳━━━╋━━━━━━━━━━━╋━━━━━━━┫
;
   %6   %5    %4    %3       %2              %1    
;
┗━━━┻━━━┻━━━┻━━━┻━━━━━━━┻━━━━━━━┛
                        /_________
;        
                          /__________________
;        
                                             /________________________________________________
;        
                                                                                              /
┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓
;        

;  
┣━╋━╋━╋━╋━┻━━┻━━┻━━╋━━╋━━┻━━╋━━╋━━┻━━┻━━┻━━┫
;        
AVL   段界限 2 (19..16)    P    DPL           TYPE          
;        
┣━━┻━━┻━━┻━━╋━━━━━━━━━━━╋━━┻━━━━━┻━━┻━━━━━━━━━━━┫
;        
      : 属性 2          : 段界限 2                         : 属性1                 
;        
┗━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━┛
;      
高地址                                                                                          低地址
;
;

; 说明:
;
; (1) P:   
存在(Present)位。
;  P=1
表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中;
;  P=0
表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异常。
;
; (2) DPL: 
表示描述符特权级(Descriptor Privilege level),共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。
;
; (3) S:  
说明描述符的类型。
;  
对于存储段描述符而言,S=1,以区别与系统段描述符和门描述符(S=0)
;
; (4) TYPE:
说明存储段描述符所描述的存储段的具体属性。
;
;  
数据段类型 类型值  说明
;   ----------------------------------
;   0  
只读
;   1  
只读、已访问
;   2  
/
;   3  
/写、已访问
;   4  
只读、向下扩展
;   5  
只读、向下扩展、已访问
;   6  
/写、向下扩展
;   7  
/写、向下扩展、已访问
;
;  
;   
类型值  说明
代码段类型 ----------------------------------
;   8  
只执行
;   9  
只执行、已访问
;   A  
执行/
;   B  
执行/读、已访问
;   C  
只执行、一致码段
;   D  
只执行、一致码段、已访问
;   E  
执行/读、一致码段
;   F  
执行/读、一致码段、已访问
;
;  
系统段类型 类型编码 说明
;   ----------------------------------
;   0  <
未定义>
;   1  
可用286TSS
;   2  LDT
;   3  
忙的286TSS
;   4  286
调用门
;   5  
任务门
;   6  286
中断门
;   7  286
陷阱门
;   8  
未定义
;   9  
可用386TSS
;   A  <
未定义>
;   B  
忙的386TSS
;   C  386
调用门
;   D  <
未定义>
;   E  386
中断门
;   F  386
陷阱门
;
; (5) G:   
段界限粒度(Granularity)位。
;  G=0
表示界限粒度为字节;
;  G=1
表示界限粒度为4K 字节。
;          
注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。
;
; (6) D:    D
位是一个很特殊的位,在描述可执行段、向下扩展数据段或由SS寄存器寻址的段(通常是堆栈段)的三种描述符中的意义各不相同。
;          
在描述可执行段的描述符中,D位决定了指令使用的地址及操作数所默认的大小。
;  
D=1表示默认情况下指令使用32位地址及32位或8位操作数,这样的代码段也称为32位代码段;
;  
D=0 表示默认情况下,使用16位地址及16位或8位操作数,这样的代码段也称为16位代码段,它与80286兼容。可以使用地址大小前缀和操作数大小前缀分别改变默认的地址或操作数的大小。
;          
在向下扩展数据段的描述符中,D位决定段的上部边界。
;  
D=1表示段的上部界限为 4G
;  
D=0表示段的上部界限为64K,这是为了与80286兼容。
;          
在描述由SS寄存器寻址的段描述符中,D位决定隐式的堆栈访问指令(PUSHPOP指令)使用何种堆栈指针寄存器。
;  
D=1表示使用32位堆栈指针寄存器ESP
;  
D=0表示使用16位堆栈指针寄存器SP,这与80286兼容。
;
; (7) AVL: 
软件可利用位。80386对该位的使用未左规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。
;


;----------------------------------------------------------------------------
;
描述符类型值说明
;
其中:
;       DA_  : Descriptor Attribute
;       D    :
数据段
;       C    :
代码段
;       S    :
系统段
;       R    :
只读
;       RW   :
读写
;       A    :
已访问
;      
其它 : 可按照字面意思理解
;----------------------------------------------------------------------------
DA_32  EQU 4000h ; 32
位段

DA_DPL0  EQU   00h ; DPL = 0
DA_DPL1  EQU   20h ; DPL = 1
DA_DPL2  EQU   40h ; DPL = 2
DA_DPL3  EQU   60h ; DPL = 3
;----------------------------------------------------------------------------
;
存储段描述符类型值说明
;----------------------------------------------------------------------------
DA_DR  EQU 90h ;
存在的只读数据段类型值
DA_DRW  EQU 92h ;
存在的可读写数据段属性值
DA_DRWA  EQU 93h ;
存在的已访问可读写数据段类型值
DA_C  EQU 98h ;
存在的只执行代码段属性值
DA_CR  EQU 9Ah ;
存在的可执行可读代码段属性值
DA_CCO  EQU 9Ch ;
存在的只执行一致代码段属性值
DA_CCOR  EQU 9Eh ;
存在的可执行可读一致代码段属性值
;----------------------------------------------------------------------------
;
系统段描述符类型值说明
;----------------------------------------------------------------------------
DA_LDT  EQU   82h ;
局部描述符表段类型值
DA_TaskGate EQU   85h ;
任务门类型值
DA_386TSS EQU   89h ;
可用 386 任务状态段类型值
DA_386CGate EQU   8Ch ; 386
调用门类型值
DA_386IGate EQU   8Eh ; 386
中断门类型值
DA_386TGate EQU   8Fh ; 386
陷阱门类型值
;----------------------------------------------------------------------------


;
选择子图示:
;        
┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓
;        
15 14 13 12 11 10
;        
┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫
;        
                                 描述符索引                                 TI    RPL   
;        
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛
;
; RPL(Requested Privilege Level):
请求特权级,用于特权检查。
;
; TI(Table Indicator):
引用描述符表指示位
; TI=0
指示从全局描述符表GDT中读取描述符;
; TI=1
指示从局部描述符表LDT中读取描述符。
;

;----------------------------------------------------------------------------
;
选择子类型值说明
;
其中:
;       SA_  : Selector Attribute

SA_RPL0  EQU 0 ;
SA_RPL1  EQU 1 ;
RPL
SA_RPL2  EQU 2 ;

SA_RPL3  EQU 3 ;

SA_TIG  EQU 0 ; TI
SA_TIL  EQU 4 ;

;----------------------------------------------------------------------------

 

; ------------------------------------------------------------------------------------------------------
;
;
描述符
; usage: Descriptor Base, Limit, Attr
;        Base:  dd
;        Limit: dd (low 20 bits available)
;        Attr:  dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
 dw %2 & 0FFFFh    ;
段界限 1    (2 字节)
 dw %1 & 0FFFFh    ;
段基址 1    (2 字节)
 db (%1 >> 16) & 0FFh   ;
段基址 2    (1 字节)[J13] 
 dw ((%2 >> 8) & 0F 00h)
[J14] | (%3 & 0F 0FFh)[J15]  ; 属性 1 + 段界限 2 + 属性 2  (2 字节)
 db (%1 >> 24) & 0FFh   ;
段基址 3    (1 字节)
%endmacro ;
8 字节
;
;

; usage: Gate Selector, Offset, DCount, Attr
;        Selector:  dw
;        Offset:    dd
;        DCount:    db
;        Attr:      db
%macro Gate 4
 dw (%2 & 0FFFFh)    ;
偏移 1    (2 字节)
 dw %1     ;
选择子    (2 字节)
 dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ;
属性     (2 字节)
 dw ((%2 >> 16) & 0FFFFh)   ;
偏移 2    (2 字节)
%endmacro ;
8 字节
;


 [J1]定义的GDT数组,一共三个Descriptor,每个由段基址,段界限,属性构成。它们被赋予了一些初始化值。在进入实模式前,这里的数值将改变。

 [J2]32位的基址,第一个成员GDT界限已经赋值,因此在进入32位前,仅仅需要初始化GDT基址即可。

 [J3]该结构占据6个字节

 [J4]如何理解选择子,

 [J5]Eax存放了cs16位代码段)左移4位加上偏移量(32位代码相对于代码段CS的偏移量)的数据。这个就是最后32位代码段的段基址。

 [J6]段基址1

 [J7]段基址1

 [J8]段基址2

 [J9]计算了GDTR的中成员gdt基址,由此gdtr已经初始化完毕。

 [J10]为什么不对GDT直接加载,而必须使用这样一个结构来加载呢?

 [J11]Cr0最后1PE0:实模式,1:保护模式

 [J12]向显存edi偏移处写数据

 [J13]段基址32

 [J14]填入界限21619

 [J15]其它属性数据也填入

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值