热门搜索 :
考研考公
您的当前位置:首页正文

从实模式进入保护模式

来源:东饰资讯网

在实模式下,程序可以随意修改1M内存的任何位置的数据,没有任何监管,如同原始社会。
进入保护模式后,使用内存需要登记,不仅仅是你从哪里开始使用,还需要登记你使用的范围、用于和目的,读?写?还是执行?
为了实现登记,保护模式提出一个新的概念,描述符。每个段都有8个字节的描述,并且所有的描述符都需要集中存储,便于管理。这样就出现了描述符表
而最主要的描述符表就是全局描述符表(Global Descriptor Table, GDT)。这个全局描述符表是进保护模式前必须先定义好的。

描述符表可以放在内存任何空闲位置,为了便于跟踪全局描述符,处理器内部有一个48位的寄存器,这就是全局描述符表寄存器 (GDTR)
GDTR由32位线性地址和16位的边界组成,32位地址可位于4G内存中任何位置,16位边界描述了全局描述符表的边界。
一个描述符有8个字节,因此16位边界可界定(2^16)/8=8192个描述符。
要注意一点的是这个16 位边界需要总描述符减一。当然这是规定,哈哈。

接下来,我们就要介绍描述符了。
每个描述符占8个字节,也就是2个双字。
由于兼容,从8086到80286再到80386,这个描述符会有点怪,这也是我们常常不理解的地方。当然,你记住就好,能理解工程师的苦衷那就更好了。
一个描述符如下所示。


GDTR

每个描述符中指定了32位的段起始地址(高32位中的3124和2316,低32位中的3116),以及20位的段边界(高32位中的1916,低32位中的15~0)。
其它位的描述如下。

G 位是粒度位(Granularity)。G=0时,段界限以字节为单位,段的扩展为1B1MB;G=1时,段界限以4KB为单位,段的扩展为4KB4GB。
D/B 位区分段或栈是32位还是16位的,D/B=1表示32位,D/B=0表示16位。
L 位是64位代码段标志,L=1表示64位,其他则为0即可。
AVL 软件可用的位,处理器不使用。

P 位是段存在位,P=1表示段存在于内存中,P=0表示段不存在与内存。这个位可用于硬盘虚拟内存,当内存不够用时就将很少用的内存放于硬盘中,并这个位进行标志。
DPL 位时特权描述位,表示处理器4种特权:0、1、2、3,越小级别越高。
S 位表示描述符的类型。S=0表示一个系统段,S=1表示一个代码段或者数据段。
TYPE 位指示描述符的子类型。其对于数据段,4位是X、E、W、A;对于代码段,4位是X、C、R、A。

X E W A 描述符类别 含义
0 0 0 x 数据 只读
0 0 1 x 数据 读、写
0 1 0 x 数据 只读,向下扩展
0 1 1 x 数据 读、写,向下扩展
X C R A 描述符类别 含义
1 0 0 x 代码 只执行
1 0 1 x 代码 执行、读
1 1 0 x 代码 只执行,依从的代码段
1 1 1 x 代码 执行、读,依从的代码段

