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

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

  在前几天的任务中,我们已经简单实现了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就好了。

 in al,0x92
or al,0000_0010B
out 0x92,al

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

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

    

    

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

 mov eax,cr0
or eax,0x00000001
mov cr0,eax

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

boot.inc :

 ;-------------     loader和kernel   ----------

 LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2 ;-------------- 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
DESC_TYPE_CODE equ 1000_00000000b ;x=1,c=0,r=0,a=0 代码段是可执行的,非依从的,不可读的,已访问位a清0.
DESC_TYPE_DATA equ 0010_00000000b ;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写的,已访问位a清0. DESC_CODE_HIGH4 equ (0x00 << ) + 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 << ) + 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 << ) + 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

loader.asm:

    %include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp loader_start
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 GDT_SIZE equ $ - GDT_BASE
GDT_LIMIT equ GDT_SIZE -
times dq
SELECTOR_CODE equ (0x0001<<) + TI_GDT + RPL0
SELECTOR_DATA equ (0x0002<<) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003<<) + TI_GDT + RPL0 gdt_ptr dw GDT_LIMIT
dd GDT_BASE
loadermsg db '2 loader in real.' loader_start: mov sp, LOADER_BASE_ADDR
mov bp, loadermsg
mov cx,
mov ax, 0x1301
mov bx, 0x001f
mov dx, 0x1800
int 0x10 in al,0x92
or al,0000_0010B
out 0x92,al
lgdt [gdt_ptr]
mov eax, cr0
or eax, 0x00000001
mov cr0, eax ;jmp dword SELECTOR_CODE:p_mode_start
jmp SELECTOR_CODE:p_mode_start [bits ]
p_mode_start:
mov ax, SELECTOR_DATA
mov ds, ax
mov es, ax
mov ss, ax
mov esp,LOADER_STACK_TOP
mov ax, SELECTOR_VIDEO
mov gs, ax mov byte [gs:], 'P' jmp $

Antz_mbr.asm:

 %include "boot.inc"
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 mov ax, 0600h
mov bx, 0700h
mov cx,
mov dx, 184fh
int 10h mov byte [gs:0x00],''
mov byte [gs:0x01],0xA4 mov byte [gs:0x02],' '
mov byte [gs:0x03],0xA4 mov byte [gs:0x04],'M'
mov byte [gs:0x05],0xA4 mov byte [gs:0x06],'B'
mov byte [gs:0x07],0xA4 mov byte [gs:0x08],'R'
mov byte [gs:0x09],0xA4 mov eax,LOADER_START_SECTOR
mov bx,LOADER_BASE_ADDR
mov cx,
call rd_disk_m_16 jmp LOADER_BASE_ADDR rd_disk_m_16: mov esi,eax ;备份eax
mov di,cx mov dx,0x1f2
mov al,cl
out dx,al mov eax,esi mov dx,0x1f3
out dx,al mov cl,
shr eax,cl
mov dx,0x1f4
out dx,al shr eax,cl
mov dx,0x1f5
out dx,al shr eax,cl
and al,0x0f
or al,0xe0
mov dx,0x1f6
out dx,al mov dx,0x1f7
mov al,0x20
out dx,al .not_ready:
nop
in al,dx
and al,0x88
cmp al,0x08
jnz .not_ready mov ax, di
mov dx,
mul dx
mov cx, ax mov dx, 0x1f0
.go_on_read:
in ax,dx
mov [bx],ax
add bx,
loop .go_on_read
ret times -($-$$) db
db 0x55,0xaa

自制操作系统Antz(5)——深入理解保护模式与进入方法的更多相关文章

  1. 自制操作系统Antz -- 系列文章

    自制操作系统Antz day10——实现shell(上) AntzUhl 2018-10-10 16:25 阅读:192 评论:0   Linux内核源码分析 day01——内存寻址 AntzUhl ...

  2. Linux操作系统基础(四)保护模式内存管理(2)【转】

    转自:http://blog.csdn.net/rosetta/article/details/8570681 Linux操作系统基础(四)保护模式内存管理(2) 转载请注明出处:http://blo ...

  3. 自制操作系统Antz(2)——进入保护模式 (上) jmp到保护模式

    Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.htm Linux内核源码分析地址:https://www.cnblogs.c ...

  4. 自制操作系统Antz(3)——进入保护模式 (中) 直接操作显存

    Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html Linux内核源码分析地址:https://www.cnblogs. ...

  5. 自制操作系统Antz(9)——实现内核 (下) 实现图形化界面

    Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html Linux内核源码分析地址:https://www.cnblogs. ...

  6. 自制操作系统Antz(6)——内核初步,引入c语言

    Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html Linux内核源码分析地址:https://www.cnblogs. ...

  7. 自制操作系统Antz(13) 显示图片

    显示图片只是在多媒体课上看着bmp格式图片的突发奇想,然后就实现在了我自己的操作系统 Antz系统更新地址 Linux内核源码分析地址 Github项目地址 效果图: 显示图片的原理 在之前显卡操作时 ...

  8. 自制操作系统Antz(8)——实现内核 (中) 扩展内核

    Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html 在前几天的任务中,我们已经简单实现了MBR,直接操作显示器和硬盘操作 ...

  9. 自制操作系统Antz(7)——实现内核 (上)

    Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html Linux内核源码分析地址:https://www.cnblogs. ...

随机推荐

  1. 【转载】C++中替代sprintf的std::ostringstream输出流详解

    一.简单介绍 ostringstream是C++的一个字符集操作模板类,定义在sstream.h头文件中.ostringstream类通常用于执行C风格的串流的输出操作,格式化字符串,避免申请大量的缓 ...

  2. SharePoint Server 2019新特性

    .基于.NET4.7框架 功能基本是把office365的SPO功能搬家过来.如下: Sharepoint server 2019将在2018年发布 New web parts There were ...

  3. springmvc中为我们做了什么

    这应该是每个使用框架的人应该自问的.这就要从没有使用框架时说.在没用框架开发web应用时,自己是怎么开发的,就是写servlet,jsp. 使用springmvc后,使用Controller注解,其实 ...

  4. 静态方法(staticmethod)和类方法(classmethod)

    类方法:有个默认参数cls,并且可以直接用类名去调用,可以与类属性交互(也就是可以使用类属性) 静态方法:让类里的方法直接被类调用,就像正常调用函数一样 类方法和静态方法的相同点:都可以直接被类调用, ...

  5. java Api 读取HDFS文件内容

    package dao; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.*; import java ...

  6. SQLSERVER 检查内容

    巡检内容: 1系统信息A. 机器名称:B. 硬件配置:Intel(R) CPU E5-2630 2.3GHz(2处理器),24核,16G内存C. 操作系统版本:Windows Server 2008 ...

  7. vim快捷键与vi

    vim与程序员 所有的 Unix Like 系统都会内建 vi 文书编辑器,其他的文书编辑器则不一定会存在. 但是目前我们使用比较多的是 vim 编辑器. vim 具有程序编辑的能力,可以主动的以字体 ...

  8. 黏包:传输过程中 read(不可靠)传输时由于网络造成黏包

    但是你在读取本地文件  不涉及传输文件时   read还是可靠的

  9. 在Mac上 python中使用tesseract OCR (Pytesser) 识别图片中的文字

    仓库地址:https://github.com/RobinDavid/Pytesser brew install tesseract sudo pip install opencv-python 安装 ...

  10. cocos2dx JS 图片精灵添加纹理缓存

    添加精灵图片缓存 : cc.spriteFrameCache.addSpriteFrames("res/pic.plist"); 从缓存中获取 : var frame = cc.s ...