精华内容
下载资源
问答
  • 保护模式
    万次阅读 多人点赞
    2018-09-23 13:11:02

           从80386开始,CPU有三种工作模式:实模式、保护模式和虚拟8086模式。80286开始的CPU引入保护模式,实际上,实模式概念是在保护模式推出之后为了区别保护模式之前的8086CPU工作模式才有的,在8086时代CPU工作模式只有一种,自然没有实模式之说。实模式有着先天的缺陷(下文详述),但出于向上兼容的考虑,现代CPU仍然保持着对16位操纵模式的兼容。

           所谓工作模式,是指CPU的寻址方式、寄存器大小、指令用法和内存布局等。

    实模式

           实模式的“实”体现在程序中用到的地址都是真实的物理地址,“段基址:段内偏移地址”产生的逻辑地址就是物理地址,即程序员可见的地址完全是真实的内存地址。

            在实模式下,内存寻址方式和8086相同,由16位段寄存器的内容乘以16(左移4位)作为段基址,加上16位段偏移地址形成20位的物理地址,最大寻址空间1MB,最大分段64KB。可以使用32位指令,即32位的x86 CPU也可以兼容实模式,此时的实模式相当于高速的8086(32位CPU的实模式可以使用32位下的资源)。在32位CPU下,系统复位或加电时都是以实模式启动,然后再切换为保护模式。在实模式下,所有的段都是可以读、写和可执行的。

           下图是实模式下的内存访问模型:

           8086CPU的实模式开创性地提出了地址分段的概念,改变了在它之前的CPU只能“硬编码”,程序无法重定位的缺点。然而实模式还是有很多缺陷,其中最主要的是实模式的安全隐患。在实模式下,用户程序和操作系统拥有同等权利,因为实模式下没有特权级。此外,程序可以随意修改自己的段基址,加上实模式下对地址的访问就是实实在在的物理地址,因此程序可以随意修改任意物理地址,甚至包括操作系统所在的内存,这给操作系统带来极大的安全问题。

    保护模式

           尽管在Intel 80286手册中已经提出了保护模式,但实际上它只是一个指引。80286虽然有了保护模式但其依然是16位的CPU,其通用寄存器还是16位宽,只不过其地址线由20位变成了24位,即寻址空间扩大到了16MB(但受限于寄存器位宽,单个寄存器的寻址空间仍然为64KB)。80286只是一个“过渡”产品,很快就被淘汰。

           真正的32位地址出现在Intel 80386上,它的地址总线和寄存器都是32位的,因此其单寄存器的寻址空间扩大到了4GB——在当时甚至其后的数年,仅通过段内偏移地址都足以访问内存的任意角落,这也开启了“平坦模型”的时代。

           保护模式本身是80286及以后的x86系列处理器产生的一种操作模式,它具有许多特性设计为提高系统的多道任务和系统的安全性及稳定性——例如内存的保护,分页机制和硬件虚拟存储的支持。现代多数的x86处理器操作系统都运行在保护模式下,包括Linux, Free BSD, 和Windows3.0(它也运行在实模式下,为了和Windows 2.x应用程序兼容)及以后的版本。 

           在保护模式中,内存的管理模式分为两种——段模式和页模式。其中页模式也是基于段模式的。也就是说,保护模式的内存管理模式事实上是:纯段模式和段页式。进一步说,段模式是必不可少的,而页模式则是可选的——如果使用页模式,则是段页式,否则这是纯段模式。

           为了改进实模式下内存访问的不安全性,保护模式给内存段添加了段属性来限制用户程序对内存的操作权限。保护模式引入了全局描述符表(Global Descriptor Table,GDT),GDT的表项是描述段类型属性的数据结构——段描述符。GDT中的每一个段描述符都描述了一个内存段的基本属性,如段基址、段界限、类型、DPL等等。

           正是由于以上概念的提出,使得“段地址:段内偏移地址”的访问策略从实模式下对物理地址的直接映射变成了保护模式下对GDT或LDT的间接映射(如下图所示),进程在访问内存段(无论是数据段还是代码段)前都需要通过特权级检查。段属性的加入让用户程序对内存的访问不再“为所欲为”。

    更多相关内容
  • x86汇编语言 从实模式到保护模式完整版.pdf
  • x86汇编语言从实模式到保护模式,包括后三章,网上大部分缺少后三章。文件太大包内是网盘地址和提取码。之前的过期,已更新。
  • x86汇编语言:从实模式到保护模式 基本信息 作者: 李忠 王晓波 余洁 出版社:电子工业出版社 ISBN:9787121187995 上架时间:2013-1-10 出版日期:2013 年1月 开本:16开 页码:375 版次:1-1
  • 《x86汇编语言:从实模式到保护模式》主要讲述INTEL x86处理器的16位实模式、32位保护模式,至于虚拟8086模式,则是为了兼容传统的8086程序,现在看来已经完全过时,不再进行讲述。《x86汇编语言:从实模式到保护模式...
  • 保护模式下的80386及其编程 pdf,扫描版的
  • x86汇编语言++从实模式到保护模式 x86汇编语言++从实模式到保护模式
  • x86汇编语言 从实模式到保护模式完整版,是完整版的,不是样章
  • x86汇编语言从实模式到保护模式- 光盘
  • 从实模式到保护模式

    千次阅读 2019-04-13 16:25:29
    这节就实战编写loader实现从实模式到保护模式再返回实模式 目录 1、从实模式到保护模式 1、从计算机的历史谈起 2、CPU历史的里程碑 - 8086 3、80286的登场 4、初识保护模式 5、80386的登场(计算机新时期的...

    上节Boot成功加载loader到内存并且将控制权交给他,突破了512字节的限制,loader程序没有体积上的限制

    这节就实战编写loader实现从实模式到保护模式再返回实模式

    目录

    1、从实模式到保护模式

    1、从计算机的历史谈起

    2、CPU历史的里程碑 - 8086

    3、80286的登场

    4、初识保护模式

    5、80386的登场(计算机新时期的标志)

    4、编程实验

    2、深入保护模式

    1、显存段、数据段和栈段

    2、编程实验

    2、从保护模式返回实模式

    3、局部段描述符表的使用

    4、初探保护模式的保护机制



    1、从实模式到保护模式

    1、从计算机的历史谈起

    远古时期的程序开发:直接操作物理内存

    CPU指令的操作数直接使用实地址(实际内存地址)

    程序员拥有绝对的权利(利用CPU指哪打哪)

     

    绝对的权利带来的问题

         - 难以重定位:程序每次都需要同样地址的内存执行

         - 给多道程序设计带来障碍:不管内存多大,但凡一个字节被其它程序占用都无法执行

     

    2、CPU历史的里程碑 - 8086

    地址线宽度为20位,可访问1M内存空间(0~0xFFFFF)

    引入[段地址 : 偏移地址]的内存访问方式

         - 段地址左移4位,构成20位的基地址(起始地址):基地址+偏移地址=实地址(由地址加法器完成)

         - 8086的段寄存器和通用寄存器为16位

         - 单个寄存器寻址最多访问64K的内存空间

         - 需要两个寄存器配合,完成所有内存空间的访问

    对于开发者的意义:

         - 更有效的划分内存的功能(数据段,代码段,等)

         - 当出现程序地址冲突时,通过修改段地址解决冲突(即书上所说的重定位寄存器)

    mov ax, [0x1234] ;根据默认段地址ds和给出的偏移地址得出实地址: (ds<<4)+ 0x1234
    mov ax, [es:0x1234] ;实地址:(es<<4)+ 0x1234

    字面上[段地址:偏移地址]能访问的最大地址为0xFFFF:0XFFFF,即:10FFEF;超过了1MB的空间,CPU如何处理?

    答案:不处理,而超出的部分我们称呼为高端地址区

    8086中的高端地址区(High Memory Area

    再谈8086历史

         - 8086在当时是非常成功的一款产品

         - 因此,拥有一大批的开发者和应用程序

         - 各种基于8086程序设计的技术得到了发展

         - 不幸的是,各种奇淫技巧也应运而生...

    8086时期应用程序中的问题

         - 1MB内存完全不够用(内存在任何时期都不够用)

         - 开发者在程序中大量使用内存回卷技术(HMA地址被使用)

         - 应用程序之间没有界限,相互之间随意干扰

               ①A程序可以随意访问B程序中的数据

               ②C程序可以修改系统调度程序的指令

     

    8086程序中问题的本质是什么?如果是你,准备如何解决?

    安全性!!!没有考虑内存保护,所有内存想读就读,想写就写...

     

    3、80286的登场

    8086已经有那么多应用程序了,所以必须兼容再兼容

    加大内存容量,增加地址线数量(24位),可访问16M内存(0~FFFFFF)

    [段地址 : 偏移地址]的方式可以强化一下

         - 为每个段提供更多属性(如:范围,特权级,等)

         - 为每个段的定义提供固定方式

     

    80286的兼容性

         - 默认情况下完全兼容8086的运行方式(实模式

              ★ 默认可直接访问1MB的内存空间(即便使用回卷技术的程序依旧可以正常运行)

              ★ 通过特殊的方式访问1MB+的内存空间

     

    这个特殊的方式指的是80286之后的工作模式:保护模式

     

    4、初识保护模式

    每一段内存的拥有一个属性定义(描述符 Descriptor

    所有段的属性定义构成一张表(描述符表 Descriptor Table

    段寄存器保存的是属性定义在表中的索引(选择子 Selector

     

    描述符(Descriptor)的内存结构

    可以看到一个描述符在内存中占8字节。

    段基址就是描述内存段的起始地址,分三部分存放,这是由于80386在80286基础上改进,硬件会自己拼装它们。

    段界限就指出了段内偏移地址的最大值。

    其它段属性后续用到时介绍

     

    描述符表(Descriptor Table)

    描述符表放在内存是数组的结构,每一个元素都是一个描述符,下标从0开始,索引号依次递增

    描述符占8个字节所以在表中的偏移地址是索引号 * 8

    最主要的描述符表是全局描述符表(GDT),处理器内部有一个48位的寄存器称为全局描述符表寄存器(GDTR)高32位存放描述符表地址,低16位存放表的界限。可以通过lgdt指令将GDT的入口地址装入此寄存器

     

    选择子(Selector)的结构

    索引号:3-15位保存着段描述符在段描述符表的位置

    RPL:请求特权级标识,通过特权级判断是否可以访问对应段,有四个值0~3(详细后续介绍)

    TI:描述符表指示器,TI=0,表示描述符在GDT(全局段描述符表);TI=1,表示描述符在LDT(局部段描述符表)。

           给定一个选择子通过TI位就能知道在哪个表中找描述符

     

    选择子放在段寄存器中(在保护模式下叫段选择器),从80286开始每个段寄存器都配有段描述符高速缓冲寄存器。

     

    进入保护模式的方式

       1. 定义描述符表

       2. 打开A20地址线(从0x92端口读数据,将其第2位置1,再写入该端口)

       3. 加载描述表(lgdt,将描述符表的地址和长度放入一个48位寄存器)

       4. 通知CPU进入保护模式(CR0寄存器的第1位置为1即可)

    注解:CR0,CR1....是处理器内部的控制寄存器(80286叫MSW寄存器),CR0是32寄存器,包含一系列用于控制处理器操作模式和运行状态的标志位,它的第1位是保护模式允许位,为1则处理器进入保护模式,其他位后续若用到讲解

                                       

    80286的光荣退场

         - 历史意义

                  引入了保护模式,为现代操作系统和应用程序奠定了基础

         - 奇葩设计

                  段寄存器为24位,通用寄存器为16位(不伦不类)

                  ✦ 理论上,段寄存器中的数值可以直接作为段基址

    (实模式下段寄存器只使用16位,保护模式下段寄存器保存16位的选择子,24位明显多余)

                  ✦ 16位通用寄存器最多访问64K的内存,为了访问16M内存,必须不停切换段基址

    小结

        - [段地址:偏移地址]的寻址方式解决了早期程序重定位难的问题

        - 8086实模式下的程序无法保证安全性

        - 80286中提出了保护模式,加强了内存段的安全性

        - 出于兼容性的考虑,80286之后的处理器都有2种工作模式

        - 处理器需要特定的设置步骤才能进入保护模式,默认为实模式

     

    5、80386的登场(计算机新时期的标志)

    32位地址总线(可支持4G的内存空间)

    段寄存器和通用寄存器都为32位

         - 任何一个寄存器都能访问到内存的任意角落

                  ✦ 开启了平坦内存模式的新时代

                  ✦ 段基址为0,使用通用寄存器访问4G内存空间

    新时期的内存使用方式

         - 实模式

                  ✦ 兼容8086的内存使用方式(指哪打哪)

         - 分段模式

                  ✦ 通过[段地址 : 偏移地址]的方式将内存从功能上分段(数据段,代码段)

         - 平坦模式

                  ✦ 所有内存就是一个段[0:32位偏移地址]

     

    段属性定义

    选择子属性定义

     

    保护模式中的段定义

    汇编小贴士

         - section 关键字用于"逻辑的"定义一段代码集合

         - section 定义的代码段不同于[段地址 : 偏移地址]的代码段

               ✦ section定义的代码段仅限于源码中的代码段(代码节

               ✦ [段地址 : 偏移地址]的代码段指内存中的代码段

         - [bits 16]:用于指示编译器将代码按照16位方式进行编译

         - [bits 32]:用于指示编译器将代码按照32位方式进行编译

     

    注意事项

         - 全局段描述表中的第0个描述符不使用(仅用于占位)

         - 代码中必须显示的指明16位代码段和32位代码段

         - 必须使用jmp指令从16位代码段跳转到32位代码段

     

    4、编程实验

    inc.asm

    
    ; Segment Attribute
    DA_32    equ    0x4000
    DA_DR    equ    0x90
    DA_DRW   equ    0x92
    DA_DRWA  equ    0x93
    DA_C     equ    0x98
    DA_CR    equ    0x9A
    DA_CCO   equ    0x9C
    DA_CCOR  equ    0x9E
    
    ; Selector Attribute
    SA_RPL0    equ    0
    SA_RPL1    equ    1
    SA_RPL2    equ    2
    SA_RPL3    equ    3
    
    SA_TIG    equ    0
    SA_TIL    equ    4
    
    ; 描述符
    ; usage: Descriptor Base, Limit, Attr
    ;        Base:  dd
    ;        Limit: dd (low 20 bits available)
    ;        Attr:  dw (lower 4 bits of higher byte are always 0)
    %macro Descriptor 3	                          ; 段基址, 段界限, 段属性
        dw    %2 & 0xFFFF                         ; 段界限1
        dw    %1 & 0xFFFF                         ; 段基址1
        db    (%1 >> 16) & 0xFF                   ; 段基址2
        dw    ((%2 >> 8) & 0xF00) | (%3 & 0xF0FF) ; 属性1 + 段界限2 + 属性2
        db    (%1 >> 24) & 0xFF                   ; 段基址3
    %endmacro                                     ; 共 8 字节
    
    

    loader.asm

    %include "inc.asm"
    
    org 0x9000
    
    jmp CODE16_SEGMENT
    
    [section .gdt]
    ; GDT definition
    ;                                 段基址,       段界限,       段属性
    GDT_ENTRY       :     Descriptor    0,            0,           0             ;第一个段描述符只用于占位
    CODE32_DESC     :     Descriptor    0,    Code32SegLen  - 1,   DA_C + DA_32  ;32位保护模式代码段描述符
    ; GDT end
    
    GdtLen    equ   $ - GDT_ENTRY
    
    GdtPtr:
              dw   GdtLen - 1
              dd   0
              
              
    ; GDT Selector
    
    Code32Selector    equ (0x0001 << 3) + SA_TIG + SA_RPL0
    
    ; end of [section .gdt]
    
    
    ;实模式代码段
    [section .s16]
    [bits 16]
    CODE16_SEGMENT:
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, 0x7c00
        
        ; initialize GDT for 32 bits code segment
        mov eax, 0
        mov ax, cs
        shl eax, 4
        add eax, CODE32_SEGMENT ;(段地址 << 4) + 偏移地址 ==》 32位代码段实地址
        mov word [CODE32_DESC + 2], ax ;段基址的0-15位放在描述符低4字节的16-31位
        shr eax, 16
        mov byte [CODE32_DESC + 4], al ;段基址的16-23位放在描述符的高4字节的0-7位
        mov byte [CODE32_DESC + 7], ah ;段基址的24-31位放在描述符的高4字节的24-31位
        
        ; initialize GDT pointer struct
        mov eax, 0
        mov ax, ds
        shl eax, 4
        add eax, GDT_ENTRY ;得到全局段描述符表的起始地址
        mov dword [GdtPtr + 2], eax
    
        ; 1. load GDT
        lgdt [GdtPtr]
        
        ; 2. close interrupt 
        cli    
        
        ; 3. open A20
        in al, 0x92
        or al, 00000010b
        out 0x92, al
        
        ; 4. enter protect mode
        mov eax, cr0
        or eax, 0x01 ;通知处理器进入保护模式:将某一位置1
        mov cr0, eax
        
        ; 5. jump to 32 bits code ;从16位实模式跳转到32保护模式
        jmp dword Code32Selector : 0 ;这一步会将Code32Selector 装入CS,通过选择子就能拿到段基址
    
    	
    ;32位代码段
    [section .s32]
    [bits 32]
    CODE32_SEGMENT:
        mov eax, 0
        jmp CODE32_SEGMENT
    
    Code32SegLen    equ    $ - CODE32_SEGMENT

    makefile

    
    .PHONY : all clean rebuild
    
    BOOT_SRC := boot.asm
    BOOT_OUT := boot
    
    LOADER_SRC  := loader.asm
    INCLUDE_SRC := inc.asm
    LOADER_OUT  := loader
    
    IMG := data.img
    IMG_PATH := /mnt/hgfs
    
    RM := rm -fr
    
    all : $(IMG) $(BOOT_OUT) $(LOADER_OUT)
    	@echo "Build Success ==> D.T.OS!"
    
    $(IMG) :
    	bximage $@ -q -fd -size=1.44
    	
    $(BOOT_OUT) : $(BOOT_SRC)
    	nasm $^ -o $@
    	dd if=$@ of=$(IMG) bs=512 count=1 conv=notrunc
    	
    $(LOADER_OUT) : $(LOADER_SRC) $(INCLUDE_SRC)
    	nasm $< -o $@
    	sudo mount -o loop $(IMG) $(IMG_PATH)
    	sudo cp $@ $(IMG_PATH)/$@
    	sudo umount $(IMG_PATH)
    	
    clean :
    	$(RM) $(IMG) $(BOOT_OUT) $(LOADER_OUT)
    	
    rebuild :
    	@$(MAKE) clean
    	@$(MAKE) all
    

    很多东西都可以反汇编调试分析

    例如:分析用lgdt指令将GDT基地址(32位)和边界(16位)装入GDTR(48位)

    找到lgdt指令的内存地址,在bochs里打断点分析

    上面的汇编代码只有两个描述符所以GDT大小为16字节(0x0f),GDT的起始地址是0x9004

    再例如:实模式和保护模式对内存单元的访问不同

                       0008:00000000 -->  8是索引1<<3+0+0所得,通过选择子得到段基址(0x907c)

     

    疑难问题:

    为什么不直接使用标签定义描述符中的段基地址?

    为什么16位代码段到32位代码段必须无条件跳转?

    jmp dword Code32Selector : 0为什么使用dword?

     

    需要掌握的重点

         - NASM将汇编文件当成一个独立的代码段编译,从0开始计算,每一条指令对应一个汇编地址

         - 汇编代码中的标签(Label)代表的是段内偏移地址

         - 实模式下需要配合段寄存器中的值计算标签的物理地址

    流水线技术

         - 处理器为了提高效率将当前指令和后续指令预取到流水线

         - 因此,可能同时预取的指令中既有16位代码又有32位代码

         - 为了避免将32位代码用16位的方式运行,需要刷新流水线

         - 无条件跳转jmp 能强制刷新流水线...

    不一般的jmp(s16→s32)

         - 在16位代码中,所有的立即数默认为16位

         - 从16位代码段跳转到32位代码段的时,必须做强制转换

         - 否则,段内偏移地址可能被截断

     

    小结

        - 80386处理器是计算机发展史上的里程碑

        - 32位的寄存器和地址总线能够直接访问4G内存的任意角落

        - 需要在16位实模式中对GDT中的数据进行初始化

        - 代码中需要为GDT定义一个标识数据结构(GdtPtr)

        - 需要使用jmp指令从16位代码跳转到32位代码

     

    2、深入保护模式

    1、显存段、数据段和栈段

    为了显示数据,必须存在两大硬件:显卡+显示器

         - 显卡:为显示器提供需要显示的数据,控制显示器的模式和状态

         - 显示器:将目标数据以可见的方式呈现在屏幕上

    显存的概念和意义

         - 显卡拥有自己内部的数据存储器,简称显存

         - 显存在本质上和普通内存无差别,用于存储目标数据

         - 操作显存中的数据将导致显示器上内容的改变

    显卡的工作模式:文本模式&图形模式

         - 在不同的模式下,显卡对显存内容的解释是不同的

         - 可以使用专属指令或int 0x10中断改变显卡工作模式

         - 在文本模式下:显存的地址范围映射 为:[0xB8000,0XBFFFF],一屏幕可以显示25行,每行80个字符

       

    显卡的文本模式原理

              每行可以显示80个字符,一个字符由两个字节组成,低字节为要显示的字符,高字节为显示的属性

     

    小目标:在保护模式下,打印指定内存中的字符串

         - 定义全局堆栈段(gs),用于保护模式下的函数调用

         - 定义全局数据段(.dat),用于定义只读数据(D.T.OS!)

         - 利用对显存段的操作定义字符串打印函数(PrintString)

     

    保护模式下的栈段(Stack Segment)

        1. 指定一段空间,并为其定义段描述符

        2. 根据段描述符表中的位置定义选择子

        3. 初始化栈段寄存器(ss←StackSelector)

        4. 初始化栈顶指针(esp←TopOfStack)

     

    打印函数(PrintString)的设计

    汇编小贴士

    32位保护模式下的乘法操作(mul)

         - 被乘数放到AX寄存器

         - 乘数放到通用寄存器或内存单元(16位)

         - 相乘的结果放到EAX寄存器中

    再论$和$$

         - $表示当前行相对于代码起始位置处的偏移量

         - $$表示当前代码节(section)的起始位置

     

    2、编程实验

    深度体验保护模式 loader.asm

    %include "inc.asm"
    
    org 0x9000
    
    jmp CODE16_SEGMENT
    
    [section .gdt]
    ; GDT definition
    ;                                 段基址,       段界限,       段属性
    GDT_ENTRY       :     Descriptor    0,            0,           0
    CODE32_DESC     :     Descriptor    0,    Code32SegLen - 1,    DA_C + DA_32
    VIDEO_DESC      :     Descriptor 0xB8000,     0x07FFF,         DA_DRWA + DA_32
    DATA32_DESC     :     Descriptor    0,    Data32SegLen - 1,    DA_DR + DA_32
    STACK32_DESC      :     Descriptor    0,     TopOfStack32,     DA_DRW + DA_32 ;实模式栈空间0~0x7c00,保护模式下的栈空间另外定义
    ; GDT end
    
    GdtLen    equ   $ - GDT_ENTRY
    
    GdtPtr:
              dw   GdtLen - 1
              dd   0
              
              
    ; GDT Selector
    
    Code32Selector    equ (0x0001 << 3) + SA_TIG + SA_RPL0
    VideoSelector     equ (0x0002 << 3) + SA_TIG + SA_RPL0
    Data32Selector    equ (0x0003 << 3) + SA_TIG + SA_RPL0
    Stack32Selector     equ (0x0004 << 3) + SA_TIG + SA_RPL0
    
    ; end of [section .gdt]
    
    TopOfStack16   equ 0x7c00
    
    [section .dat]
    [bits 32]
    DATA32_SEGMENT:
        DTOS               db  "D.T.OS!", 0
        DTOS_OFFSET        equ DTOS - $$        ;段内偏移地址
        HELLO_WORLD        db  "Hello World!", 0
        HELLO_WORLD_OFFSET equ HELLO_WORLD - $$ ;段内偏移地址
    
    Data32SegLen equ $ - DATA32_SEGMENT
    
    [section .s16]
    [bits 16]
    CODE16_SEGMENT:
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, TopOfStack16 
        
        ; initialize GDT for 32 bits code segment
        mov esi, CODE32_SEGMENT
        mov edi, CODE32_DESC
        
        call InitDescItem ;实模式下的函数调用
        
        mov esi, DATA32_SEGMENT
        mov edi, DATA32_DESC
        
        call InitDescItem
    	
    	mov esi, STACK32_SEGMENT
        mov edi, STACK32_DESC
        
        call InitDescItem
        
        ; initialize GDT pointer struct
        mov eax, 0
        mov ax, ds
        shl eax, 4
        add eax, GDT_ENTRY
        mov dword [GdtPtr + 2], eax
    
        ; 1. load GDT
        lgdt [GdtPtr]
        
        ; 2. close interrupt
        cli 
        
        ; 3. open A20
        in al, 0x92
        or al, 00000010b
        out 0x92, al
        
        ; 4. enter protect mode
        mov eax, cr0
        or eax, 0x01
        mov cr0, eax
        
        ; 5. jump to 32 bits code
        jmp dword Code32Selector : 0
    
    
    ; esi    --> code segment label
    ; edi    --> descriptor label
    InitDescItem:
        push eax
    
        mov eax, 0
        mov ax, cs
        shl eax, 4
        add eax, esi
        mov word [edi + 2], ax
        shr eax, 16
        mov byte [edi + 4], al
        mov byte [edi + 7], ah
        
        pop eax
        
        ret
        
        
    [section .s32]
    [bits 32]
    CODE32_SEGMENT:
        mov ax, VideoSelector
        mov gs, ax
        
        mov ax, Stack32Selector  
        mov ss, ax
    	
    	mov eax, TopOfStack32
        mov esp, eax
        
        mov ax, Data32Selector
        mov ds, ax
        
        mov ebp, DTOS_OFFSET ;注意应该是数据段内偏移地址
        mov bx, 0x0C
        mov dh, 12
        mov dl, 33
        
        call PrintString ;32位保护模式下的函数调用
        
        mov ebp, HELLO_WORLD_OFFSET
        mov bx, 0x0C
        mov dh, 13
        mov dl, 31
        
        call PrintString
        
        jmp $
    
    ; ds:ebp    --> string address
    ; bx        --> attribute
    ; dx        --> dh : row, dl : col
    PrintString:
        push ebp
        push eax
        push edi
        push cx
        push dx
        
    print:
        mov cl, [ds:ebp]
        cmp cl, 0
        je end
        mov eax, 80
        mul dh
        add al, dl
        shl eax, 1 ;(80 * row + col) * 2
        mov edi, eax
        mov ah, bl
        mov al, cl
        mov [gs:edi], ax
        inc ebp
        inc dl
        jmp print
    
    end:
        pop dx
        pop cx
        pop edi
        pop eax
        pop ebp
        
        ret
        
    Code32SegLen    equ    $ - CODE32_SEGMENT
    
    ;32位保护模式的栈
    [section .gs]
    [bits 32]
    STACK32_SEGMENT:
        times 1024 * 4 db 0
        
    Stack32SegLen equ $ - STACK32_SEGMENT
    TopOfStack32  equ Stack32SegLen - 1

     

    小结

        - 实模式下可以使用32位寄存器和32位地址

        - 显存是显卡内部的存储单元,本质上与普通内存无差别

        - 显卡有两种工作模式:文本模式&图形模式

        - 文本模式下操作显存单元中的数据能够立即反映到显示器

        - 定义保护模式的栈段时,必须设置段选择子和栈顶指针

    2、从保护模式返回实模式

    80x86中的一个神秘限制

        - 无法直接从32位保护模式代码段回到实模式

        - 只能从16位保护模式代码段间接返回实模式(保护模式下也可以定义16位代码段)

          这是因为无法实现从32位代码段返回时cs高速缓冲寄存器中的属性符合实模式的要求(实模式不能改变段属性)。

          需要加载一个合适的描述符选择子到有关段寄存器,以使对应段描述符高速缓冲寄存器中含有合适的段界限和属性

        - 在返回前必须用合适的选择子对段寄存器赋值

     

    80286之后的处理器都提供兼容8086的实模式, 然而,绝大多时候处理器都运行于保护模式,

    因此,保护模式的运行效率至关重要。那么,处理器为了高效的访问内存中的段描述符增加高速缓冲存储器

    当使用选择子设置段寄存器时,根据选择子 访问内存中的段描述符,将段描述符加载到段寄存器的高速缓冲存储器

    需要段描述符信息时,直接从高速缓冲存储器中获得,这是前面讲过的。

     

    那么当处理器运行于实模式时段寄存器的高速缓冲存储器是否会用到

     

    注意事项!!!

        - 在实模式下,高速缓冲存储器仍然保存这三个属性,段基址是段寄存器左移4位的值

        - 段基址是32位,其值是相应段寄存器的值乘以16(不用每次做乘法,直接从高速缓冲寄存器取)

        - 实模式下段基址有效位为20位,段界限固定为0xFFFF(64K)

        - 段属性的值不可设置,只能继续沿用保护方式下所设置的值

    因此,当从保护模式返回实模式时:在16位保护模式代码段中

    通过加载一个合适的描述符选择子到有关段寄存器,以使得对应段描述符高速缓冲寄存器中含有合适的段界限和属性!!

     

    返回实模式的流程

    jmp指令

    编程实验

    从保护模式返回实模式

    %include "inc.asm"
    
    org 0x9000
    
    jmp ENTRY_SEGMENT
    
    [section .gdt]
    ; GDT definition
    ;                                 段基址,       段界限,       段属性
    GDT_ENTRY       :     Descriptor    0,            0,           0
    CODE32_DESC     :     Descriptor    0,    Code32SegLen - 1,    DA_C + DA_32
    VIDEO_DESC      :     Descriptor 0xB8000,     0x07FFF,         DA_DRWA + DA_32
    DATA32_DESC     :     Descriptor    0,    Data32SegLen - 1,    DA_DR + DA_32
    STACK32_DESC    :     Descriptor    0,     TopOfStack32,       DA_DRW + DA_32
    CODE16_DESC     :     Descriptor    0,        0xFFFF,          DA_C ;此处不可设置为Code16SegLen,否则在jmp时会检查地址越界CPU异常
    UPDATE_DESC     :     Descriptor    0,        0xFFFF,          DA_DRW ;16位实模式下每一个段最大64k,段属性可读可写
    ; GDT end
    
    GdtLen    equ   $ - GDT_ENTRY
    
    GdtPtr:
              dw   GdtLen - 1
              dd   0
              
              
    ; GDT Selector
    
    Code32Selector    equ (0x0001 << 3) + SA_TIG + SA_RPL0
    VideoSelector     equ (0x0002 << 3) + SA_TIG + SA_RPL0
    Data32Selector    equ (0x0003 << 3) + SA_TIG + SA_RPL0
    Stack32Selector   equ (0x0004 << 3) + SA_TIG + SA_RPL0
    Code16Selector    equ (0x0005 << 3) + SA_TIG + SA_RPL0
    UpdateSelector    equ (0x0006 << 3) + SA_TIG + SA_RPL0
    ; end of [section .gdt]
    
    TopOfStack16    equ 0x7c00
    
    [section .dat]
    [bits 32]
    DATA32_SEGMENT:
        DTOS               db  "D.T.OS!", 0
        DTOS_OFFSET        equ DTOS - $$
        HELLO_WORLD        db  "Hello World!", 0
        HELLO_WORLD_OFFSET equ HELLO_WORLD - $$
    
    Data32SegLen equ $ - DATA32_SEGMENT
    
    [section .s16]
    [bits 16]
    ENTRY_SEGMENT:
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, TopOfStack16
        
        mov [BACK_TO_REAL_MODE + 3], ax ;动态的填入cs寄存器运行后的值
        
        ; initialize GDT for 32 bits code segment
        mov esi, CODE32_SEGMENT
        mov edi, CODE32_DESC
        
        call InitDescItem
        
        mov esi, DATA32_SEGMENT
        mov edi, DATA32_DESC
        
        call InitDescItem
        
        mov esi, STACK32_SEGMENT
        mov edi, STACK32_DESC
        
        call InitDescItem
        
        mov esi, CODE16_SEGMENT
        mov edi, CODE16_DESC
        
        call InitDescItem
        
        ; initialize GDT pointer struct
        mov eax, 0
        mov ax, ds
        shl eax, 4
        add eax, GDT_ENTRY
        mov dword [GdtPtr + 2], eax
    
        ; 1. load GDT
        lgdt [GdtPtr]
        
        ; 2. close interrupt
        cli 
        
        ; 3. open A20
        in al, 0x92
        or al, 00000010b
        out 0x92, al
        
        ; 4. enter protect mode
        mov eax, cr0
        or eax, 0x01
        mov cr0, eax
        
        ; 5. jump to 32 bits code
        jmp dword Code32Selector : 0
    
    ;返回实模式
    BACK_ENTRY_SEGMENT:
        mov ax, cs 
        mov ds, ax ;对段寄存器的赋值只会改变高速缓冲寄存器中的段基址,段界限和属性沿用UPDATE_DESC
        mov es, ax
        mov ss, ax
        mov sp, TopOfStack16 ;重新设置各个段寄存器的值,恢复sp的值
        
        in al, 0x92
        and al, 11111101b
        out 0x92, al
        
        sti
        
        mov bp, HELLO_WORLD
        mov cx, 12
        mov dx, 0
        mov ax, 0x1301
        mov bx, 0x0007
        int 0x10
        
        jmp $
    
    ; esi    --> code segment label
    ; edi    --> descriptor label
    InitDescItem:
        push eax
    
        mov eax, 0
        mov ax, cs
        shl eax, 4
        add eax, esi
        mov word [edi + 2], ax
        shr eax, 16
        mov byte [edi + 4], al
        mov byte [edi + 7], ah
        
        pop eax
        
        ret
        
    
    ;保护模式下16位代码段
    [section .s16]
    [bits 16]
    CODE16_SEGMENT:
        mov ax, UpdateSelector
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov ss, ax  ;刷新对应段描述符高速缓冲寄存器(含有合适的段界限和属性)以合法表示实模式
    	
        mov eax, cr0
        and al, 11111110b
        mov cr0, eax ;成功进入实模式
    
    BACK_TO_REAL_MODE:    
        jmp 0 : BACK_ENTRY_SEGMENT ;此处按照16位实模式方式跳转,所以这个偏移地址(最大64k)不可超出段界限大小,执行时依旧受保护
        
    Code16SegLen    equ    $ - CODE16_SEGMENT
    
    ;保护模式下32位代码段
    [section .s32]
    [bits 32]
    CODE32_SEGMENT:
        mov ax, VideoSelector
        mov gs, ax
        
        mov ax, Stack32Selector
        mov ss, ax
        
        mov eax, TopOfStack32
        mov esp, eax
        
        mov ax, Data32Selector
        mov ds, ax
        
        mov ebp, DTOS_OFFSET
        mov bx, 0x0C
        mov dh, 12
        mov dl, 33
        
        call PrintString
        
        mov ebp, HELLO_WORLD_OFFSET
        mov bx, 0x0C
        mov dh, 13
        mov dl, 31
        
        call PrintString
        
        jmp Code16Selector : 0 ;跳转到16位保护模式代码段
    
    ; ds:ebp    --> string address
    ; bx        --> attribute
    ; dx        --> dh : row, dl : col
    PrintString:
        push ebp
        push eax
        push edi
        push cx
        push dx
        
    print:
        mov cl, [ds:ebp]
        cmp cl, 0
        je end
        mov eax, 80
        mul dh
        add al, dl
        shl eax, 1
        mov edi, eax
        mov ah, bl
        mov al, cl
        mov [gs:edi], ax
        inc ebp
        inc dl
        jmp print
    
    end:
        pop dx
        pop cx
        pop edi
        pop eax
        pop ebp
        
        ret
        
    Code32SegLen    equ    $ - CODE32_SEGMENT
    
    [section .gs]
    [bits 32]
    STACK32_SEGMENT:
        times 1024 * 4 db 0
        
    Stack32SegLen equ $ - STACK32_SEGMENT
    TopOfStack32  equ Stack32SegLen - 1

     

    小结

        - 从保护模式能够间接跳转返回实模式

        - 在实模式下,依然使用高速缓冲存储器中的数据做有效性判断

        - 通过运行时修改指令中的数据能够动态决定代码的行为

     

     

    3、局部段描述符表的使用

    什么是LDT(Local Descriptor Table)?

    局部段描述符表

         - 本质是一个段描述符表,用于定义段描述符

         - 与GDT类似,可以看作“段描述符的数组"

         - 通过定义选择子访问局部段描述符表中的元素

    LDTR局部描述符寄存器:16位寄存器,存放的是选择子。

    ①一个处理器只对应一个GDT,前面说过GDTR存放着GDT在内存的入口地址和界限,此后CPU通过GDTR找到GDT。

    ②局部描述符表可以有多个,局部段描述符表也是一段内存,即需要在GDT增加一个描述符描述它(③),该描述符的

        选择子是通过lldt指令装载到LDTR(16位)。通过这个选择子就能在GDT中找到LDT描述符,从而确定内存中的LDT。

    ④段选择器存放的是局部描述符表中描述符的选择子,于是就能确定描述符,从而确定描述符描述的一段内存(⑤)

     

                                     此图来源网络,由于源较多具体也不知是谁的

     

    局部段描述符选择子

    注意事项!!

         - 局部段描述符表需要在全局段描述符表中注册(增加描述项)

         - 通过对应的选择子加载局部段描述符(lldt指令)

         - 局部段描述符表从第0项开始使用(different from GDT)

     

    LDT具体用来干什么?为什么还需要一个“额外的“段描述符表?

            要知道当需要很多段时全局描述符表肯定不够用

    LDT的意义

         - 代码层面的意义:分级管理功能相同意义不同的段(如:多个代码段)

         - 系统层面的意义:实现多任务的基础要素(每个任务对应一系列不同的段)

    LDT的定义与使用

         1. 定义独立功能相关的段(代码段,数据段,栈段)(有着对应的段描述符)

         2. 将目标段描述符组成局部段描述符表(LDT)

         3. 为各个段描述符定义选择子(SA_TIL)

         4. 在GDT中定义LDT的段描述符,并定义选择子

     

    编程实验

    使用LDT实现新功能

    inc.asm

    
    ; Segment Attribute
    DA_32    equ    0x4000
    DA_DR    equ    0x90
    DA_DRW   equ    0x92
    DA_DRWA  equ    0x93
    DA_C     equ    0x98
    DA_CR    equ    0x9A
    DA_CCO   equ    0x9C
    DA_CCOR  equ    0x9E
    
    ; Special Attribute
    DA_LDT   equ    0x82
    
    ; Selector Attribute
    SA_RPL0    equ    0
    SA_RPL1    equ    1
    SA_RPL2    equ    2
    SA_RPL3    equ    3
    
    SA_TIG    equ    0
    SA_TIL    equ    4
    
    ; 描述符
    ; usage: Descriptor Base, Limit, Attr
    ;        Base:  dd
    ;        Limit: dd (low 20 bits available)
    ;        Attr:  dw (lower 4 bits of higher byte are always 0)
    %macro Descriptor 3	                          ; 段基址, 段界限, 段属性
        dw    %2 & 0xFFFF                         ; 段界限1
        dw    %1 & 0xFFFF                         ; 段基址1
        db    (%1 >> 16) & 0xFF                   ; 段基址2
        dw    ((%2 >> 8) & 0xF00) | (%3 & 0xF0FF) ; 属性1 + 段界限2 + 属性2
        db    (%1 >> 24) & 0xFF                   ; 段基址3
    %endmacro                                     ; 共 8 字节
    

    loader.asm

    %include "inc.asm"
    
    org 0x9000
    
    jmp ENTRY_SEGMENT
    
    [section .gdt]
    ; GDT definition
    ;                                 段基址,       段界限,       段属性
    GDT_ENTRY       :     Descriptor    0,            0,           0
    CODE32_DESC     :     Descriptor    0,    Code32SegLen - 1,    DA_C + DA_32
    VIDEO_DESC      :     Descriptor 0xB8000,     0x07FFF,         DA_DRWA + DA_32
    DATA32_DESC     :     Descriptor    0,    Data32SegLen - 1,    DA_DR + DA_32
    STACK32_DESC    :     Descriptor    0,     TopOfStack32,       DA_DRW + DA_32
    CODE16_DESC     :     Descriptor    0,        0xFFFF,          DA_C 
    UPDATE_DESC     :     Descriptor    0,        0xFFFF,          DA_DRW
    TASK_A_LDT_DESC :     Descriptor    0,     TaskALdtLen - 1,    DA_LDT ;LDT在GDT的描述符
    ; GDT end
    
    GdtLen    equ   $ - GDT_ENTRY
    
    GdtPtr:
              dw   GdtLen - 1
              dd   0
              
              
    ; GDT Selector
    
    Code32Selector    equ (0x0001 << 3) + SA_TIG + SA_RPL0
    VideoSelector     equ (0x0002 << 3) + SA_TIG + SA_RPL0
    Data32Selector    equ (0x0003 << 3) + SA_TIG + SA_RPL0
    Stack32Selector   equ (0x0004 << 3) + SA_TIG + SA_RPL0
    Code16Selector    equ (0x0005 << 3) + SA_TIG + SA_RPL0
    UpdateSelector    equ (0x0006 << 3) + SA_TIG + SA_RPL0
    TaskALdtSelector  equ (0x0007 << 3) + SA_TIG + SA_RPL0 ;LDT在GDT的描述符的选择子
    ; end of [section .gdt]
    
    TopOfStack16    equ 0x7c00
    
    [section .dat]
    [bits 32]
    DATA32_SEGMENT:
        DTOS               db  "D.T.OS!", 0
        DTOS_OFFSET        equ DTOS - $$
        HELLO_WORLD        db  "Hello World!", 0
        HELLO_WORLD_OFFSET equ HELLO_WORLD - $$
    
    Data32SegLen equ $ - DATA32_SEGMENT
    
    [section .s16]
    [bits 16]
    ENTRY_SEGMENT:
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, TopOfStack16
        
        mov [BACK_TO_REAL_MODE + 3], ax
        
        ; initialize GDT for 32 bits code segment
        mov esi, CODE32_SEGMENT
        mov edi, CODE32_DESC
        
        call InitDescItem
        
        mov esi, DATA32_SEGMENT
        mov edi, DATA32_DESC
        
        call InitDescItem
        
        mov esi, STACK32_SEGMENT
        mov edi, STACK32_DESC
        
        call InitDescItem
        
        mov esi, CODE16_SEGMENT
        mov edi, CODE16_DESC
        
        call InitDescItem
        
    	;注意初始化段基址
        mov esi, TASK_A_LDT_ENTRY
        mov edi, TASK_A_LDT_DESC
        
        call InitDescItem
        
        mov esi, TASK_A_CODE32_SEGMENT
        mov edi, TASK_A_CODE32_DESC
        
        call InitDescItem
        
        mov esi, TASK_A_DATA32_SEGMENT
        mov edi, TASK_A_DATA32_DESC
        
        call InitDescItem
        
        mov esi, TASK_A_STACK32_SEGMENT
        mov edi, TASK_A_STACK32_DESC
        
        call InitDescItem
        
        ; initialize GDT pointer struct
        mov eax, 0
        mov ax, ds
        shl eax, 4
        add eax, GDT_ENTRY
        mov dword [GdtPtr + 2], eax
    
        ; 1. load GDT
        lgdt [GdtPtr]
        
        ; 2. close interrupt
        cli 
        
        ; 3. open A20
        in al, 0x92
        or al, 00000010b
        out 0x92, al
        
        ; 4. enter protect mode
        mov eax, cr0
        or eax, 0x01
        mov cr0, eax
        
        ; 5. jump to 32 bits code
        jmp dword Code32Selector : 0
    
    BACK_ENTRY_SEGMENT:
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, TopOfStack16
        
        in al, 0x92
        and al, 11111101b
        out 0x92, al
        
        sti
        
        mov bp, HELLO_WORLD
        mov cx, 12
        mov dx, 0
        mov ax, 0x1301
        mov bx, 0x0007
        int 0x10
        
        jmp $
    
    ; esi    --> code segment label
    ; edi    --> descriptor label
    InitDescItem:
        push eax
    
        mov eax, 0
        mov ax, cs
        shl eax, 4
        add eax, esi
        mov word [edi + 2], ax
        shr eax, 16
        mov byte [edi + 4], al
        mov byte [edi + 7], ah
        
        pop eax
        
        ret
        
    
    [section .s16]
    [bits 16]
    CODE16_SEGMENT:
        mov ax, UpdateSelector
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov ss, ax
        
        mov eax, cr0
        and al, 11111110b
        mov cr0, eax
    
    BACK_TO_REAL_MODE:    
        jmp 0 : BACK_ENTRY_SEGMENT
        
    Code16SegLen    equ    $ - CODE16_SEGMENT
    
        
    [section .s32]
    [bits 32]
    CODE32_SEGMENT:
        mov ax, VideoSelector
        mov gs, ax
        
        mov ax, Stack32Selector
        mov ss, ax
        
        mov eax, TopOfStack32
        mov esp, eax
        
        mov ax, Data32Selector
        mov ds, ax
        
        mov ebp, DTOS_OFFSET
        mov bx, 0x0C
        mov dh, 12
        mov dl, 33
        
        call PrintString
        
        mov ebp, HELLO_WORLD_OFFSET
        mov bx, 0x0C
        mov dh, 13
        mov dl, 31
        
        call PrintString
        
        mov ax, TaskALdtSelector
        
        lldt ax ;使用LDTR寄存器保存 LDT在GDT的描述符的选择子 
        
        jmp TaskACode32Selector : 0 ;通过选择子在LDT找到描述符获得段基址 (通过TI位得到哪个描述符表)
        
        ; jmp Code16Selector : 0
    
    ; ds:ebp    --> string address
    ; bx        --> attribute
    ; dx        --> dh : row, dl : col
    PrintString:
        push ebp
        push eax
        push edi
        push cx
        push dx
        
    print:
        mov cl, [ds:ebp]
        cmp cl, 0
        je end
        mov eax, 80
        mul dh
        add al, dl
        shl eax, 1
        mov edi, eax
        mov ah, bl
        mov al, cl
        mov [gs:edi], ax
        inc ebp
        inc dl
        jmp print
    
    end:
        pop dx
        pop cx
        pop edi
        pop eax
        pop ebp
        
        ret
        
    Code32SegLen    equ    $ - CODE32_SEGMENT
    
    [section .gs]
    [bits 32]
    STACK32_SEGMENT:
        times 1024 * 4 db 0
        
    Stack32SegLen equ $ - STACK32_SEGMENT
    TopOfStack32  equ Stack32SegLen - 1
    
    
    ;启动一个新任务
    ; ==========================================
    ;
    ;            Task A Code Segment 
    ;
    ; ==========================================
    
    [section .task-a-ldt]
    ; Task A LDT definition
    ;                                             段基址,                段界限,                段属性
    TASK_A_LDT_ENTRY:
    TASK_A_CODE32_DESC    :    Descriptor          0,           TaskACode32SegLen - 1,        DA_C + DA_32
    TASK_A_DATA32_DESC    :    Descriptor          0,           TaskAData32SegLen - 1,        DA_DR + DA_32
    TASK_A_STACK32_DESC   :    Descriptor          0,           TaskAStack32SegLen - 1,       DA_DRW + DA_32
    
    TaskALdtLen  equ   $ - TASK_A_LDT_ENTRY
    
    ; Task A LDT Selector
    TaskACode32Selector  equ   (0x0000 << 3) + SA_TIL + SA_RPL0 ;表明是局部段描述符表的选择子
    TaskAData32Selector  equ   (0x0001 << 3) + SA_TIL + SA_RPL0
    TaskAStack32Selector equ   (0x0002 << 3) + SA_TIL + SA_RPL0
    
    [section .task-a-dat]
    [bits 32]
    TASK_A_DATA32_SEGMENT:
        TASK_A_STRING        db   "This is Task A!", 0
        TASK_A_STRING_OFFSET equ  TASK_A_STRING - $$
        
    TaskAData32SegLen  equ  $ - TASK_A_DATA32_SEGMENT
    
    [section .task-a-gs]
    [bits 32]
    TASK_A_STACK32_SEGMENT:
        times 1024 db 0 ;该栈大小1k
        
    TaskAStack32SegLen  equ  $ - TASK_A_STACK32_SEGMENT
    TaskATopOfStack32   equ  TaskAStack32SegLen - 1 ;初始栈顶指针位置
    
    [section .task-a-s32]
    [bits 32]
    TASK_A_CODE32_SEGMENT:
        mov ax, VideoSelector 
        mov gs, ax
        
    	;从新指定栈段,数据段
        mov ax, TaskAStack32Selector 
        mov ss, ax
        
        mov eax, TaskATopOfStack32
        mov esp, eax
        
        mov ax, TaskAData32Selector
        mov ds, ax
        
    	;打印字符串
        mov ebp, TASK_A_STRING_OFFSET
        mov bx, 0x0C
        mov dh, 14
        mov dl, 29
        
        call TaskAPrintString ;为什么不直接调用PrintString而是拷贝一份在该段?
        
        jmp Code16Selector : 0
        
    
    ; ds:ebp    --> string address
    ; bx        --> attribute
    ; dx        --> dh : row, dl : col
    TaskAPrintString:
        push ebp
        push eax
        push edi
        push cx
        push dx
        
    task_print:
        mov cl, [ds:ebp]
        cmp cl, 0
        je task_end
        mov eax, 80
        mul dh
        add al, dl
        shl eax, 1
        mov edi, eax
        mov ah, bl
        mov al, cl
        mov [gs:edi], ax
        inc ebp
        inc dl
        jmp task_print
    
    task_end:
        pop dx
        pop cx
        pop edi
        pop eax
        pop ebp
        
        ret
        
    TaskACode32SegLen   equ  $ - TASK_A_CODE32_SEGMENT
    

    发现关于ldtr寄存器网上很多说法不一致,这里断点分析一下

                  ldtr保存的值是0x0038即56 = 7 * 8 索引值为7,说明ldtr保存的就是ldt在gdt中的选择子

     

    多任务程序设计的实现思路

     

    保护模式下的不同段之间如何进行代码复用(如:调用同一个函数)?

    解决方案

           - 将不同代码段需要复用的函数定义到独立的段中(retf)

           - 计算每一个可复用函数的偏移量(FuncName-$$)

           - 通过段选择子 : 偏移地址的方式对目标函数进行远调用

     

    小结

       - 局部段描述表用于组织功能相关的段(section)

       - 局部段描述符表需要加载后才能正常使用(lldt)

       - 局部段描述符表必须在全局段描述符表中注册(Descriptor)

       - 通过局部段描述符表的选择子对其进行访问

       - 局部段描述符表是实现多任务的基础

     


    4、初探保护模式的保护机制

    保护模式利用段界限对内存访问进行保护

        - 使用选择子访问段描述符表时,索引值会被处理器合法性检测

               ✦ 当索引值越界时,引发异常(cpu reset)

               ✦ 判断规则:索引值 * 8 + 7 <= 段描述表界限值

        - 使用选择子给段寄存器赋值时,内存段类型会被合法性检测

               ✦ 具备可执行属性的段(代码段)只能加载到CS寄存器

               ✦ 具备可写属性的段(数据段)才能加载到SS寄存器

               ✦ 具备可读属性的段才能加载到DS,ES,FS,GS寄存器

        - 代码段和数据段的保护

               ✦ 处理器每访问一个地址都要确认该地址不超过界限值

               ✦ 判断规则:

                              ☛ 代码段:IP+指令长度<=代码段界限

                              ☛ 数据段:访问起始地址+访问数据长度<=数据段界限

     

                                          IP+指令长度若大于界限值,后面的指令就不会再执行

    举例说明:

    1、CODE32_DESC     :     Descriptor    0,    Code32SegLen - 2,    DA_C + DA_32  

    界限值正常应该是Code32SegLen - 1,而 - 2会有指令在界限值之外,cpu在执行这些指令的时候出现异常

    2、若CODE32_DESC :     Descriptor    0,    Code32SegLen - 1,    DA_DR + DA_32 (没有可执行属性DA_C)

    执行 jmp dword Code32Selector : 0时会触发异常 (cpu reset)。

    执行这条指令时会将选择子Code32Selector加载到CS寄存器中时,要确保加载到CS寄存器的段要有可执行属性

    注意:保护模式中代码中定义的界限值通常为:最大偏移地址值(相对于段基地址)。

     

    保护模式除了利用段界限对内存访问进行保护,是否还提供其它的保护机制?

     

    下一篇文章详细讲解其他保护机制


    本节参考狄泰未来《操作系统专题课程》、《x86汇编 · 从实模式到保护模式》文中图片多直接引用其中

     

     

    展开全文
  • 但不同的是,LinuxBIOS,Grub这些引导程序一开始在上电启动的第一步就已经进保护模式了,因为它们的目标非常明确,就是引导真正的系统,在保护模式下更安全编程更灵活,比如可以访问大内存,所以它们在启动时,无一...

    台风夜基本是不会睡觉的,写点有意思的。此时,2019年8月10日 2:20.

    酷爱历史,于是在主音吉他手的推荐下,在历史的垃圾堆里找到了Freedos。这个从1994年开始的dos兼容开源操作系统。实际上,它就是dos,可以说它是MS-DOS的续命者。

    非常令人震惊,2016年至今,这个Freedos竟然依然活跃。我以前竟然不知道这个。

    我一直以为1994年和现在的201X年对于计算机这个特定的领域而言简直就是两个时代,中间的技术代差堪比希腊火,伏火雷这种和现代洲际导弹之间的差异,没想到1994年的系统,竟然在今天还能玩到原汁原味的延续下来的东西,不错!同样的1990年代初的Linux,你看看5.3和0.01之间的差异就知道了。

    索性就装了一个。先是在VirtualBox上装的,后来改到了bochs。

    对于可以执行指令的机器而言,最纯粹的玩法就是眼睁睁看着这些指令被执行,而不是去折腾什么库,SDK,中间件,编译器之类,这是原教旨主义的看法,当然,我只是说随便玩玩,不提赚钱,那是程序员的事。

    我一直都想直接在一个屏幕上直接敲代码,然后机器给我反馈,但是总是被所谓 操作系统内核 所阻止,然后我改成了写内核模块,然而又被各种烦人的panic,oops等困扰,接下来重启一遍机器恢复一次环境需要一根烟的工夫。为了吃顿牛肉,养了一头牛,这简直糟糕透顶。

    大多数时候我就想看看在一个指令序列执行后机器发生了什么变化,却需要安装并学习大量的工具,然后望而却步,空留了一个想法。说实话,我憎恨复杂的工具。

    现如今的工具并不像它们声称的那般简单,相反,它们在故弄玄虚,显得复杂就是高端。

    Freedos让事情变好了很多。它更纯粹!

    Freedos自带的经典的debug程序,基本就是瑞士军刀了。我先用它写个hello程序,看看效果:
    在这里插入图片描述
    不用单独的汇编器,也不像往常那般先写一个文本的代码,然后编译器汇编器将它转成可执行的二进制文件,debug可以逐行汇编你的指令,然后直接执行。当然了,你也可以将它保存到文件里:
    在这里插入图片描述
    就是这么简单,而且你可以随时改二进制代码,先用 -u $段内地址 查看反汇编,然后想改哪个地址了,直接 -a $段内地址 修改即可了,非常方便。


    我准备用debug将这个dos系统带入到保护模式,搞不好能将它变成Linux呢。
    dos把vmlinuz读入内存,准备好rootfs,jmp过去…

    这种想法并不奇怪,LinuxBIOS可以,Grub可以,Lilo可以,为什么dos不可以,无非就是一个控制权转交的问题而已。但不同的是,LinuxBIOS,Grub这些引导程序一开始在上电启动的第一步就已经进保护模式了,因为它们的目标非常明确,就是引导真正的系统,在保护模式下更安全编程更灵活,比如可以访问大内存,所以它们在启动时,无一例外都会一步跨过千年的历史,直接进入保护模式,就连Linux本身也是如此,进保护模式这件事基本都是刻录在512字节的引导扇区的,这是第一步要做的事。

    dos与此不同,它本身就是一个实模式下的完整操作系统(Freedos可以使用保护模式,但我还是进实模式更好玩些),它在实模式下提供了一个操作系统应该提供的几乎所有功能,在特定的历史条件下,它没有进保护模式的动机,除非有人带它进。

    这就需要 运行时进入保护模式 ,而不是引导时进入保护模式。

    看看我写的最初的代码:
    在这里插入图片描述

    显然是错的,为什么呢?有我自己的原因也有debug程序的原因。

    我自己显然是不怎么会编程的,我没有系统学过任何语言的语法规则,都是需要解决一个问题时慢慢死磕的,所以写出 jmp -1,jmp 8:b13e 这种代码也就不足为奇了。

    此外,debug程序不支持标号,也可能是它支持,但我不知道怎么用,就当不支持吧,所以我必须把标号化作硬编码的地址,这也无可厚非,如果写对了,代码是可以运行产生预期结果的,但是很难写对,比如我上面程序的:

    lgdt [b138]
    

    这个就写错了。

    在实模式下,程序里的地址都是逻辑地址,代码里是看不见段寄存器的,所以lgdt的操作数就应该是GDTR的相对地址,也就是 0018,而不是 B120<<4+0018=B138 这个地址,B138这个地址是机器负责转换的,就像分页保护模式下程序看到的都是虚拟地址,然后机器通过MMU机制负责转化为物理地址一样。

    那么修改了上述错误呢?还是报错,机器直接崩溃。

    是的,谭浩强式的崩溃!
    【上大学时,谭浩强的C语言教程里说要是指针搞错了,就会系统崩溃,可是我怎么也不会把系统搞崩溃,顶多是程序崩溃。后来知道,原来我在保护模式下工作,如果在实模式下,比如原始dos系统,指针错了系统真的就会崩溃。谭浩强老师是对的】

    所以说,我准备先用nasm汇编一个 正儿八经的汇编程序

    所有的工具都在Linux下,我需要将Linux汇编而成的.com放进Freedos的磁盘中,有万种方法我都想不中,这使我不得开心颜。我不希望在Freedos里折腾网络。

    VirtualBox很难操作虚拟磁盘,想从外部放进去一个文件不知道该怎么办,于是我换到了bochs,如果只是为了玩而不是为了用,那么bochs显然要比VBox好太多,这也是听了主音吉他手的建议。
    【主音吉他手是一位猛士,就职于阿里巴巴,与就职于腾讯的温州皮鞋厂老板针锋相对!】

    Ubuntu里装bochs显然要比Centos里装bochs方便很多,因为Ubuntu是自带桌面的。
    【suse也是,但不很。我第一次接触ubuntu是在2007年冬天,姓赵的一位同事展示了ubuntu的立体桌面】

    自带桌面当然自带X11,而Centos往往都是纯命令行操作,即便是有什么图形界面,我一般也都不装或者装了之后卸掉。

    以下的命令可以初始化一个虚拟磁盘:

    root@name-VirtualBox:~/bochs# bximage
    ========================================================================
                                    bximage
      Disk Image Creation / Conversion / Resize and Commit Tool for Bochs
             $Id: bximage.cc 13481 2018-03-30 21:04:04Z vruppert $
    ========================================================================
    
    1. Create new floppy or hard disk image
    2. Convert hard disk image to other format (mode)
    3. Resize hard disk image
    4. Commit 'undoable' redolog to base image
    5. Disk image info
    
    0. Quit
    
    Please choose one [0] 1
    
    Create image
    
    Do you want to create a floppy disk image or a hard disk image?
    Please type hd or fd. [hd]
    
    What kind of image should I create?
    Please type flat, sparse, growing, vpc or vmware4. [flat]
    
    Choose the size of hard disk sectors.
    Please type 512, 1024 or 4096. [512]
    
    Enter the hard disk size in megabytes, between 10 and 8257535
    [10] 200
    
    What should be the name of the image?
    [c.img] freedos1.img
    
    Creating hard disk image 'freedos1.img' with CHS=406/16/63 (sector size = 512)
    
    The following line should appear in your bochsrc:
      ata0-master: type=disk, path="freedos.img", mode=flat
    root@name-VirtualBox:~/bochs#
    

    然后将freedos.iso挂在cdrom上,将系统装入这个freedos.img虚拟磁盘:

    # 编辑bochs配置文件,添加虚拟磁盘和虚拟光驱
    ata0-master: type=disk, mode=flat, path=/home/name/bochs/freedos.img
    # 一旦安装完成,注释掉虚拟光驱以表示断开
    #ata0-slave: type=cdrom, path=/home/name/bochs/freedos.iso, status=inserted
    

    接下来就可以通过以下命令挂载虚拟磁盘到Linux本地目录了:

    mount -t msdos -o loop,offset=32256 freedos.img /mnt/dos
    

    然后就可以随便拷贝文件进出了。

    现在需要写一个汇编程序,实现进入保护模式的功能,我想从最最最简单的做起,先仅仅完成段式保护模式,先不分页。在进入保护模式后,执行死循环。我的汇编程序代码如下:

    ; COM程序在dos中的加载位置,这个必须写100h,不写的话会报错,我也是第一次知道
    org 100h
    
    ;第一行指令跳转到GDT之下
    jmp main
    
    ;GDT
    gdt_start:
        dd 0
        dd 0
    ; 仅仅一个段,平坦模式
    gdt_code:
        dw 0xFFFF
        dw 0
        db 0
        db 0b10011010   ;9a
        db 0b11001111   ;cf
        db 0
    gdt_end:
    
    ;GDTR
    gdtr:
        dw gdt_end-gdt_start-1
        dd gdt_start
    
    ;保护模式下的死循环代码
    _loop_:
    jmp $
    
    ;主程序
    main:
    ; 必须手工自己计算地址:段<<4+offset
    mov eax,cs
    shl eax,4
    add [gdtr+2],eax
    add eax, _loop_
    
    ; 这里有个技巧,采用堆栈实现跨段长跳。这需要对jmp指令比较熟悉,进入保护模式的长跳需要重新加载段寄存器,所以需要的jmp指令为:
    ; 0x66 0xea $4字节的段内偏移 $2字节的段选择子
    ; 所以在jmp指令的操作数中要构造6个字节的数据,这里采用了堆栈
    push dword 0x08
    push eax
    mov bx, sp
    
    ; 进入保护模式的核心3步骤:
    ; 1. 加载准备好的GDTR
    ; 2. 开启A20
    ; 3. 开启cr0保护模式
    ; 暂时不准备分页。
    lgdt [gdtr]
    
    cli
    in al, 0x92
    or al, 0x02
    out 0x92, al
    
    mov eax,cr0
    or eax,1
    mov cr0,eax
    
    ; 按照准备好的6字节地址和段选择子,执行66 ea长跳!
    jmp dword far [bx]
    

    用nasm编译之:

    nasm -f bin rtime.asm -o pro.com
    

    将这个pro.com放入freedos的虚拟磁盘,umount虚拟磁盘后开启bochs虚拟机,进入实模式dos系统,执行 pro.com,死循环!

    然后,我尝试着将这个rtime.asm代码逐行手敲到debug的命令行,结果debug无法识别最后一步的 jmp dword far [bx]

    于是准备将其等价转换一下,决定手工写指令,即写入:

    db 0x66
    db 0xea
    dd $4字节偏移
    dw 8
    

    问题就在于这4字节偏移,这个偏移显然是在运行时才能算出来的,所以不能事先dd,需要在运行时填充,于是标号注之:

    org 100h
    
    jmp main
    
    gdt_start:
        dd 0
        dd 0
    gdt_code:
        dw 0xFFFF
        dw 0
        db 0
        db 0b10011010   ;9a
        db 0b11001111   ;cf
        db 0
    gdt_end:
    
    gdtr:
        dw gdt_end-gdt_start-1
        dd gdt_start
    
    _loop_:
    jmp $
    
    main:
    mov eax,cs
    shl eax,4
    mov ecx,eax
    add [gdtr+2],eax
    add eax, _loop_
    
    ; 填充4字节的偏移地址
    add [addr], eax
    
    lgdt [gdtr]
    
    cli
    in al, 0x92
    or al, 0x02
    out 0x92, al
    
    mov eax,cr0
    or eax,1
    mov cr0,eax
    
    ; 直接插入指令的方式
    db 0x66
    db 0xea
    addr:dd 0x00000000
    dw 0x08
    

    非常不错,这下可以手敲进debug了。但是由于debug程序里逐行汇编无法使用标号,所以需要把所有的标号转化为固定的地址偏移,于是如下:

    org 100h
    
    ;jmp main
    jmp 150
    
    ; 110:GDT载入110
    gdt_start:
        dd 0
        dd 0
    gdt_code:
        dw 0xFFFF
        dw 0
        db 0
        db 0b10011010   ;9a
        db 0b11001111   ;cf
        db 0
    gdt_end:
    
    ; 130:GDTR载入130
    gdtr:
        dw gdt_end-gdt_start-1
        dd gdt_start
    
    ; 140:32位程序载入140
    _loop_:
    jmp $
    
    ; 150:main载入150
    main:
    mov eax,cs
    shl eax,4
    mov ecx,eax
    add [gdtr+2],eax
    add eax, _loop_
    
    ; 填充4字节的偏移地址
    add [addr], eax
    
    lgdt [gdtr]
    
    cli
    in al, 0x92
    or al, 0x02
    out 0x92, al
    
    mov eax,cr0
    or eax,1
    mov cr0,eax
    jmp 190
    
    ; 190:为了确定addr的地址,这里将jmp far指令单独载入到固定的地址,短跳到达
    db 0x66
    db 0xea
    ; 192:addr自然就是192咯
    addr:dd 0x00000000
    dw 0x08
    

    接下来就要把这些逐行敲入debug了:
    在这里插入图片描述
    每写完一段,可以用 -u $段内偏移 看一下对不对:
    在这里插入图片描述
    如果哪个地址的指令不对,可以用 -a $地址 来修改,比如你写指令的时候,加不加dword修饰符可能结果是不一样,如果真的不行就需要手工写入指令了,比如用db,dd,dw这种,或者直接用debug的 -e $地址 来写指令。

    此外,还要注意的是,我们把标号化作了地址偏移,但是我们自己规定的130,140,150这种可能只是一厢情愿,由于有偏移问题,必须用 -u 指令反汇编确认,如果有问题,则需要用 -a 指令来修正。

    一切结束后,用 -n -rcx 指令存文件,执行,死循环。这意味着成功了。


    以上这些其实是要被主音吉他手耻笑鄙视的,因为原教旨主义者不使用nasm工具,只能用debug,然而我还是用了,其实我是先用nasm把程序调通,然后照着调通之后的程序的反汇编抄到debug命令行逐行汇编的,然后把抄的过程隐藏,就留下一个手工敲进debug命令行的指令的截图。

    然后显得我好像是直接逐行汇编的,但其实把截图多截宽一点,旁边还有呢
    在这里插入图片描述
    哈哈。


    本文只是做到了进入保护模式,没有做任何事情,这没有意义。
    但其实,进入保护模式后,世界就是你的了,打印个字符串那是教科书式的,更没有意义,而且代码还非常冗长,所以我什么也不打印,只是执行 jmp $

    你可以再准备一个页表,然后开启分页,打印一些东西,然后呢…厄… 再准备一个页表,执行打印一些别的东西,厄…准备一个时钟中断处理程序,每一次时钟中断到来,CR3在两个页表间切换,同时save/restore寄存器上下文…厄…这就是个多任务现代操作系统了。

    以为我会写下去吗?写上面这段话的内容?看上去很有吸引力,也很有成就感,但我不会去写的。

    我面试过会写这些的人,也和很多会写这种的人纯粹的技术交流无关利益,貌似很懂操作系统的样子,能说明白段式,页式,段页式,平坦内存等等X86系列兼容处理器一系列复杂且恶心的特性,也确实有作品,所谓的《自己动手写操作系统》这种,从一个MBR引导进保护模式,交叉打印不同的字符,也许很多人都觉得,太厉害了…

    然而他不懂如何评价进程调度算法,不知道LRU的实质,无法写出一个高效的并发链表,更不懂网络。操作系统的精髓不是X86处理如何引导进保护模式,操作系统的精髓是进程,内存,IO等一系列时间空间的分配和调度问题,这是个及其复杂的运筹学博弈问题,背后有复杂的博弈论,运筹学,哲学,算法等等看上去很虚但又很难企及的东西。

    就不说那些博士才操心的算法方面的事了,同样是在工程领域,写个引导进保护模式交叉打印字符远不如写个LinuxBIOS,Grub这种引导其它程序的程序(而不是自己在那无聊地打印字符串)更加有意义。


    那么本文的意义何在?

    本文的意义在于 运行时进入保护模式 。这不同于机器刚上电系统启动就进入保护模式。不同点在哪里呢?系统初启时的内存是空的,这意味着引导程序可以将其他的代码任意设置位置,机器的状态寄存器也是唯一由引导程序决定的,可是运行时却不同,运行时的dos系统已经有一个内核在内存里了,替换它需要格外小心,幸亏dos是单任务的,否则便更加麻烦,不信你把Linux变身到Windows试试?鸠占鹊巢正在执行期间,万一来了个调度事件,就会全盘皆输。

    所以运行时进保护模式会 更麻烦些

    这就是本文的全部意义。

    话说,既然一般的系统在上电初启时都会进入保护模式,为啥要有实模式?这又是Intel在搞事情。

    先不回答这个问题,先看看 “既然有简单的分页并且工作的还那么好,为什么还要有分段?” 而且,大部分的操作系统,比如Linux,Windows都是例行公事用平坦模式绕开分段的,这么看来分段显得毫无意义。

    这全是Intel为了兼容老旧处理器而搞的。也就是说,如果说最初的8086有一个机制或者特性,8088上就要支持,随后的286,386都要支持,P4,Core i5/i7大概率都要支持,即前一代的处理支持什么,后面的处理器大概率都要支持,最终的效果就是Intel处理器越发臃肿。

    后果是什么?我就不说为操作系统的实现增加复杂性了,另一个重要的毒副作用就是为很多人炫耀技巧提供了及其广阔的空间,不然也就没人觉得能背下段寄存器格式的人很厉害了,毕竟本来无一物,何处惹尘埃。

    Intel作为个人计算机独钟的微处理器提供商,推进了近40年个人计算机产业的发展,我们有目共睹的是,个人计算机从零到100的发展速度远远快于大型机,工作站以及服务器,这是因为个人计算机的需求是繁杂的,特别是游戏,互联网等等客户端App需要处理器在各个方面不断进化。与此相对,工作站,服务器则只需要保持高并发高吞吐即可。

    个人计算机的不断进化不断复杂意味着向后兼容的压力非常大,但又不得不做,因此Intel处理器就成了现在的样子。

    很多别的处理器一开始就是保护模式的,根本就没有实模式一说,这种专门迎合Multics/UNIX的设计,因为UNIX一开始就是进程保护隔离的,UNIX并非从CP/M这种及其简单的系统进化而来,它一开始就是设计出来的。

    既然操作系统提供进程间内存的隔离保护,那么处理器做到即可,不多也不少,这种简洁的设计和Intel的风格是相对的。


    写完了,2019年8月10日 5:40. 超强台风利奇马已经登陆,杭州风雨大作,但是雨量没有达到预期。迷茫一会儿。


    现在7:40,独坐窗前看风起云涌,一家人都醒了,疯子在做早饭,小小在刷抖音,安安在学步车里欢呼,嘟嘟乱跑,我依然在作文。

    超强台风即将登陆前的八小时,800019年8月9日 周五,下午六点,皮鞋厂工人都在等着下班。
    温州皮鞋厂wu老板发了邮件:
    由于此次台风非常强烈,登陆地点离本厂非常近,对本厂影响巨大,或对本厂业务产生严重影响!
    于此,特公告,所有车间,所有工人,务必值守岗位,按照日常制度执行,若有急事需请假,电话务必保持24小时畅通,请随时处理紧急事务。
    货单照常发货,值班邮件请查收,如遇突发事件按流程请报工伤。
    经理与你同在!
    – 皮鞋二厂/三厂,行政处
    – 钦此!

    【800019年8月10日,皮鞋厂厂房在台风登陆时轰然崩摧,wu老板发文,经理穿着湿皮鞋逃跑,若干工人death has no place】
    在这里插入图片描述
    杭州看不见了,因为在台风云下面…


    浙江温州皮鞋湿,下雨进水不会胖!
    皮鞋湿了,是祸,然而皮鞋不胖,是福报!
    浙江温州皮鞋湿,下雨进水不会胖!

    展开全文
  • 1.保护模式(Protected mode)

    千次阅读 2021-09-18 14:58:14
    0.什么是保护模式? x86CPU的3个模式: 实模式,保护模式和虚拟8086模式. Intel白皮书对保护模式有详细介绍(详情可参考Intel白皮书第三卷) In protected mode, the Intel 64 and IA-32 architectures provide a ...

    目录

    0.什么是保护模式?

    1.保护模式特点

    <1>段的机制

    <2>页的机制


    0.什么是保护模式?

    x86CPU的3个模式: 实模式,保护模式和虚拟8086模式.

    CPU两大架构:

    RISC(Reduced Instruction Set Computer),中文即“精简指令集计算机”。RISC构架的指令格式和长度通常是固定的(如ARM是32位的指令)、且指令和寻址方式少而简单、大多数指令在一个周期内就可以执行完毕.

    CISC(complex instruction set computer)即复杂指令集计算机,在20世纪90年代前被广泛的使用,其特点是通过存放在只读存储器中的微码(microcode)来控制整个处理器的运行.工作分为5个阶段,取指令,指令译码,访存取数,执行指令,结果写回.

    The IA-32 architecture supports three operating modes and one quasi-operating mode:
    Protected mode — This is the native operating mode of the processor. It provides a rich set of architectural features, flexibility, high performance and backward compatibility to existing software base.
    Real-address mode — This operating mode provides the programming environment of the Intel 8086 processor, with a few extensions (such as the ability to switch to protected or system management mode).
    System management mode (SMM) — SMM is a standard architectural feature in all IA-32 processors, beginning with the Intel386 SL processor. This mode provides an operating system or executive with a transparent mechanism for implementing power management and OEM differentiation features. SMM is entered through activation of an external system interrupt pin (SMI#), which generates a system managementinterrupt (SMI). In SMM, the processor switches to a separate address space while saving the context of thecurrently running program or task. SMM-specific code may then be executed transparently. Upon returningfrom SMM, the processor is placed back into its state prior to the SMI.
    Virtual-8086 mode — In protected mode, the processor supports a quasi-operating mode known as virtual- 8086 mode. This mode allows the processor execute 8086 software in a protected, multitasking environment.
    Intel 64 architecture supports all operating modes of IA-32 architecture and IA-32e modes:
    IA-32e mode — In IA-32e mode, the processor supports two sub-modes: compatibility mode and 64-bit mode. 64-bit mode provides 64-bit linear addressing and support for physical address space larger than 64 GBytes. Compatibility mode allows most legacy protected-mode applications to run unchanged.

    Intel白皮书对保护模式有详细介绍(详情可参考Intel白皮书第三卷)

    In protected mode, the Intel 64 and IA-32 architectures provide a protection mechanism that operates at both the segment level and the page level. This protection mechanism provides the ability to limit access to certain segments or pages based on privilege levels (four privilege levels for segments and two privilege levels for pages). For example, critical operating-system code and data can be protected by placing them in more privileged segments than those that contain applications code. The processor’s protection mechanism will then prevent application code from accessing the operating-system code and data in any but a controlled, defined manner. Segment and page protection can be used at all stages of software development to assist in localizing and detecting design problems and bugs. It can also be incorporated into end-products to offer added robustness to operating systems, utilities software, and applications software. When the protection mechanism is used, each memory reference is checked to verify that it satisfies various protection checks. All checks are made before the memory cycle is started; any violation results in an exception. Because checks are performed in parallel with address translation, there is no performance penalty. The protection checks that are performed fall into the following categories:

    Limit checks.

    Type checks.

    Privilege level checks.

    • Restriction of addressable domain.

    • Restriction of procedure entry-points.

    • Restriction of instruction set.

    1.保护模式特点

    <1>段的机制

    #include <stdio.h>
    #include <stdlib.h>
    
    int val = 0;
    
    int main()
    {
    	__asm
    	{
    		//保护模式中段寄存器都有自身特定属性
    
    		mov dword ptr ds:[val], 1	//这行指令将会成功执行 ds段寄存器拥有可写属性且目标操作数为有效线形地址
    		mov ax, cs					//将cs段寄存器的段选择子读取到ax寄存器
    		mov ds, ax					//这行指令使ds段寄存器拥用同cs相同属性(可读可执行不可写)
    		mov dword ptr ds:[val], 1	//这行指令执行时将会出错因为此时ds段寄存器不具备写属性
    	}
    
    	system("pause");
    	return 0;
    }

    <2>页的机制

    通过windbg查看GDT(Global Descriptor Table)表(GDT表介绍)

    #include <stdio.h>
    #include <stdlib.h>
    
    int val = 0;
    
    int main()
    {
    	__asm
    	{
    		mov eax, dword ptr ds:[0x80b99000]	//指令执行会失败 0x80b99000为有效线性地址但所对应物理页属性禁止R3访问
    		mov eax, dword ptr ds:[0]			//指令执行会失败 如果给0线性地址挂有效物理页且属性可以允许R3访问这行指令便会成功
    	}
    
    	system("pause");
    	return 0;
    }

    在后续保护模式分析中将会详细介绍保护模式段页机制

    展开全文
  • 实模式和保护模式的区别

    千次阅读 2020-07-15 20:08:37
    实模式和保护模式的简单对比。
  • redis关闭保护模式

    千次阅读 2021-03-16 16:12:49
    redis在启动的时候默认会启动一个保护模式,只有同一个服务器可以连接上redis。别的服务器连接不上这个redis 解决办法:关闭保护模式 1、进入redis安装目录找到conf目录下的redis.conf(/etc/redis/redis.conf)...
  • 谈谈Eureka的自我保护模式

    千次阅读 多人点赞 2019-03-14 00:16:09
    Eureka 一般是springcloud 的第一个...ok,回到正题,后来我通过慢慢的学习了解,这个和Eureka 自我保护模式有关 今天我们来聊聊这个问题 统一环境: SpringBoot 2.1.3.RELEASE 相关代码上传git仓库: eurekaServer: ...
  • x86汇编语言-从实模式到保护模式-Ubuntu学习环境
  • Windows10如何关闭电源保护模式

    千次阅读 2020-12-20 16:56:22
    展开全部首先,在Win10系统桌面上32313133353236313431303231363533e78988e69d...在接下来弹出的设置菜单中,点击选择“电源和睡眠设置”选项,就可以进入系统关闭电源保护模式了。扩展:1、Windows 10...
  • x86 实模式与保护模式

    千次阅读 2016-07-26 16:25:47
    0386开始,CPU有三种工作方式:实模式,保护模式和虚拟8086模式。只有在刚刚启动的时候是real-mode,等到操作系统运行起来以后就切换到protected-mode。实模式只能访问地址在1M以下的内存称为常规内存,我们把地址在...
  • 《x86汇编语言:从实模式到保护模式》配书文件包。内容包括第5、6、7、8、9、11、12、13、14、15、16和17章的汇编语言程序源代码,以及工具软件。还包括VirtualBox和Bochs软件的安装手册。
  • 买了《x86汇编语言:从实模式到保护模式》却一直找不到配套的源码,其他网站的源码要么不全,要么收费,无奈之下联系作者要了一套,是完整的书本源码+配套工具,故免费分享出来,为读者分忧,为作者分担。
  • 粗略阅读操作系统进入保护模式源码咯 ! ! setup.s (C) 1991 Linus Torvalds ! ! setup.s is responsible for getting the system data from the BIOS, ! and putting them into the appropriate places in ...
  • x86 汇编语言:从实模式到保护模式 + 代码(前13章) 加书签
  • 《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读...
  • redis 关闭保护模式

    千次阅读 2019-04-22 14:37:41
    2)或者,您可以通过编辑Redis配置文件并将保护模式选项设置为“no”来禁用保护模式,然后重新启动服务器。 3)如果您只是为了测试而手动启动服务器,请使用“ - 保护模式否”选项重新启动服务器。4)设置绑定地址或...
  • 提示redis服务有保护模式,需要解除 [root@localhost src]# telnet 192.168.56.56 6379 Trying 192.168.56.56... Connected to 192.168.56.56. Escape character is '^]'. -DENIED Redis is running in protected ...
  • 使用python解决IE保护模式报错
  • 06.保护模式

    千次阅读 2018-12-12 17:59:47
    为了让程序在内存中自由装载,处理器将内存划分为逻辑上的段,并在指令中使用段内偏移地址,保护模式下对内存的访问依然使用段地址和偏移地址,在每个段被访问之前必须先登记。登记的信息包括段起始地址、段的界限和...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 496,160
精华内容 198,464
关键字:

保护模式

友情链接: perceptron_buzzer.zip