实模式和保护模式

本文对比了在Dos和Windows环境下进行汇编语言编程的主要区别。Dos编程允许直接访问系统资源,包括内存管理和键盘中断;而Windows则采用保护模式,限制了应用程序的访问权限,并采用了基于消息的程序结构。
   Dos下编汇编程序,我们可以管理系统的所有资源,我们可以改动系统中所有的内存,如自己改动内存控制块来分配内存,自己修改中断向量表来截获中断等,对其他操作也是如此,如我们对键盘端口直接操作就可以把键盘屏蔽掉,可以这样来描述Dos系统:系统只有一个特权级别,在编程上讲,任何程序和操作系统都是同级的,所以在Dos下,一个编得不好的程序会影响其他所有的程序,如一个程序把键盘口中断关掉了,所有程序就都不能从键盘获得键入的数据,直到任何一个程序重新打开键盘为止,一个程序陷入死循环,也没有其他程序可以把它终止掉。Dos下的编程思路是单任务的,你只要认为你的程序会按照你的流程一步步的执行下去,不必考虑先后问题(当然程序可能会被中断打断,但你可以认为它们会把环境恢复,如果中断程序没有把环境恢复,那是他们的错)。

   在内存管理方式上,Dos汇编和Win32汇编也有很多的不同:Dos工作在实模式下,我们可以寻址1M的内存,寻址时通过段寄存器来制定段的初始地址,每个段的大小为64K,超过1M的部分,就只能把他作为XMS使用,也就是说,只能用作数据存放使用而无法在其中执行程序。

   Windows在保护模式下执行,这里所有的资源对应用程序来说都是被保护的:程序在执行中有级别之分,只有操作系统工作在最高级--0级中,所有应用程序都工作在3级中(Ring3)
Ring3中,你无法直接访问IO端口,无法访问其他程序运行的内存,连向程序自己的代码段写入数据都是非法的,会在Windows的屏幕上冒出一个熟悉的蓝屏幕来。只有对Ring0的程序来说,系统才是全开放的。

   在内存方面,Windows使用了处理器的分页机制,使得对应用程序来说,所有的内存都是平坦的,你不必用一个段寄存器去指定段的地址,因为在保护模式下,段寄存器的含义是不同的(可以参见80386手册方面的书籍),你可以直接指定一个32位的地址来寻址4GB的内存。

   在程序结构方面,Windows程序也有很大的不同,它是基于消息的,你可以想象这样一个常见的Windows窗口,上面有几个按钮,如果你用Dos编程的思路去考虑,你会发现实现它很困难:鼠标移动到窗口边缘时拖动会改变窗口大小,鼠标点击按钮时再做要做的事,你会发现,你的程序自开始执行后就在等待,你不知道鼠标先会点什么地方,实际上你是在等待所有可能的事情的发生。而在Dos下,你可以只顾自己先执行,需要用户输入时,再停下来,你不输入我就不再执行,而且,我让你输入数据A你就不能输入数据B  
