MyOS(五):由实模式进入保护模式(32位)

保护模式的寻址方式不在使用寄存器分段的方式直接寻址方式了。而采用的是使用GDT(全局分段描述表)来寻址。从而使用更多的内存地址     

     

      从实模式到保护模式,是计算法技术跨时代的发展。大家想想笨拙的Dos界面,黑底白字的那种冷漠界面到win95各种色彩斑斓的窗口,两者之间的区别其实就是实模式和保护模式的天壤之别     

      操作系统内核真正要发挥功能的话,必须进入保护模式,从而实现32位的寻址。之前我们一直是处于实模式,实模式寻址只有16位,最多就寻址到1M左右的内存

      从实模式到保护模式,是计算法技术跨时代的发展。从笨拙的Dos界面,黑底白字的那种冷漠界面到win95各种色彩斑斓的窗口,两者之间的区别其实就是实模式和保护模式的天壤之别

      保护模式中,最重要的一个概念莫过于”保护”二字,有了“保护”功能后,CPU为软件提供了很多的功能,当然也有了更多的限制

 

注意,实模式和保护模式是内存的两种模式

内核态与用户态是操作系统的两种运行级别,跟intel cpu没有必然的联系

 

进入保护模式后,还有一个巨大的好处是,我们可以引入C语言来开发内核

 

实模式下的寻址方式

段寄存器*16+偏移(16bit)

保护模式下分段机制的内存寻址

保护模式下 分段机制是利用一个称作段选择符的偏移量,从而到描述符表找到需要的段描述符,而这个段描述符中就存放着真正的段的物理首地址,再加上偏移量

 

 

保护模式的实现

我们要理解什么叫保护模式,先看保护模式的两个显著特点:

①寻址空间从实模式的1M增强到4G

②不同的代码拥有不同的优先级,优先级高的能够执行特殊指令,优先级低的,某些重要指令就无法执行。

于是进入保护模式,我们需要解决两个问题,一是如何获取超过1M以上的内存地址,第二是如何设置不同代码所具有的优先级。

      我们先看看寻址能力的变化,在实模式下,cpu是16位的,寄存器16位,数据总线16位,地址总线20位,于是寻找的范围必然受限于20位的地址总线,所以寻找范围无法超过1M(2^20). 要想实现4GB的寻址,我们必须使用32位来表示地址,intel是这么解决这个问题的,他们用连续的8个字节组成的结构体来解决一系列问题
byte0
byte1
…..
byte7

其中,字节2,3,4以及字节7,这四个字节合在一起总共有32位,这就形成了一个32位的地址(234是低位,7是高位)。同时把字节0,字节1,以及将字节6的拆成两部分,各4个bits,前4个bits跟字节0,字节1合在一起,形成一个20个bit的数据(1M大小),用来表示要访问的内存长度,也就是段长度。这样,我们就解决了内存寻址的问题。

byte5是用来标记属性的

        由于保护模式是一个非常复杂的逻辑,为了掌握它,我们一次只吃透一点,这样才好掌握,这里我们首先了解如何进行32位的寻址

 

pm.inc

汇编语言的头文件

%macro Descriptor 3
    dw    %2  &  0FFFFh  ;byte1和byte0
    dw    %1  &  0FFFFh  ;byte3和byte2
    db   (%1>>16) & 0FFh
    dw   ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)
    db   (%1 >> 24) & 0FFh
%endmacro


DA_32		EQU	4000h	; 32 位段
DA_C		EQU	98h	; 存在的只执行代码段属性值
DA_DRW		EQU	92h	; 存在的可读写数据段属性值

pm.inc里面的宏定义就是我们说的7字节数据结构,
%macro Descriptor 3
表示要初始化该数据结构,需要传入3个参数,%1表示引用第一个参数,%2表示引用第二个参数。初始化该结构时,输入的一个参数是内存的地址。

大家看语句:
dw %1 & 0FFFFh
db (%1>>16) & 0FFh
这两句就是把内存地址的头三个字节放入到byte2,byte3,byte4

 