接下来我们就可以读示例代码了。(本人比较懒,就直接使用别人写好的例子啦,出处《x86汇编--从实模式到保护模式》,这是我见过写的最详细关于编写系统内核的书。)

        org 0x7c00                         ; 该命令表示程序将被装在到偏移地址为0x7C00的地方
        jmp start
   start:
        mov ax,cs      
        mov ss,ax                          ;设置堆栈段和栈指针 
        mov sp,0x7c00
     
        ;计算GDT所在的逻辑段地址 
        mov ax,[cs:gdt_base]        ;低16位 gdt_base是段内偏移标志,不是一个变量
        mov dx,[cs:gdt_base+0x02]   ;高16位 
        mov bx,16        
        div bx                             ;实模式下段基址x16+偏移地址,其实就是将0x7e00除以16
        mov ds,ax                          ;令DS指向该段以进行操作
        mov bx,dx                          ;段内起始偏移地址 
    
        mov dword [bx+0x00],0x00           ;创建0#描述符,它是空描述符,这是处理器的
                                                                要求
        mov dword [bx+0x04],0x00  

                                           ;创建#1描述符,保护模式下的代码段描述符
        mov dword [bx+0x08],0x7c0001ff     ; 7c00为段基址15~0,01ff为段界限
                                                                   符15~0 512字节
        mov dword [bx+0x0c],0x00409800     ;0040 00为段基址31~24 40:0100_0000B
                                表示G=0以字节为段界限粒度,D/B=1表示为32位偏移或操作,
                                 L=0表示不为64位,AVL保留位,后面的0000为段界限19~16;
                                 98:1001_1000表示P=1段存在,DPL=00特权为0最高级,
                        S=1表示为代码/数据段,TYPE=8表示只执行,00表示段基址23~16

                                           ;创建#2描述符,保护模式下的数据段描述符
                                           (文本模式下的显示缓冲区) 
        mov dword [bx+0x10],0x8000ffff     ;64KB 数据段基址 0xb8000显存地址
        mov dword [bx+0x14],0x0040920b     ;92表示TYPE=2读写

                                           ;创建#3描述符,保护模式下的堆栈段描述符
        mov dword [bx+0x18],0x00007a00
        mov dword [bx+0x1c],0x00409600     ;96表示type=6可读可写

                                           ;初始化描述符表寄存器GDTR
        mov word [cs: gdt_size],31  ;描述符表的界限(总字节数减一)   
                                            
        lgdt [cs: gdt_size]         ;载入6个字节,先载入gdt_size也就是31,
                                                 然后是gdt_base 0x7e000 32位基址
     
        in al,0x92                         ;南桥芯片内的端口 
        or al,0000_0010B
        out 0x92,al                        ;打开A20

        cli                                ;保护模式下中断机制尚未建立,应 
                                           ;禁止中断 
        mov eax,cr0
        or eax,1
        mov cr0,eax                        ;设置PE位,打开了保护模式标志
     
        ;以下进入保护模式... ...
        jmp dword 0x0008:(flush-0x7c00)             ;16位的描述符选择子:32位偏移,
                                           为什么是0x0008,因为已经进入了保护模式,段寄存器不
                                     再保存基址了,而是保存偏移地址的索引号,8=01_000B,
                                 也就是索引号为1的代码段描述符
                                           ;清流水线并串行化处理器,这个比较重要,
                                           清初了流水线可更新段寄存器
        [bits 32] 

   flush:
        mov cx,00000000000_10_000B         ;加载数据段选择子(0x10)
        mov ds,cx

        ;以下在屏幕上显示"Protect mode OK." 
        mov byte [0x00],'P'  
        mov byte [0x02],'r'
        mov byte [0x04],'o'
        mov byte [0x06],'t'
        mov byte [0x08],'e'
        mov byte [0x0a],'c'
        mov byte [0x0c],'t'
        mov byte [0x0e],' '
        mov byte [0x10],'m'
        mov byte [0x12],'o'
        mov byte [0x14],'d'
        mov byte [0x16],'e'
        mov byte [0x18],' '
        mov byte [0x1a],'O'
        mov byte [0x1c],'K'

                                           ;以下用简单的示例来帮助阐述32位保护模式下
                                         的堆栈操作 
        mov cx,00000000000_11_000B         ;加载堆栈段选择子
        mov ss,cx
        mov esp,0x7c00

        mov ebp,esp                        ;保存堆栈指针 
        push byte '.'                      ;压入立即数(字节)
        
        sub ebp,4
        cmp ebp,esp                        ;判断压入立即数时,ESP是否减4 
        jnz ghalt                          
        pop eax
        mov [0x1e],al                      ;显示句点 
     
 ghalt:     
        hlt                                ;已经禁止中断,将不会被唤醒 

        gdt_size         dw 0
        gdt_base         dd 0x00007e00     ;GDT的物理地址, 这个是自定义的,
                               只要在实模式1M内存内即可,当然最好是在第一个扇区512之后,
                               (7E00H = 7C00H + 512)
                            
        times 510-($-$$) db 0
                         db 0x55,0xaa

实验方法:

  1. 使用bochs自带的bximage创建一个1.44软盘
  2. 使用nasm -f bin test.asm -o test.bin编译汇编
  3. 使用dd if=test.bin of=test.img bs=1024 count=1440 conv=notrunc写入虚拟映像,再使用虚拟机引导即可
Top