06.实模式进入保护模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Zllvincent/article/details/83549084

简介

上一节我们实现了从内核加载器中加载其它扇区代码并执行,但始终工作在实模式状态下。内存寻址方式和8086相同,由16位段寄存器的内容乘以16(10H)当做段基地址,加上16位偏移地址形成20位的物理地址,最大寻址空间1MB,最大分段64KB。

保护模式与实模式相比地址转换方式差异较大:

实模式下的地址转换方式,假设我们在ES中存入0x1000,DI中存入0xFFFF,那么ES:DI=0x1000*0x10+0xFFFF=0x1FFFF。
保护模式下ES:DI=全局描述符表中第0x200项描述符(一个描述符8个字节)给出的段基址+0xFFFF

1、描述符
保护模式下引入描述符来描述各种数据段,所有的描述符均为8个字节(0-7),由第5个字节说明描述符的类型,类型不同,描述符的结构也有所不同。若干个描述符集中在一起组成描述符表,而描述符表本身也是一种数据段,也使用描述符进行描述。“地址转换”由描述符表来完成,描述符表是一张地址转换函数表。

2、选择子
选择子是一个2字节的数,共16位,最低2位表示RPL,第3位表示查表是利用GDT(全局描述符表)还是LDT(局部描述符表)进行,最高13位给出了所需的描述符在描述符表中的地址。(注:13位正好足够寻址8K项)有了以上三个概念之后可以进一步工作了,现在程序的运行与实模式下完全一样!各段寄存器仍然给出一个“段值”,只是这个“假段值”到真正的段地址的转换不再是“左移4位”,而是利用描述符表来完成。

3、80x86系列中引入了两个新寄存器GDTR和LDTR,其中GDTR用于表示GDT在内存中的段地址和段限(就是表的最大偏移上限),因此GDTR是一个48位的寄存器,其中32位表示段地址,16位表示段限。

目标

实现实模式到保护模式的切换需要使用汇编语言实现,boot.s文件修改如下

;能用于操作内存的寄存器只能是bx、bp、si、di
;0x7c00--0x7dff 这512字节用于启动区
;对内存的访问都必须指定段寄存器,没有显示指定时将使用ds作为段寄存器


        org 0x7c00
    
        LOAD_ADDR EQU 0x9000   ;内核加载偏移地址
    
        mov ax,0
        mov ss,ax
        mov ds,ax
        mov es,ax 
    
        mov bx,LOAD_ADDR
       
        mov ch,1        ;柱面号
        mov dh,0        ;磁头号
        mov cl,2        ;扇区号
        mov ah,0x02     ;0x02表示读盘操作
        mov al,1        ;表示连续读取扇区数
        mov dl,0        ;驱动器号,早期有多个软驱,一般只有一个写死0
        int 0x13        ;调用BIOS实现磁盘读取
        jc error        ;读盘操作失败,flag标志寄存器cf 标志位被置1 
        jmp bx          ;跳转到加载的内存地址开始执行代码
    
    
    fin:
        hlt
        jmp fin
    
    
    putloop:
        mov al,[si]
        inc si
        cmp al,0
        je fin
        mov ah,0x0e     ;中断调用参数
        mov bx,15       ;字符颜色
        int 0x10        ;中断调用号
        jmp putloop
    
    
    error:
        mov si,errMsg
        jmp putloop
       
    
    errMsg:
        db 'error'
        db 0

kernel.s文件如下

	;全局描述符结构 8字节
    ; byte7 byte6 byte5 byte4 byte3 byte2 byte1 byte0
    ; byte6低四位和 byte1 byte0 表示段偏移上限
    ; byte7 byte4 byte3 byte2 表示段基址
    


    ;定义全局描述符数据结构
    ;3 表示有3个参数分别用 %1、%2、%3引用参数
    ;%1:段基址     %2:段偏移上限  %3:段属性
    %macro GDescriptor  3
        dw %2 & 0xffff
        dw %1 & 0xffff
        db (%1>>16) & 0xff 
        dw ((%2>>8) & 0x0f00) | (%3 & 0xf0ff)
        db (%1>>24) & 0xff 
    %endmacro


    DA_32       EQU 0x4000   ; 32 位段
    DA_CODE     EQU 0x98     ; 只执行代码段属性值
    DA_RW       EQU 0x92     ; 可读写数据段属性值


    org 0x9000 
    jmp entry
    
    [SECTION .gdt]
    ;定义全局描述符                            段基址           段偏移上限       段属性
    LABEL_GDT:           GDescriptor         0,             0,             0
    LABEL_DESC_CODE:     GDescriptor         0,             SegCodeLen-1,  DA_CODE+DA_32 
    LABEL_DESC_VIDEO:    GDescriptor         0xb8000,       0xffff,        DA_RW


    ;gdt 表大小
    GdtLen  equ     $-LABEL_GDT

    ;gdt表偏移上限和基地址
    GdtPtr  dw      GdtLen-1
            dd      0


    ;cpu开机进入实模式时使用的段寄存器 cs,ds,es,ss 和偏移地址组成内存地址,内存地址=段寄存器 * 16 + 偏移地址 
    ;保护模式中段寄存器保存的是gdt 描述表中各个描述符的偏移,也叫选择子
    

    SelectorCode32  EQU     LABEL_DESC_CODE-LABEL_GDT
    SelectorVideo   EQU     LABEL_DESC_VIDEO-LABEL_GDT

    [SECTION .s16]
    [BITS 16]