db (%1 >> 24) & 0FFh
就是讲地址的第4个字节放入到byte7. 初始化数据结构的第二个参数表示的是要访问的内存的长度

 

dw %2 & 0FFFFh
就是把内存长度的头两个字节写入byte0,byte1

 

dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)
中的((%2 >> 8) & 0F00h)就是把内存长度的第16-19bit写入到byte6的前4个bit.

 

由此要访问的内存和内存的长度就都设置好了

 

这个GDT.java和pm.inc是对应的,都是描述GDT全局描述符表,只不过用java写起来更直观好看一点

kernel.asm中的descriptor的数据结构就类似下面的程序

但我们今天实现的时候并没有用到GDT.java这段代码,只是展示一下descriptor的结构


/*|BYTE7|BYTE6I|BYTE5|BYTE4|BYTE3|BYTE2|BYTE1|BYTE0 */
public class GDT{
	byte[] segmentLength_low = new byte[2];//BYTE1|BYTE0 */

	byte[] baseAddressLow = new byte[3]; //BYTE2|BYTE3|BYTE4
	
	byte[] attribute = new byte[2]; //BYTE5|BYTE6
									//BYTE6的头4个bit,用于segment length,于是段长度的字长为20bit
	byte addressHigh;//BYTE7
	
	//实模式下的寻址方式
	//段态存器*16+偏移(16bit)
}

 

kernel.asm

kernel.asm生成kernel.bin

其中会引用pm.inc

%include "pm.inc"  
;定义了一个数据结构
 
org   0x9000
 
jmp   LABEL_BEGIN
 
[SECTION .gdt] ;全局描述符表,用来描述一段内存数据的属性,加载到CPU之后,CPU就会通过它去访问数据
;这里面用的Descriptor数据结构是在pm.inc里面定义的
 ;                                  段基址          段界限                属性
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
 
GdtLen     equ    $ - LABEL_GDT       ;当前的地址-开头 = 24, 一个descriptor八字节,三个共24字节
GdtPtr     dw     GdtLen - 1 
           dd     0
 
SelectorCode32    equ   LABEL_DESC_CODE32 -  LABEL_GDT
SelectorVideo     equ   LABEL_DESC_VIDEO  -  LABEL_GDT
 
[SECTION  .s16]
[BITS  16]
LABEL_BEGIN:
     mov   ax, cs
     mov   ds, ax
     mov   es, ax
     mov   ss, ax
     mov   sp, 0100h
 
     xor   eax, eax ;清零
     mov   ax,  cs
     shl   eax, 4
     add   eax, LABEL_SEG_CODE32
     mov   word [LABEL_DESC_CODE32 + 2], ax  ;byte2
     shr   eax, 16
     mov   byte [LABEL_DESC_CODE32 + 4], al  ;byte4
     mov   byte [LABEL_DESC_CODE32 + 7], ah  ;byte7
     ;上面这一段就将用于保护模式的32位地址的描述地址相关信息给初始化完成了
 
     xor   eax, eax ;清零
     mov   ax, ds
     shl   eax, 4
     add   eax,  LABEL_GDT
     mov   dword  [GdtPtr + 2], eax
 
     lgdt  [GdtPtr]  ;lgdt是BIOS调用,加载中断描述符到CPU
 
     cli   ;关中断——防止下面代码被打断
 
     ;------------这一段就是从实模式进入保护模式----------------------
     in    al,  92h
     or    al,  00000010b
     out   92h, al
 
     mov   eax, cr0 ;cr0也是一个寄存器,有专门的用途的,用来控制硬件的,不能像ax这样的用于计算
     or    eax , 1
     mov   cr0, eax
     ;----------------------------------
 
     jmp   dword  SelectorCode32: 0
 
     [SECTION .s32]
     [BITS  32]
 
LABEL_SEG_CODE32:
    mov   ax, SelectorVideo ; SelectorVideo的段基址是0B8000H
    mov   gs, ax
    mov   si, msg
    mov   ebx, 10
    mov   ecx, 2
