博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
自制操作系统Antz(5)——深入理解保护模式与进入方法
阅读量:4679 次
发布时间:2019-06-09

本文共 9014 字,大约阅读时间需要 30 分钟。

Antz系统更新地址: 

Linux内核源码分析地址:

 

  在前几天的任务中,我们已经简单实现了MBR,直接操作显示器和硬盘操作来加载其他扇区的程序,我们这些任务都是为了进入保护模式做准备,虽然我们已经给出了jmp到保护模式的方法,但是我们还是需要理解保护模式下的一些特性,才能更好的实现我们操作系统的功能。


 

0 .为什么要有保护模式

  以下是实模式的不足。

  1)操作系统和用户程序属于同一特权级。

  2)用户程序所引用的地址都是指向真实的物理地址,也就是说逻辑地址等于物理地址。

  3)用户程序可以自由修改段基址,可以访问任意内存。

  4)访问超过64KB的内存区域时要切换段基址。

  5)一次只能运行一个程序,无法充分利用计算机资源。

  6)共20跟地址线,最大可用内存为1MB。

  1~3是安全缺陷,4~5是使用方面的缺陷,第6条简直就是不能忍受的硬伤,1MB内存真的太束缚手脚了。

  后来为了解决这些问题,厂商开发处保护模式。这时,物理内存地址不能被程序直接访问,程序内部的地址(虚拟地址)需要被转换为物理地址后再去访问,程序对此一无所知。而且地址的转换时由处理器和操作系统共同协作完成的,处理器在硬件上提供地址转换部件,操作系统提供转换过程需要的页表。


 

1 .保护模式的寄存器扩展

  计算机的发展必须遵守兼容的特点,CPU发展到32位之后,地址总线和数据总线也发展到了32位,寻址空间更是达到了4GB。寻址空间大了,但寻址方式还是得兼容老方法,就是“段基址:偏移地址”,如果还是16位的话,不能承受4GB寻址的重任,所以寄存器也得跟上。为了让一个寄存器就可以寻址4GB空间,寄存器扩展到了32位。除了段寄存器,其他寄存器均扩展到了32位,因为段寄存器16位就够用了。

 

  寄存器的低16位都是为了兼容模式,高16位无法单独使用,只能在用32位寄存器时才可以用到。

  偏移地址还是和实模式下一样,但段基址为了安全,在其内添加了约束信息,这些约束信息就是内存段的描述信息,由于这些信息在寄存器中放不下,所以用了一个专门的数据结构——全局描述符表。其中有表项,用来描述各个内存段的起始地址,大小,权限等信息,每个表项大小是64字节,因为全局描述符表很大,只能放在内存中,由寄存器指向它。

  至此,段寄存器中再也不是段基址了,里面保存的叫做选择子(selector) ,它是一个数,用来索引全局描述符表中的段描述符,把全局描述符表当作数组,选择子就像是下标。

  段描述符是放在内存中的,访问内存对于CPU而言效率不高,而且段描述符的格式很奇怪,一个数据要分三个地方存,所以CPU把这些数组合并成一个完整数据也是需要花时间的。既然如此花费时间,在保护模式中,CPU为了提高效率,采取了对段寄存器的缓存技术,将段信息用一个寄存器来存储,这就是段描述符缓冲寄存器(对程序员不可见)。在获得一个段描述符之后,以后访问相同段时,会直接访问该寄存器。

  下面是三种段描述符寄存器的结构:

      


 

2 .保护模式的运行模式反转

  保护模式如何分辨16位和32位指令和操作数呢?

  汇编产生的机器码机器并不能识别是运行在16位还是32位系统下,在编译时可以通过[bits 16]和[bits 32]来确定编译器将代码编译为多少位的机器码。

  [bis]是伪指令,编译并无具体机器码,那么在编译之后机器如何识别呢?  这里引入前缀,在指令前加入前缀指令重复前缀rep,段跨越前缀"段寄存器",还有操作数反转前缀0x66,寻址方式反转前缀0x67。

