自制操作系统Antz day05——深入理解保护模式与进入方法

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

  Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html

  Linux内核源码分析地址: https://www.cnblogs.com/LexMoon/category/1267413.html

  Github地址:https://github.com/CasterWx 


  在前几天的任务中,我们已经简单实现了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,0x92
2 or al,0000_0010B
3 out 0x92,al

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

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

    

 

    

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

1 mov eax,cr0
2 or eax,0x00000001
3 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_0000000000000000b
12 DESC_LIMIT_DATA2  equ DESC_LIMIT_CODE2
13 DESC_LIMIT_VIDEO2  equ 0000_000000000000000b
14 DESC_P        equ          1_000000000000000b
15 DESC_DPL_0  equ           00_0000000000000b
16 DESC_DPL_1  equ           01_0000000000000b
17 DESC_DPL_2  equ           10_0000000000000b
18 DESC_DPL_3  equ           11_0000000000000b
19 DESC_S_CODE equ             1_000000000000b
20 DESC_S_DATA equ      DESC_S_CODE
21 DESC_S_sys  equ             0_000000000000b
22 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 + 0x00
26 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
27 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
28 
29 ;--------------   选择子属性  ---------------
30 RPL0  equ   00b
31 RPL1  equ   01b
32 RPL2  equ   10b
33 RPL3  equ   11b
34 TI_GDT     equ   000b
35 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_HIGH4
10 
11    DATA_STACK_DESC:  dd    0x0000FFFF
12              dd    DESC_DATA_HIGH4
13 
14    VIDEO_DESC: dd    0x80000007           ;limit=(0xbffff-0xb8000)/4k=0x7
15            dd    DESC_VIDEO_HIGH4  
16 
17    GDT_SIZE   equ   $ - GDT_BASE
18    GDT_LIMIT   equ   GDT_SIZE -    1 
19    times 60 dq 0
20    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_BASE
26    loadermsg db '2 loader in real.'
27 
28    loader_start:
29 
30    mov     sp, LOADER_BASE_ADDR
31    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,0x92
39    or al,0000_0010B
40    out 0x92,al
41    lgdt [gdt_ptr]
42    mov eax, cr0
43    or eax, 0x00000001
44    mov cr0, eax
45 
46    ;jmp dword SELECTOR_CODE:p_mode_start
47    jmp  SELECTOR_CODE:p_mode_start     
48 
49 [bits 32]
50 p_mode_start:
51    mov ax, SELECTOR_DATA
52    mov ds, ax
53    mov es, ax
54    mov ss, ax
55    mov esp,LOADER_STACK_TOP
56    mov ax, SELECTOR_VIDEO
57    mov gs, ax
58 
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,0xb800
10    mov gs,ax
11 
12    mov     ax, 0600h
13    mov     bx, 0700h
14    mov     cx, 0                
15    mov     dx, 184fh        
16    int     10h                    
17 
18    mov byte [gs:0x00],'1'
19    mov byte [gs:0x01],0xA4
20 
21    mov byte [gs:0x02],' '
22    mov byte [gs:0x03],0xA4
23 
24    mov byte [gs:0x04],'M'
25    mov byte [gs:0x05],0xA4    
26 
27    mov byte [gs:0x06],'B'
28    mov byte [gs:0x07],0xA4
29 
30    mov byte [gs:0x08],'R'
31    mov byte [gs:0x09],0xA4
32 
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_ADDR
39 
40 rd_disk_m_16:
41 
42       mov esi,eax      ;备份eax
43       mov di,cx        
44 
45 
46       mov dx,0x1f2
47       mov al,cl
48       out dx,al           
49 
50       mov eax,esi       
51 
52       mov dx,0x1f3
53       out dx,al
54 
55 
56       mov cl,8
57       shr eax,cl
58       mov dx,0x1f4
59       out dx,al
60 
61       shr eax,cl
62       mov dx,0x1f5
63       out dx,al
64 
65       shr eax,cl
66       and al,0x0f      
67       or al,0xe0      
68       mov dx,0x1f6
69       out dx,al
70 
71       mov dx,0x1f7
72       mov al,0x20
73       out dx,al
74 
75   .not_ready:
76       nop
77       in al,dx
78       and al,0x88       
79       cmp al,0x08
80       jnz .not_ready     
81 
82       mov ax, di
83       mov dx, 256
84       mul dx
85       mov cx, ax       
86 
87       mov dx, 0x1f0
88   .go_on_read:
89       in ax,dx
90       mov [bx],ax
91       add bx,2
92       loop .go_on_read
93       ret
94 
95    times 510-($-$$) db 0
96    db 0x55,0xaa

 

