(5)打造简单OS-实模式与进入保护模式

1.简介:

      8086实模式     

      80286才出现保护模式,保护模式下的段寄存器存储的是段选择子,不在是8086实模式的段基址了!

      [扩展知识]:可以看了后面知识在回头看一下这段.      

           [8086各个段寄存器通用寄存器都是16位地址总线20位,采用段式段+偏移可访问物理内存1M]

           [80286各个段寄存器通用寄存器都是16位地址总线24位,采用段式选择子+偏移可访问虚拟内存1G;物理内存16M]

           80286全局描述符表一项是占8字节,其中段基址占3字节(24位与地址总线一致)

           [80386段寄存器依然16位通用寄存器也各自扩展到了32位地址总线32位,采用段页式(选择子+【分页】+偏移)可访问虚拟内存4G;物理内存4G]

           80386全局描述符表一项也是占8字节,其中段基址占4字节(32位与地址总线一致),也是在80286描述符基础上增加了些位域内容

2.选择子

        保护模式下的段寄存器存储的是段选择子!这时候选择子相当于数组中的索引,那么怎么用这13位索引获取段基址呢?段寄存器16位每个位域代表意思具体结构如下:

        用高13位索引获取段基址,这样我们必须要在内存某个地方去保存例如实模式下的各个段寄存器的值,另外在加上一些限制和保护体现,最终会形成一种叫做全局描述符表!全局描述符表只是一个概念,也就是描述一个数据的结构组成。描述符表由一项一项的描述符组成,其中每一项占8字节。

 

        既然是索引,就像数组那样总该有个地址可以确定这个数组的首地址吧,在80386中有一个这样的寄存器GDTR里面由软件写入地址,这个地址里存放的便是全局描述符表的首地址,然后利用[首地址+8*索引值]就可以算出最终所要找到的全局描述符表中的一项,即  段选择子指向的描述符地址= GDTR里存的地址 +8×索引

        选择子高13位索引即可索引到2的13次方8192个描述符[0~8191],与GDT界限的16位界限一致,因GDTR是48位寄存器,其中32位是GDTR存储的基址,16位是界限!即GDT界限大小为2的16次方65536,又因为每个描述符占8位,65536/8=8192!所以综上所述,选择子高13位索引和描述符表界限一致!

        扩展知识:如何获取段基址https://blog.csdn.net/jadeshu/article/details/72837230

        ......待添加内容

3.32位实模式与保护模式下的分段机制

       程序想要在计算机上运行,就必须将源代码编译链接成二进制的可执行文件之后才可能被操作系统加载执行。如果在加载的过程中,程序的地址都是绝对的物理地址,那么程序就必须放在一个固定的地方,那么拥有两个相同地址的程序就只能运行一个了。

       于是,分段机制就产生了。让CPU通过 段基址:段内偏移 来访问任意内存,这样程序就可以实现重定位。也就是说,段内偏移相对于段基址是不变的。无论段基址是多少,只要给出段内偏移,CPU就能访问到正确的指令。于是加载用户程序时,只要将整个段的内容复制到新的位置,再将段基址寄存器中的地址改为该地址,程序便可准确无误的运行,因为程序中用的是偏移地址,相对于新的段基址,该偏移地址处的内容还是一样的,如图所示:

       到了保护模式下,虽然访问内存同样是通过段基址:段内偏移的方式进行访问。而且段值同样是存放在原来16位的段寄存器中,但是这些段寄存器存放的不在是段基址,而是相当于一个数组索引的东西,通过这个索引,可以找到一个表项。在这个表项中,存放了段基址等很多的属性,这个表项称为段描述符表。一个段描述符只用来定义一个内存段。代码段要占用一个段描述符,数据段和栈段同样分别要占一个段描述符,这些描述符就存放在全局描述附符表中(GDT)。