行号 指令 机器码
1 [bits 16] 伪指令
2 mov ax,0x1234 B83412
3 mov eax,0x1234 66B834120000
4 [bits 32] 伪指令
5 mov ax,0x1234 66B83412
6 mov eax,0x1234 B834120000

   如果32位的代码被编译为16位的代码就会在机器码前加入前缀。即一种模式下要用另一中模式的操作数大小,需要在指令前加入指令前缀0x66。

  以上是操作数大小的改变时的前缀,如果是寻址方式改变,则添加前缀0x67。


 

3 .全局描述符表概述

  上面我们已经提到过全局描述符表了,它可以当作一个数组,而段描述符就是这个数组的下标。其结构如下:

 

  段描述符是8字节的,专门用来描述一个内存段,8字节也就是64位,而且是连续的8个字节。

  保护模式下地址总线是32位,段基址需要32位地址表示,段界限用20位表示,不过这个段界限只是个单位量,它的单位要么是字节,要么是4KB,这是感觉描述符的G位来确定的。最终段的边界是此段界限值*单位,故段的大小要么是2的20次方1MB要么是2的32次方(4KB==2的12次方)4GB。

  这里说的1MB和4GB只是个范围,并不是具体的边界值。由于段界限只是个偏移量,是从0开始算,所以实际的段界限边界值等于(描述符中段界限+1)*(段界限的粒度大小:4KB或1) -1  。

  这个公式的意思就是表示有多少个4KB或1 。由于描述符中的段界限是从0起的,所以左边第1个括号中要加个1,表示实际数量,由于地址是从0开始的,所以最后减1 。

  内存访问需要用到“段基址:偏移地址”,段界限其实就是用来限制段内偏移地址的,段内偏移地址必须位于段内,否则CPU会抛异常,“段界限*单位”就是限定偏移地址的最值的。

  仔细观察上面段描述符,你会发现段界限属性被分为了两部分,32位的段基址属性居然被分为了三份,这是为了兼容性考虑的。

  

  段描述符的低32位分为了两部分,前16位来存储段的段界限的前0~15位,后16位存储段基址的0~15位。

  主要属性都是在段描述符的高32位,0~7位是段基址的16~23位,24~31位是段基址的24~31位,加上段描述符的低32位的0~15位,这下32位的基地址才算完整。

  8~11位的type属性,四位,用来指定本描述符的类型。一个段描述符在CPU眼中分为两类,要么是系统段,要么是数据段。这是感觉段描述符的S位决定的,它用来指示是否是系统段,CPU眼中,硬件运行需要的都是系统,软件需要的都是数据,S是0表示系统段,S是1是数据段。type字段是和S字段配合才能确定段描述符的确切类型,至有S确定了,type才有具体意义。

  再来看type字段,它用于表示内存段或门的子类型。

  这是type在S确定之后的意义,我们需要注意的是非系统段。

  段描述符的第13~14位就是DPL字段,即描述符特权符,这是保护模式提供的安全解决方案,将计算机世界分为不同等级。这两位可以代表四种特权级,分别是0~4,数字越小特权越大。

  段描述符的第15位的P字段,即段是否存在,如果段存在于内存中,P为1,否则P为0。P是由CPU检查的,如果为0,CPU将抛出异常。

  段描述符的第16~19位是段界限的第16~19位。这样段界限就齐全了。

  段描述符的第20位是AVL,这位是相对用户的,暂不用理会。

  段描述符的第21位是L,是用来检查是否是64位代码段,在我们32位CPU时,将其置0即可。

  段描述符的第22位是D/B字段,用来表示有效地址及操作数的大小。

  段描述符的第23位是G,用来指定段界限的大小,粒度。

  段描述符的第24~31位是段基址的24~31位,是段基址的最后8位。

 


 

