自制操作系统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

 

目录
相关文章
|
7月前
|
项目管理 Python
深入理解Python中的os.chdir()方法
`os.chdir()`是Python中用于改变当前工作目录的方法,简化文件和目录操作。语法为`os.chdir(path)`,`path`是目标目录路径。示例中展示了如何切换及检查工作目录。它常用于脚本执行、文件操作和多项目管理。注意目标目录必须存在,否则会抛出异常。相关方法有`os.getcwd()`获取当前目录和`os.path.join()`拼接路径。使用时结合异常处理可提升效率。参考[Python官方文档](https://docs.python.org/3/library/os.html)。
307 3
|
3月前
|
机器学习/深度学习 Dart 前端开发
移动应用与系统:构建现代数字生态的基石在当今这个高度数字化的社会中,移动应用与操作系统已成为我们日常生活不可或缺的一部分。它们不仅改变了我们的沟通方式,还重塑了我们的工作、学习和娱乐模式。本文将深入探讨移动应用开发的基础、移动操作系统的功能以及这两者如何共同塑造了我们的数字世界。
随着智能手机和平板电脑的普及,移动应用与系统的重要性日益凸显。它们不仅为用户提供了便捷的服务和丰富的功能,还为开发者提供了广阔的创新平台。本文将介绍移动应用开发的基本概念、技术栈以及最佳实践,并探讨主流移动操作系统的特点和发展趋势。通过分析移动应用与系统的相互作用,我们可以更好地理解它们在现代社会中的重要地位。
|
24天前
|
Python
文件元数据获取方法对比:`os.path` 与 `os.stat`
本文对比了Python中两种获取文件元数据的方法:`os.path`和`os.stat`。通过示例代码展示了如何获取文件大小和修改时间,并从性能、功能性和代码可读性三方面进行了详细对比。最终给出了根据具体需求选择合适方法的最佳实践建议。
25 2
|
1月前
|
监控 安全 程序员
探索操作系统的心脏:内核与用户模式
【10月更文挑战第41天】本文将带你进入操作系统的核心,揭示内核与用户模式之间的神秘面纱。我们将通过浅显易懂的语言和生动的比喻,让你轻松理解这一复杂主题。从内核的定义到它如何管理计算机资源,再到用户模式如何保障程序运行的安全性,你将获得一次深入浅出的知识之旅。让我们一起揭开操作系统的神秘面纱,探索它的奥秘!
|
2月前
|
存储 Java C语言
MacOS环境-手写操作系统-04-实模式进入保护模式
MacOS环境-手写操作系统-04-实模式进入保护模式
21 1
|
2月前
|
Shell Python
Python中os模块的常用方法和示例
在Python中,`os`模块提供了与操作系统交互的函数,用于文件和目录管理、路径操作、环境变量等。常用方法包括路径操作(如`os.path.join()`、`os.path.abspath()`)、文件和目录管理(如`os.mkdir()`、`os.remove()`)、环境变量和进程管理(如`os.getenv()`、`os.system()`)以及其他常用功能(如`os.getcwd()`、`os.urandom()`)。
38 0
|
3月前
|
安全
探索操作系统的心脏:内核与用户模式的交互之旅
【9月更文挑战第12天】在数字世界的海洋中,操作系统扮演着灯塔的角色,指引着每一条数据流的方向。本文将深入探讨操作系统的核心机制——内核与用户模式,揭示它们如何协同工作以保障计算机系统的高效与安全。我们将从基础概念出发,逐步深入到实际代码示例,旨在为读者呈现一幅清晰的操作系统工作原理图景。
|
2月前
|
存储 iOS开发 C++
MacOS环境-手写操作系统-05-保护模式超强寻址
MacOS环境-手写操作系统-05-保护模式超强寻址
31 0
|
3月前
|
安全
探索操作系统的心脏:内核与用户模式的奥秘
在数字世界的海洋中,操作系统如同一艘巨轮,承载着无数数据的流动。本文将揭开这艘巨轮的核心机密——内核与用户模式,带你领略它们如何协同工作,确保系统的稳定与安全。通过浅显易懂的语言和生动的比喻,我们将一探究竟,看看这两种模式如何在幕后默默支撑着我们的日常计算体验。准备好了吗?让我们启航,深入操作系统的心脏地带!
|
4月前
|
安全 调度 开发者
探索操作系统的心脏:内核与用户模式
【8月更文挑战第30天】在数字世界的每一次跳动中,操作系统扮演着至关重要的角色。本文将深入浅出地探讨操作系统的核心概念——内核与用户模式,通过生动的比喻和直观的解释,带领读者理解这一复杂主题。我们将从内核的定义和功能出发,逐步揭示用户模式的秘密,并通过代码示例,展示如何在实际应用中区分和利用这两种模式。无论你是计算机科学的初学者还是资深开发者,这篇文章都将为你打开一扇了解操作系统深层工作原理的大门。