目录
相关文章
|
2月前
|
存储 算法 Unix
操作系统复习篇一分大小单双方式方法
用户程序访问操作系统的程序和数据 • 地址映射:逻辑地址转换为物理地址 • 内存扩充(虚拟存储技术):请求调入功能、置换功能 设备管理功能一分大小单双方式方法 《罔 F C W 2 3 ●VIP》【企鹅Q1344-687】 • 缓冲管理:缓冲区机制 • 设备分配 • 设备处理:设备驱动程序 主要任务:完成I/O请求、提高CPU和I/O设备的使用率 文件管理功能 • 文件存储空间的管理 • 目录管理:按名存取 • 文件的读/写管理和保护:文件的读/写管理、文件保护
|
8月前
|
Unix API 调度
【python】 一文了解 OS 操作系统交互库简单使用方法
Python的os模块(Operating System Interface)提供了访问操作系统相关功能的接口,它允许程序与底层操作系统交互,对文件系统、进程、套接字等底层操作进行控制。典型的用法是程序打开某个文件时,os库可以帮助我们判断文件是否存在/是否可读/是否可写等等,同时也可以帮助我们创建和删除目录、文件链接等等。os库提供了一系列的函数和方法,用于处理和管理操作系统文件、文件夹,以及运行和控制进程,具体包括以下功能:对文件和目录(文件夹)的操作:包括创建、重命名、删除等等。
110 0
|
4月前
|
Ubuntu Linux 开发工具
Linux操作系统Ubuntu 22.04配置Visual Studio Code与C++代码开发环境的方法
Linux操作系统Ubuntu 22.04配置Visual Studio Code与C++代码开发环境的方法
126 0
|
4月前
|
存储 Ubuntu Linux
虚拟机VMware Workstation Pro中配置Linux操作系统Ubuntu的方法
虚拟机VMware Workstation Pro中配置Linux操作系统Ubuntu的方法
|
7月前
|
Ubuntu Linux 数据安全/隐私保护
安装Ubuntu Linux 操作系统(以Ubuntu20.04为例)方法1
安装Ubuntu Linux 操作系统(以Ubuntu20.04为例)方法1
365 1
|
10月前
|
Python
26.从入门到精通:Python3 OS 文件/目录方法 Python3 错误和异常 语法错误 异常
26.从入门到精通:Python3 OS 文件/目录方法 Python3 错误和异常 语法错误 异常
|
弹性计算 Ubuntu Linux
Ubuntu操作系统配置阿里云镜像方法一
Ubuntu操作系统配置阿里云镜像方法一
Ubuntu操作系统配置阿里云镜像方法一
|
12月前
|
存储
使用树莓派简单快速的烧录操作系统(含两个方法)
使用树莓派简单快速的烧录操作系统(含两个方法)
406 0
|
Unix 关系型数据库 API
|
弹性计算 Ubuntu Unix
阿里云服务器操作系统选择方法
阿里云服务器操作系统有哪些?选择哪个系统比较好?镜像Alibaba Cloud Linux、Anolis OS、CentOS、Windows Server、Red Hat、SUSE Linux、Ubuntu、Debian、Fedora、Fedora CoreOS、OpenSUSE、FreeBSD、Rocky Linux、CentOS Stream和AlmaLinux
192 0
阿里云服务器操作系统选择方法