entry:
    mov ax,cs 
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov sp,0x100 

    ;设置LABEL_DESC_CODE描述符段基址
    mov eax,0 
    mov ax,cs 
    shl eax,4
    add eax,SEG_CODE32
    mov word [LABEL_DESC_CODE+2],ax
    shr eax,16
    mov [LABEL_DESC_CODE+4],al
    mov [LABEL_DESC_CODE+7],ah

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

    ;设置GDTR 寄存器
    lgdt [GdtPtr]

    cli     ;关闭可可屏蔽中断,如键盘中断

    in al,0x92 
    or al,0x02
    out 0x92,al 

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

    jmp dword SelectorCode32:0


    [SECTION .s32]
    [BITS 32]
SEG_CODE32:
    mov ax,SelectorVideo 
     

    ;gs 寄存器是80386新增的辅助段寄存器
    mov gs,ax

    ;在屏幕中间显示字符串,屏幕为每行80个字符,共25行。低字节为ascii字符编码,高字节为字符显示属性
    ;可参考 《汇编语言》 王爽,屏幕显示相关章节

    ;在屏幕11行20列开始显示字符
    mov ax,(80*11+20)
    mov ecx,2
    mul ecx
    mov edi,eax
    mov ah,00000010b 

    mov si,msg 
    
putloop:
    mov al,[esi]
    cmp al,0
    je  fin
    mov [gs:edi],ax
    add edi,2
    inc esi 
    jmp putloop

fin:
    hlt
    jmp fin

msg:
    db 'protected mode',0


SEG_CODE32_END: nop 



;32为模式代码长度
SegCodeLen  EQU SEG_CODE32_END-SEG_CODE32

使用nasm 分别编译boot.s 、kernel.s 生成boot.bat和kernel.bat

使用C语言软盘功能模块制作虚拟软盘,main.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#include "floppy.h"


int main(int argc,char **argv){
    FILE *fp = initFloppy("floppy.img");
    if(fp == NULL) {
        printf("初始化磁盘失败");
        exit(0);
    }
    
    FILE *src = fopen("boot.bat", "r");
    if(src == NULL) {
        printf("文件打开失败");
        exit(0);
    }
    char buf[512];
    memset(buf, 0, 512);
    fread(buf, 512, 1, src);
    buf[510] = 0x55;
    buf[511] = 0xaa;
    writeFloppy(0, 0, 1, fp, buf);
    fclose(src);
    
    memset(buf, 0, 512);
    src = fopen("kernel.bat", "r");
    if(src == NULL) {
        printf("文件打开失败");
        exit(0);
    }
    fread(buf, 512, 1, src);
    writeFloppy(1, 0, 2, fp, buf);
    fclose(src);
    
    fclose(fp);
    return 1;
}

使用C语言文件操作boot.bat、kernel.bat,生成floppy.img 虚拟软盘文件,VirtualBox虚拟机加载该软盘文件效果如下:
在这里插入图片描述

我们成功从实模式进入保护模式,并在保护模式下实现字符显示!

补充

在内存地址空间中,B8000H~BFFFFH共32KB的空间,为8025彩色字符模式的显示缓冲区,向这个地址空间写数据,写入的内容将立即出现在显示器上.
在80
25彩色字符模式下,显示器可以显示25行,每行80个字符,每个字符可以有256种属性(背景色,闪烁,高亮等组合信息).
这样一个字符在显示缓冲区中就要占两个字节,分别存放字符的ASCII码和属性,80*25模式下,一屛内容在显示缓冲区中占4000个字节.
显示缓冲区分为8页,每页4KB,显示器可以任意显示任意一页的内容,一般情况下,显示第0页的内容,也就是B8000~B8F9FH中的4000个字节.

颜色属性字节格式:
7 6 5 4 3 2 1 0
BL R G B I R G B
闪烁 背景(rgb) 高亮 前景(rgb)

猜你喜欢

转载自blog.csdn.net/Zllvincent/article/details/83549084
今日推荐