4 .全局描述符表GDT及选择子

  一个段描述符只能用来定义一个内存段,代码段要占用一个段描述符,数据段和栈段等,多个内存段也要各自占用一个段描述符,这些描述符会放在全局描述符表中,也就是GDT,GDT是公用的,它位于内存中,需要专门的寄存器指向。这个寄存器就是GDTR,一个48位寄存器。

 

  gdtr不能直接用mov gdtr,xxxx的方式初始化,而是有专门的指令,就是lgdt。

  lgdt指令格式是: lgdt 48位内存数据

  这48位内存数据分为两部分,前16位是GDT以字节为单位的界限值,后32位是GDT的其实地址,由于GDT的大小是16位,所以范围是65536字节,每个描述符大小是8字节,所以一个GDT中有8192个段或门。

  在保护模式下,原本存在段寄存器的段基址,现在放在了段描述符中,而段寄存器中放入的是选择子,就是一个索引,在描述符表中索引描述符的索引。

  段寄存器是16位的,所以选择子也是16位的,在其低2位即0~1位,用来存储RPL,即请求特权级,可以表示四种特权。高13位,即3~15是索引部分,2的13次方是8192,故可以索引8192个段,正好吻合GDT的8192个段。

  下图是描述符表和内存段的关系,还有选择子的结构:

          

 


5 .进入保护模式

  1. 打开A20地址线

    打开A20Gate的方式极其简单,只需要将0x92端口的第一个位置置1就好了。

1 in al,0x922 or al,0000_0010B3 out 0x92,al

  2.保护模式的开关,CR0寄存器的PE位

    这是进入保护模式的最后一步,CR0寄存器的PE位置1。

    

 

    

    PE为0表示在实模式下运行,PE为1表示在保护模式运行。

1 mov eax,cr02 or eax,0x000000013 mov cr0,eax

 

    写入完毕,接下来可以让我们进入保护模式了!

boot.inc :

1 ;-------------     loader和kernel   ---------- 2  3 LOADER_BASE_ADDR equ 0x900  4 LOADER_START_SECTOR equ 0x2 5  6 ;--------------   gdt描述符属性  ------------- 7 DESC_G_4K   equ      1_00000000000000000000000b    8 DESC_D_32   equ       1_0000000000000000000000b 9 DESC_L        equ        0_000000000000000000000b    ;  64位代码标记,此处标记为0便可。10 DESC_AVL    equ         0_00000000000000000000b    ;  cpu不用此位,暂置为0  11 DESC_LIMIT_CODE2  equ 1111_0000000000000000b12 DESC_LIMIT_DATA2  equ DESC_LIMIT_CODE213 DESC_LIMIT_VIDEO2  equ 0000_000000000000000b14 DESC_P        equ          1_000000000000000b15 DESC_DPL_0  equ           00_0000000000000b16 DESC_DPL_1  equ           01_0000000000000b17 DESC_DPL_2  equ           10_0000000000000b18 DESC_DPL_3  equ           11_0000000000000b19 DESC_S_CODE equ             1_000000000000b20 DESC_S_DATA equ      DESC_S_CODE21 DESC_S_sys  equ             0_000000000000b22 DESC_TYPE_CODE  equ          1000_00000000b    ;x=1,c=0,r=0,a=0 代码段是可执行的,非依从的,不可读的,已访问位a清0.  23 DESC_TYPE_DATA  equ          0010_00000000b    ;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写的,已访问位a清0.24 25 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 + 0x0026 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 + 0x0027 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 + 0x0b28 29 ;--------------   选择子属性  ---------------30 RPL0  equ   00b31 RPL1  equ   01b32 RPL2  equ   10b33 RPL3  equ   11b34 TI_GDT     equ   000b35 TI_LDT     equ   100b

loader.asm:

