Linux内核系列—操作系统开发之进入32位保护模式
Posted 是非猫
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核系列—操作系统开发之进入32位保护模式相关的知识,希望对你有一定的参考价值。
源码如下:
; ========================================== ; pmtest1.asm ; 编译方法:nasm pmtest1.asm -o pmtest1.bin ; ========================================== %include "pm.inc" ; 常量, 宏, 以及一些说明 org 07c00h jmp LABEL_BEGIN [SECTION .gdt] ; GDT ; 段基址, 段界限 , 属性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代码段 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址 ; GDT 结束 GdtLen equ $ - LABEL_GDT ; GDT长度 GdtPtr dw GdtLen - 1 ; GDT界限 dd 0 ; GDT基地址 ; GDT 选择子 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ; END of [SECTION .gdt] [SECTION .s16] [BITS 16] LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h ; 初始化 32 位代码段描述符 xor eax, eax 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 ; 为加载 GDTR 作准备 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_GDT ; eax <- gdt 基地址 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址 ; 加载 GDTR lgdt [GdtPtr] ; 关中断 cli ; 打开地址线A20 in al, 92h or al, 00000010b out 92h, al ; 准备切换到保护模式 mov eax, cr0 or eax, 1 mov cr0, eax ; 真正进入保护模式 jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, ; 并跳转到 SelectorCode32:0 处 ; END of [SECTION .s16] [SECTION .s32]; 32 位代码段. 由实模式跳入. [BITS 32] LABEL_SEG_CODE32: mov ax, SelectorVideo mov gs, ax ; 视频段选择子(目的) mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。 mov ah, 0Ch ; 0000: 黑底 1100: 红字 mov al, ‘P‘ mov [gs:edi], ax ; 到此停止 jmp $ SegCode32Len equ $ - LABEL_SEG_CODE32 ; END of [SECTION .s32]
运行结果如下,在屏幕最右边有一个红色的P:
源码解析:
1.首先程序跳转至LABEL_BEGIN处,jmp LABEL_BEGIN。将ds、es、ss段寄存器全部初始化为当前代码段。
2.初始化32位代码段描述符
在实模式下,也就是8086的16位的CPU的寻址方式是段x16+偏移,而在保护模式下,段寄存器值变成了一个索引,这个索引指向段描述符,也就是GDT中对应的那个描述符,描述符中包含了最终的基地址。
38-41行表示将当前程序代码段左移4位也就是x16,再加上LABEL_SEG_CODE32的偏移地址,最终形成LABEL_SEG_CODE32的物理地址,42-45表示把此物理地址加载到段描述符对应的段基址位置,段描述符格式如下图所示
段基地址0-15位也就是LABEL_DESC_CODE32 + 2地址处,将16位的ax传入,第43行向右移动16位表示已经传入的16位消除,然后将剩余的两个8位高低寄存器值传入对应的段基地址处。
Descriptor是在pm.inc中定义的宏,如下图所示,表示的就是段描述符的格式:
3.加载GDTR,GDRT的结构图如下所示
GDTR是唯一的一个指向段描述符表的寄存器,48-55行就是把段描述符表的基地址加载到GDTR寄存器当中。48行清除eax值,49-52是把LABEL_GDT的物理地址加载到20行的GdtPtr的双字处,也就是GDT基地址。55行把GdtPtr地址的内容加载到GDTR,双字节dw对应的是16位界限,双字dd对应的是32位基地址。
4.关中断和打开A20
保护模式下中断处理机制和实模式不同,所以先关闭中断,指令为cli。打开A20为历史原因防止偏移超出最大值时回滚。58-63行
5.切换到保护模式
只要把寄存器cr0的第0位置为1就行了。66-68行
6.真正进入保护模式
jmp dword SelectorCode32:0,这一行是真正进入保护模式的代码,SelectorCode32是个段选择子,段选择子的作用是找到对应的段描述符从而找到对应的段基地址。段选择子的格式如下图所示
从位3开始表示为段描述符表的索引,这句代码执行完之后就会把描述符LABEL_DESC_CODE32中的段基址也就是LABEL_SEG_CODE32加载到cs。
dword的作用是因为当前还是16位的代码,如果没有dword,SelectorCode32:0的偏移地址如果超出16位那么只会截取16位。
到此已经完全进入32位保护模式了。
以上是关于Linux内核系列—操作系统开发之进入32位保护模式的主要内容,如果未能解决你的问题,请参考以下文章