一个段描述符表中,会存放很多的段描述符数据,每一个段描述符为8字节,它的格式如下图所示

      G位表示段界限粒度,为0时表示粒度为1字节,为1时表示粒度为4KB

      实际段界限=(描述符中的段界限+1)*粒度-1,假设段界限为0xfffff,G位为1时,实际段界限=0x100000*4KB-1 = 0xFFFFFFFF。如果偏移地址超过了段界限,CPU会抛出异常

      S为表示是否是系统段。当S为1时,表示非系统段,为0表示系统段。

      type字段的属性和S用关系,用一张图来表示

     P位表示段是否位于内存中。

     L位用来设置是否为64位代码段

     D/B位表示有效地址及操作数的大小。对于代码段来说,此位是D位。为0时表示有效地址和操作数是16位。对于栈段来说,此为是B位,为0时表示使用的16位的栈指针寄存器

     我们想要从实模式进入保护模式,就必须构建段描述符表,并将构建好的段描述符加载到全局描述符表中,这是进入保护模式的第一步

4.实模式下的寻址方式

     在计算机的上古时代,还只有16位的CPU,此时我们只能访问1MB的内存空间,这个阶段也还没有保护模式的概念

     在上古时代的CPU设计中,访问内存需要通过 段基址:段内偏移 来访问内存,因为当时还是16位的CPU,所以当时的基址寄存器同样也是16位的,16位所能表示的最大地址空间为 2^10 * 2^6 = 64KB 也就是说,访问超过64KB的内存空间,就需要切换段基址。

     访问内存的方式是通过 物理地址 = 段基址*16 + 段内偏移,当时的地址总线是20位的,也就是刚好能表示1MB的内存空间,而CPU只有16位,想要访问到20位的地址空间,就只能通过特殊的方式处理一下,当时CPU的设计者就在地址处理单元中动了手脚,自动将段基址*16,也就是左移4位,在和16位的段内偏移相加,组成20位的物理地址

      通过上面这种内存访问的方式,能够表示的最大内存是 0xffff:0xffff=0x10ffef = 1M + 64K - 16B超过1M的内存部分被称为高端内存区HMA,由于实模式下的地址线是20位,最大寻址空间是1MB,即0x0~0xfffff。超过1MB内存的部分在逻辑上也是正常的,但物理内存中并没有与之对应的部分。为了让段基址:段内偏移的策略任然可用,CPU采用的做法是将超过1MB的部分自动回绕为0,继续从0地址开始映射

      地址回绕如图

      了解决上述兼容性问题,IBM使用键盘控制器上剩余的一些输出线来管理第21根地址线(从0开始数是第20根)的有效性,被称为A20Gate:

 

  1. 如果A20Gate被打开,则当程序员给出100000H-10FFEFH之间的地址的时候,系统将真正访问这块内存区域;
  2. 如果A20Gate被禁止,则当程序员给出100000H-10FFEFH之间的地址的时候,系统仍然使用8086/8088的方式即取模方式

       如果想要从实模式进入到保护模式下,A20Gate就必须打开,否则只能访问到0x10FFEF的内存空间,这也是我们进入保护模式做的第二步

5.进入保护模式实战

      打开cr0控制寄存器的PE位,也就是将PE位置1,这是保护模式的开关

     通过前面的介绍,进入保护模式要分三步走

  1.  打开A20

  2.  加载gdt  :进入保护模式需要使用GDTR,则需要加载设置GDTR

  3. 将cr0寄存器的PE位置设为1

演示程序

mbr.s 

;主引导程序 
;------------------------------------------------------------
SECTION MBR vstart=0x7c00         
   mov ax,cs      
   mov ds,ax
   mov es,ax
   mov ss,ax
   mov fs,ax
   mov sp,0x7c00
   mov ax,0xb800
   mov gs,ax

; 清屏
;利用0x06号功能,上卷全部行,则可清屏。
; -----------------------------------------------------------
;INT 0x10   功能号:0x06	   功能描述:上卷窗口
;------------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:
   mov     ax, 0600h
   mov     bx, 0700h
   mov     cx, 0                   ; 左上角: (0, 0)
   mov     dx, 184fh		   ; 右下角: (80,25),
				   ; 因为VGA文本模式中,一行只能容纳80个字符,共25行。
				   ; 下标从0开始,所以0x18=24,0x4f=79
   int     10h                     ; int 10h

   ; 输出字符串:MBR
   mov byte [gs:0x00],'L'
   mov byte [gs:0x01],0xA4

   mov byte [gs:0x02],'o'
   mov byte [gs:0x03],0xA4

   mov byte [gs:0x04],'a'
   mov byte [gs:0x05],0xA4	   ;A表示绿色背景闪烁,4表示前景色为红色

   mov byte [gs:0x06],'d'
   mov byte [gs:0x07],0xA4

   mov byte [gs:0x08],'!'
   mov byte [gs:0x09],0xA4
	 
   mov eax,0x2	 ; 起始扇区lba地址.第2个扇区
   mov bx,0x900       ; 写入的地址 0x900
   mov cx,4			 ; 待读入的扇区数 4
   call rd_disk_m_16		 ; 以下读取程序的起始部分(一个扇区)
  
   jmp 0x900      ;跳转到0x900地址
       