showChar:
    mov   edi, (80*11)
    add   edi, ebx
    mov   eax, edi
    mul   ecx
    mov   edi, eax
    mov   ah, 0ch ;ah存放字符的颜色
    mov   al, [si]
    cmp   al, 0
    je    end
    add   ebx,1
    add   si, 1
    mov   [gs:edi], ax
    jmp    showChar
end: 
    jmp   $
    msg:
    DB     "Protect Mode", 0
 
SegCode32Len   equ  $ - LABEL_SEG_CODE32

从LABEL_SEG_CODE32:这一部分开始,代码就执行在保护模式下

这段代码的作用就是显示一串字符

在保护模式下,寄存器不再存储具体地址的数值,而是全局描述符表的偏移

gs是计算机的一个寄存器,它跟eax,ebx这些寄存器差不多,但作用更为单一,主要用来指向显存

当我们将信息写入gs指向的内存后,信息会显示到屏幕上。内存地址从0XB800h开始,从该地址开始,每两个字节用来在屏幕上显示一个字符,这两个字节中,第一个字节的信息用来表示字符的颜色,第二个字节用来存储要显示的字符的ASCII值,屏幕一行能显示80个字符

0B8000h表示的是显存的地址。我们如果要在屏幕上显示字符的话,需要把显示的信息写入到0B8000h开始的地方,写入0B8000h的数据会直接显示在屏幕上
 

 

代码中有语句
mov edi, (80*11)
这表明我们要从第11行开始显示字符。因为整个屏幕可以显示80个字符,80*11表示我们要显示的字符从第11行开始

add edi, ebx
其中,ebx的值是11,这表明我们要从第11行的第10列开始显示字符串


mov eax, edi
mul ecx

ecx的值是2,这个2就是我们前面说过的显示一个字符需要两个字节,上面几句汇编语句的作用是:
eax = ((80*11) + 10) * 2
这样eax就指向了第11行第11列所在的显存位置,接下来语句:
mov ah, 0ch
它的作用是在用来显示字符的两字节中,对第一个字节放入数值0ch,也就是设置字符的颜色,接下来的语句:

mov al, [si]
将寄存器si指向的字符的ascii值写入到第二个字节,这样,字符就显示到屏幕上了。大家注意寄存器si的用法:[si]. si相当于C语言中的一个指针,指向内存某个地址,[si]就是读取si指向的内存地址的信息,等同于 c语言中的*(si)

这里的boot.asm还是用的上一节的,boot.asm生成boot.bin

boot.asm就是loader的代码,是程序启动引导的

kernel.bin是操作系统内核
计算机运行时要先由boot.bin引导计算机去读取存放在硬盘的kernel.bin


boot.asm

org  0x7c00;

LOAD_ADDR  EQU  0X9000

entry:
    mov  ax, 0
    mov  ss, ax
    mov  ds, ax
    mov  es, ax
    mov  si, ax


readFloppy:
    mov          CH, 1        ;CH 用来存储柱面号
    mov          DH, 0        ;DH 用来存储磁头号
    mov          CL, 2        ;CL 用来存储扇区号

    mov          BX, LOAD_ADDR       ; ES:BX 数据存储缓冲区

    mov          AH, 0x02      ;  AH = 02 表示要做的是读盘操作
    mov          AL,  1        ; AL 表示要练习读取几个扇区
    mov          DL, 0         ;驱动器编号,一般我们只有一个软盘驱动器,所以写死   
                               ;为0
    INT          0x13          ;调用BIOS中断实现磁盘读取功能
   
    JC           fin

    jmp          LOAD_ADDR



fin:
    HLT
    jmp  fin


 

MyOS(五):由实模式进入保护模式(32位)_第1张图片

写完“Protect Mode"之后整个内核跳入到死循环

 

 

参考:

https://blog.csdn.net/tyler_download/article/details/52021120

你可能感兴趣的:(Java,操作系统,保护模式,实模式,内核)