1    %include "boot.inc" 2    section loader vstart=LOADER_BASE_ADDR 3    LOADER_STACK_TOP equ LOADER_BASE_ADDR 4    jmp loader_start                 5    GDT_BASE:   dd    0x00000000  6            dd    0x00000000 7  8    CODE_DESC:  dd    0x0000FFFF  9            dd    DESC_CODE_HIGH410 11    DATA_STACK_DESC:  dd    0x0000FFFF12              dd    DESC_DATA_HIGH413 14    VIDEO_DESC: dd    0x80000007           ;limit=(0xbffff-0xb8000)/4k=0x715            dd    DESC_VIDEO_HIGH4  16 17    GDT_SIZE   equ   $ - GDT_BASE18    GDT_LIMIT   equ   GDT_SIZE -    1 19    times 60 dq 020    SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0         21    SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0    22    SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0     23 24    gdt_ptr  dw  GDT_LIMIT 25         dd  GDT_BASE26    loadermsg db '2 loader in real.'27 28    loader_start:29 30    mov     sp, LOADER_BASE_ADDR31    mov     bp, loadermsg           32    mov     cx, 17            33    mov     ax, 0x1301        34    mov     bx, 0x001f         35    mov     dx, 0x1800        36    int     0x10                    37 38     in al,0x9239    or al,0000_0010B40    out 0x92,al41    lgdt [gdt_ptr]42    mov eax, cr043    or eax, 0x0000000144    mov cr0, eax45 46    ;jmp dword SELECTOR_CODE:p_mode_start47    jmp  SELECTOR_CODE:p_mode_start     48 49 [bits 32]50 p_mode_start:51    mov ax, SELECTOR_DATA52    mov ds, ax53    mov es, ax54    mov ss, ax55    mov esp,LOADER_STACK_TOP56    mov ax, SELECTOR_VIDEO57    mov gs, ax58 59    mov byte [gs:160], 'P'60 61    jmp $

Antz_mbr.asm:

1 %include "boot.inc" 2 SECTION MBR vstart=0x7c00 3    mov ax,cs 4    mov ds,ax 5    mov es,ax 6    mov ss,ax 7    mov fs,ax 8    mov sp,0x7c00 9    mov ax,0xb80010    mov gs,ax11 12    mov     ax, 0600h13    mov     bx, 0700h14    mov     cx, 0                15    mov     dx, 184fh        16    int     10h                    17 18    mov byte [gs:0x00],'1'19    mov byte [gs:0x01],0xA420 21    mov byte [gs:0x02],' '22    mov byte [gs:0x03],0xA423 24    mov byte [gs:0x04],'M'25    mov byte [gs:0x05],0xA4    26 27    mov byte [gs:0x06],'B'28    mov byte [gs:0x07],0xA429 30    mov byte [gs:0x08],'R'31    mov byte [gs:0x09],0xA432 33    mov eax,LOADER_START_SECTOR     34    mov bx,LOADER_BASE_ADDR      35    mov cx,4             36    call rd_disk_m_16         37 38    jmp LOADER_BASE_ADDR39 40 rd_disk_m_16:41 42       mov esi,eax      ;备份eax43       mov di,cx        44 45 46       mov dx,0x1f247       mov al,cl48       out dx,al           49 50       mov eax,esi       51 52       mov dx,0x1f353       out dx,al54 55 56       mov cl,857       shr eax,cl58       mov dx,0x1f459       out dx,al60 61       shr eax,cl62       mov dx,0x1f563       out dx,al64 65       shr eax,cl66       and al,0x0f      67       or al,0xe0      68       mov dx,0x1f669       out dx,al70 71       mov dx,0x1f772       mov al,0x2073       out dx,al74 75   .not_ready:76       nop77       in al,dx78       and al,0x88       79       cmp al,0x0880       jnz .not_ready     81 82       mov ax, di83       mov dx, 25684       mul dx85       mov cx, ax       86 87       mov dx, 0x1f088   .go_on_read:89       in ax,dx90       mov [bx],ax91       add bx,292       loop .go_on_read93       ret94 95    times 510-($-$$) db 096    db 0x55,0xaa

 

转载于:https://www.cnblogs.com/LexMoon/p/antz05.html

你可能感兴趣的文章