<think> 我们正在讨论模式保护模式的开启方法。根据引用[1][2][3][4],我们知道从模式切换到保护模式需要几个关键步骤。下面我将详细说明这些步骤。 关键步骤: 1. 准备全局描述符表(GDT):在保护模式下,内存访问通过段描述符进行。GDT是一个描述符数组,每个描述符定义了一个内存段的基地址、限长权限等属性。我们需要在内存中设置好GDT。 2. 打开A20地址线:为了兼容早期的8086处理器(只有20位地址线),在模式下,第21条地址线(A20)默认是关闭的。在保护模式下,我们需要打开A20地址线以访问超过1MB的内存空间。引用[1]中提到了如何打开A20:通过0x92端口将第一位置1。 3. 设置控制寄存器:将CR0寄存器的PE位(Protection Enable,第0位)置1,从而开启保护模式。 4. 跳转到保护模式代码段:在设置好GDT打开A20后,我们通过一个远跳转指令(jmp)加载CS段寄存器,从而进入保护模式。 具体步骤: 1. 准备GDT: 在模式下,我们需要在内存中构建一个GDT。通常,GDT至少包含三个描述符: - 空描述符(第一个描述符必须为空) - 代码段描述符 - 数据段描述符 每个描述符占8个字节,包含段基址(32位)、段限长(20位)访问权限等。 2. 打开A20地址线: 通过0x92端口操作: ```assembly in al, 0x92 or al, 2 ; 将第1位置1(二进制00000010) out 0x92, al ``` 3. 设置CR0的PE位: ```assembly mov eax, cr0 or eax, 1 ; 将PE位置1 mov cr0, eax ``` 4. 远跳转以更新CS: 在设置CR0后,需要立即执行一个远跳转(far jmp)来加载CS段寄存器,以使用新的段描述符。 ```assembly jmp 代码段选择子:保护模式入口点 ``` 其中,代码段选择子指向GDT中代码段描述符的索引(例如,第一个描述符索引为0,则代码段选择子为0x08,因为第一个是空描述符,第二个是代码段描述符,索引为1,而选择子的结构为:索引占13位,第3位为TI(0表示GDT),第2-0位为RPL。所以索引1的选择子为1<<3 = 0x08)。 5. 初始化其他段寄存器: 在进入保护模式后,我们需要重新加载数据段寄存器(如DS, SS, ES等),使它们指向数据段描述符。 示例代码片段(汇编): ```assembly ; 1. 定义GDT gdt_start: dd 0,0 ; 空描述符 gdt_code: ; 代码段描述符:基地址=0,限长=0xFFFFF(4GB),权限:可执行、可读,DPL=0 dw 0xFFFF ; 段限长[15:0] dw 0 ; 段基址[15:0] db 0 ; 段基址[23:16] db 0x9A ; 访问权限:P=1, DPL=00, S=1, Type=1010(可执行、可读) db 0xCF ; 标志位(G=1,D/B=1,保留0,AVL=0)段限长[19:16] db 0 ; 段基址[31:24] gdt_data: ; 数据段描述符:基地址=0,限长=0xFFFFF,权限:可读写,DPL=0 dw 0xFFFF dw 0 db 0 db 0x92 ; 访问权限:P=1, DPL=00, S=1, Type=0010(可读写) db 0xCF db 0 gdt_end: ; 定义GDT描述符(用于lgdt指令) gdt_descriptor: dw gdt_end - gdt_start - 1 ; GDT限长 dd gdt_start ; GDT基地址 ; 2. 打开A20 mov al, 0x92 or al, 2 out 0x92, al ; 3. 加载GDT lgdt [gdt_descriptor] ; 4. 设置CR0的PE位 mov eax, cr0 or eax, 1 mov cr0, eax ; 5. 远跳转,更新CS jmp 0x08:protected_mode_entry ; 0x08是代码段选择子(索引1,TI=0,RPL=00) protected_mode_entry: ; 6. 初始化其他段寄存器 mov ax, 0x10 ; 数据段选择子(索引2,TI=0,RPL=00)即16 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax ``` 注意事项: - 在模式下,我们需要先设置好GDT,因为GDT的地址(线性地址)在切换到保护模式前就要通过`lgdt`指令加载到GDTR寄存器。 - 打开A20地址线是为了能够访问1MB以上的内存。如果不打开,当访问超过1MB的地址时,会回绕到0地址。 - 在设置CR0的PE位后,处理器立即进入保护模式,但此时CS寄存器仍然保存着模式的值(即段基址=CS<<4),因此需要通过远跳转加载新的段选择子,从而更新CS寄存器。 - 在进入保护模式后,我们还需要设置堆栈等。 引用说明: - 打开A20地址线的方法引用自[^1]。 - 保护模式需要分页功能(但分页不是必须开启的),引用[2]提到分页功能需要在保护模式下开启,但保护模式本身并不要求分页必须开启。保护模式下可以只使用分段而不开启分页。 - 模式保护模式的区别引用自[^2][^3]。 - 初始化段描述符的基地址引用自[^4],其中提到了在运行期初始化段描述符的基地址。 通过以上步骤,我们完成了从模式保护模式的切换。 §§相关问题§§ 1. 在保护模式下如何设置分页机制? 2. 全局描述符表(GDT)的详细结构是怎样的? 3. 模式下如何访问超过1MB的内存? </think> ### 模式保护模式的开启方法 在 Linux 系统中,从模式切换到保护模式需要经过以下关键步骤,这些步骤基于 x86 架构的设计特性[^1][^2][^3]: --- #### 一、开启前准备 1. **初始化段描述符表** 创建全局描述符表(GDT),至少包含: - **代码段描述符**:定义代码段的基址、限长权限 - **数据段描述符**:定义数据段的基址权限 - **空描述符**(索引0)[^4] ```c // 示例:代码段描述符结构(32位) struct gdt_entry { uint16_t limit_low; uint16_t base_low; uint8_t base_mid; uint8_t access; // 访问权限 (P|DPL|S|Type) uint8_t flags_limit; // G|D/B|L|AVL|段限长[19:16] uint8_t base_high; }; ``` 2. **加载GDT到GDTR寄存器** 使用 `LGDT` 指令加载GDT基址限长: ```assembly lgdt [gdt_descriptor] ; gdt_descriptor包含GDT地址大小 ``` --- #### 二、关键开启步骤 1. **打开A20地址线** 解除8086兼容限制,启用20位以上地址线[^1]: ```assembly in al, 0x92 ; 读取0x92端口 or al, 0x02 ; 设置第1位为1 (0000_0010B) out 0x92, al ; 写回端口 ``` 2. **设置CR0控制寄存器** 将CR0寄存器的PE位(Protection Enable)置1[^2][^3]: ```assembly mov eax, cr0 or eax, 0x00000001 ; 设置PE位 mov cr0, eax ``` 3. **远跳转刷新流水线** 通过JMP指令加载CS段寄存器,激活保护模式[^4]: ```assembly jmp CODE_SEG:protected_mode_entry ; CODE_SEG是代码段选择子 ``` 4. **初始化数据段寄存器** 更新DS/ES/SS等段寄存器: ```assembly mov ax, DATA_SEG ; DATA_SEG是数据段选择子 mov ds, ax mov es, ax mov ss, ax ``` --- #### 三、完整流程示例(汇编) ```assembly [bits 16] start: ; 1. 准备GDT lgdt [gdt_descriptor] ; 2. 开启A20 in al, 0x92 or al, 2 out 0x92, al ; 3. 设置CR0.PE=1 mov eax, cr0 or eax, 1 mov cr0, eax ; 4. 跳转到保护模式 jmp CODE_SEG:protected_mode [bits 32] protected_mode: ; 5. 初始化数据段 mov ax, DATA_SEG mov ds, ax ... ; 其他段寄存器初始化 ``` --- #### 四、技术原理 1. **模式区别** | 特性 | 模式 | 保护模式 | |--------------|----------------------------|---------------------------| | **地址空间** | 1MB (20位) | 4GB (32位) | | **地址计算** | 物理地址=段基址×16+偏移 | 通过GDT查表获得段基址 | | **内存保护** | 无 | 有(段限长/权限检查) | | **特权级** | 仅0级 | 0-3级(Ring0~Ring3) | 2. **保护模式优势** - **内存隔离**:段限长防止越界访问 - **特权级保护**:阻止用户态访问内核代码 - **支持分页**:为虚拟内存奠定基础[^2][^3] > 注意:现代Linux已弱化分段机制,但保护模式仍是系统启动的必经阶段[^2][^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值