;-------------------------------------------------------------------------------
;功能:读取硬盘n个扇区
rd_disk_m_16:	   
;-------------------------------------------------------------------------------
				       ; eax=LBA扇区号
				       ; ebx=将数据写入的内存地址
				       ; ecx=读入的扇区数
      mov esi,eax	  ;备份eax
      mov di,cx		  ;备份cx
;读写硬盘:
;第1步:设置要读取的扇区数
      mov dx,0x1f2
      mov al,cl
      out dx,al            ;读取的扇区数

      mov eax,esi	   ;恢复ax

;第2步:将LBA地址存入0x1f3 ~ 0x1f6

      ;LBA地址7~0位写入端口0x1f3
      mov dx,0x1f3                       
      out dx,al                          

      ;LBA地址15~8位写入端口0x1f4
      mov cl,8
      shr eax,cl
      mov dx,0x1f4
      out dx,al

      ;LBA地址23~16位写入端口0x1f5
      shr eax,cl
      mov dx,0x1f5
      out dx,al

      shr eax,cl
      and al,0x0f	   ;lba第24~27位
      or al,0xe0	   ; 设置7~4位为1110,表示lba模式
      mov dx,0x1f6
      out dx,al

;第3步:向0x1f7端口写入读命令,0x20 
      mov dx,0x1f7
      mov al,0x20                        
      out dx,al

;第4步:检测硬盘状态
  .not_ready:
      ;同一端口,写时表示写入命令字,读时表示读入硬盘状态
      nop
      in al,dx
      and al,0x88	   ;第4位为1表示硬盘控制器已准备好数据传输,第7位为1表示硬盘忙
      cmp al,0x08
      jnz .not_ready	   ;若未准备好,继续等。

;第5步:从0x1f0端口读数据
      mov ax, di
      mov dx, 256
      mul dx
      mov cx, ax	   ; di为要读取的扇区数,一个扇区有512字节,每次读入一个字,
			   ; 共需di*512/2次,所以di*256
      mov dx, 0x1f0
  .go_on_read:
      in ax,dx
      mov [bx],ax
      add bx,2		  
      loop .go_on_read
      ret

;   times 510-($-$$) db 0
;   db 0x55,0xaa

  loader.s


;--------------   gdt描述符属性  -----------
DESC_G_4K   equ	  1_00000000000000000000000b   
DESC_D_32   equ	   1_0000000000000000000000b
DESC_L	    equ	    0_000000000000000000000b	;  64位代码标记,此处标记为0便可。
DESC_AVL    equ	     0_00000000000000000000b	;  cpu不用此位,暂置为0  
DESC_LIMIT_CODE2  equ 1111_0000000000000000b
DESC_LIMIT_DATA2  equ DESC_LIMIT_CODE2
DESC_LIMIT_VIDEO2  equ 0000_000000000000000b
DESC_P	    equ		  1_000000000000000b
DESC_DPL_0  equ		   00_0000000000000b
DESC_DPL_1  equ		   01_0000000000000b
DESC_DPL_2  equ		   10_0000000000000b
DESC_DPL_3  equ		   11_0000000000000b
DESC_S_CODE equ		     1_000000000000b
DESC_S_DATA equ	  DESC_S_CODE
DESC_S_sys  equ		     0_000000000000b
;x=1,c=0,r=0,a=0 代码段是可执行的,非依从的,不可读的,已访问位a清0.
DESC_TYPE_CODE  equ	      1000_00000000b	  
;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写的,已访问位a清0.
DESC_TYPE_DATA  equ	      0010_00000000b	

DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00
DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00
DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0b
	
;--------------   选择子属性  ---------------
RPL0  equ   00b
RPL1  equ   01b
RPL2  equ   10b
RPL3  equ   11b
TI_GDT	 equ   000b
TI_LDT	 equ   100b
;--------------------------------------------
section loader vstart=0x900

   jmp loader_start
   
;构建gdt及其内部的描述符===============================================
   GDT_BASE:   dd    0x00000000 
	       	   dd    0x00000000

   CODE_DESC:  dd    0x0000FFFF 
	       	   dd    DESC_CODE_HIGH4

   DATA_STACK_DESC:  dd    0x0000FFFF
		     		 dd    DESC_DATA_HIGH4

   VIDEO_DESC: dd    0x80000007	       ; limit=(0xbffff-0xb8000)/4k=0x7
	           dd    DESC_VIDEO_HIGH4  ; 此时dpl为0

   GDT_SIZE    equ   $ - GDT_BASE
   GDT_LIMIT   equ   GDT_SIZE -	1 
   times 60 dq 0					 ; 此处预留60个描述符的空位(slot)
   SELECTOR_CODE  equ (0x0001<<3) + TI_GDT + RPL0     ; 8[1000] 相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0
   SELECTOR_DATA  equ (0x0002<<3) + TI_GDT + RPL0	 ; 16[10000] 同上
   SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0	 ; 24[11000] 同上 


   ;以下是定义gdt的指针,前2字节是gdt界限,后4字节是gdt起始地址
   gdt_ptr  dw  GDT_LIMIT 
	    	dd  GDT_BASE
;======================================================================
			
loader_start:

;-----------------   准备进入保护模式   -------------------
;1 打开A20
;2 加载gdt
;3 将cr0的pe位置1

   ;-----------------  打开A20  ----------------
   in al,0x92
   or al,0000_0010B
   out 0x92,al

   ;-----------------  加载GDT  ----------------
   lgdt [gdt_ptr]

   ;-----------------  cr0第0位置1  ----------------
   mov eax, cr0
   or eax, 0x00000001
   mov cr0, eax

   jmp dword SELECTOR_CODE:p_mode_start	     ; 刷新流水线,避免分支预测的影响,这种cpu优化策略,最怕jmp跳转,
					     ; 这将导致之前做的预测失效,从而起到了刷新的作用。

[bits 32]
p_mode_start:
   mov ax, SELECTOR_DATA
   mov ds, ax
   mov es, ax
   mov ss, ax
   mov esp,0x900
   mov ax, SELECTOR_VIDEO
   mov gs, ax
	
   ;160=80个字符[80*2]
   mov byte [gs:160*8], 'J'
   mov byte [gs:160*8+1],0x02
   
   mov byte [gs:160*8+2], 'a'
   mov byte [gs:160*8+3],0x02
   
   mov byte [gs:160*8+4], 'd'
   mov byte [gs:160*8+5],0x02
   
   mov byte [gs:160*8+6], 'e'
   mov byte [gs:160*8+7],0x02
   
   mov byte [gs:160*8+8], ' '
   mov byte [gs:160*8+9],0x00
   
   mov byte [gs:160*8+10], 'O'
   mov byte [gs:160*8+11],0x02
   
   mov byte [gs:160*8+12], 'S'
   mov byte [gs:160*8+13],0x02

   jmp $

程序说明图:

(1)是将硬盘中的MBR加载到内存0X7C00开始的位置,由BIOS完成

(2)mbr.s中将loader加载到内存0X900开始的位置

    执行流程 BIOS[CS:IP 0XFFF0:FFF0]--》CS:IP[0:0X7C00]----(MBR将loader加载到0X900)--->CS:IP[0:0X900]---(loader中)

最后进行汇编编译 

        nasm mbr.S -o mbr                  编译产生mbr文件
        nasm loader.S -o loader          编译产生loader文件

       编译后产生的两个文件放入C++工程中,在用上节中的C++代码进行编译结果生成boot.img文件,最后直接用Bochs进行测试,前面章节都有介绍如何操作,就不做讲解了!

实验结果:

 

看看GDT中都有哪些数据

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值