精华内容
下载资源
问答
  • x86汇编语言:从实模式到保护模式 基本信息 作者: 李忠 王晓波 余洁 出版社:电子工业出版社 ISBN:9787121187995 上架时间:2013-1-10 出版日期:2013 年1月 开本:16开 页码:375 版次:1-1
  • 实模式和保护模式

    万次阅读 多人点赞 2016-06-13 16:21:59
    80386开始,CPU有三种工作方式:实模式,保护模式和虚拟8086模式。只有在刚刚启动的时候是real-mode,等到操作系统运行起来以后就切换到protected-mode。实模式只能访问地址在1M以下的内存称为常规内存,我们把地址...

    80386开始,CPU有三种工作方式:实模式保护模式虚拟8086模式。只有在刚刚启动的时候是real-mode,等到操作系统运行起来以后就切换到protected-mode。实模式只能访问地址在1M以下的内存称为常规内存,我们把地址在1M 以上的内存称为扩展内存。在保护模式下,全部32条地址线有效,可寻址高达4G字节的物理地址空间; 扩充的存储器分段管理机制和可选的存储器分页管理机制,不仅为存储器共享和保护提供了硬件支持,而且为实现虚拟存储器提供了硬件支持; 支持多任务,能够快速地进行任务切换(switch)保护任务环境(context); 4个特权级和完善的特权检查机制,既能实现资源共享又能保证代码和数据的安全和保密及任务的隔离; 支持虚拟8086方式,便于执行8086程序。


    实模式(Real Mode)

    它是 Intel公司80286及以后的x86(80386,8048680586)兼容处理器CPU)的一种操作模式。实模式被特殊定义为20位地址内存可访问空间上,这就意味着它的容量是220次幂(1M)的可访问内存空间(物理内存和BIOS-ROM),软件可通过这些地址直接访问BIOS程序和外围硬件。实模式下处理器没有硬件级内存保护概念和多道任务的工作模式。但是为了向下兼容,所以80286及以后的x86系列兼容处理器仍然是开机启动时工作在实模式下80186和早期的处理器仅有一种操作模式,就是后来我们所定义的实模式。实模式虽然能访问到1M的地址空间,但是由于BIOS的映射作用(即BIOS占用了部分空间地址资源),所以真正能使用的物理内存空间(内存条),也就是在640k924k之间。1M 地址空间组成是由 16位的段地址和16位的段内偏移地址组成的。用公式表示为:物理地址=左移4位的段地址+偏移地址

     

    286处理器体系结构引入了地址保护模式的概念,处理器能够对内存及一些其他外围设备做硬件级的保护设置(保护设置实质上就是屏蔽一些地址的访问)。使用这些新的特性,然而必不可少一些额外的在80186及以前处理器没有的操作规程。自从最初的x86微处理器规格以后,它对程序开发完全向下兼容,80286芯片被制作成启动时继承了以前版本芯片的特性,工作在实模式下,在这种模式下实际上是关闭了新的保护功能特性,因此能使以往的软件继续工作在新的芯片下。直到今天,甚至最新的x86处理器都是在计算机加电启动时都是工作在实模式下,它能运行为以前处理器芯片写的程序.

     

    DOS操作系统(例如 MS-DOS,DR-DOS)工作在实模式下,微软Windows早期的版本(它本质上是运行在DOS上的图形用户界面应用程序,实际上本身并不是一个操作系统)也是运行在实模式下,直到Windows3.0,它运行期间既有实模式又有保护模式,所以说它是一种混合模式工作。它的保护模式运行有两种不同意义(因为80286并没有完全地实现80386及以后的保护模式功能)

     

    1〉“标准保护模式”:这就是程序运行在保护模式下;

    2〉“虚拟保护模式(实质上还是实模式,是实模式上模拟的保护模式)”:它也使用32位地址寻址方式。Windows3.1彻底删除了对实模式的支持。在80286处理器芯片以后,Windows3.1成为主流操作系统(Windows/80286不是主流产品)。目前差不多所有的X86系列处理器操作系统(LinuxWindows95 and laterOS/2等)都是在启动时进行处理器设置而进入保护模式的。

     

    实模式工作机理:

    1> 对于8086/8088来说计算实际地址是用绝对地址对1M求模8086地址线的物理结构:20,也就是它可以物理寻址的内存范围为2^20个字节,即1 M空间,但由于8086/8088所使用的寄存器都是16位,能够表示的地址范围只有0-64K,这和1M地址空间来比较也太小了,所以为了在8086/8088下能够访问1M内存,Intel采取了分段寻址的模式:16位段基地址:16位偏移EA。其绝对地址计算方法为:16位基地址左移4+16位偏移=20位地址。  比如:DS=1000H EA=FFFFH 那么绝对地址就为:10000H +
    0FFFFH = 1FFFFH 地址单元
     。通过这种方法来实现使用16位寄存器访问1M的地址空间,这种技术是处理器内部实现的,通过上述分段技术模式,能够表示的最大内存为:
     
    FFFFh: FFFFh=FFFF0h+FFFFh=10FFEFh=1M+64K-16Bytes1M多余出来的部分被称做高端内存区HMA。但8086/8088只有20位地址线,只能够访问1M地址范围的数据,所以如果访问100000h~10FFEFh之间的内存(大于1M空间),则必须有第21根地址线来参与寻址(8086/8088没有)。因此,当程序员给出超过1M100000H-10FFEFH)的地址时,因为逻辑上正常,系统并不认为其访问越界而产生异常,而是自动从0开始计算,也就是说系统计算实际地址的时候是按照对1M求模的方式进行的,这种技术被称为wrap-around

     

     

    2> 对于80286或以上的CPU通过A20 GATE来控制A20地址线  技术发展到了 80286,虽然系统的地址总线由原来的20根发展为24根,这样能够访问的内存可以达到2^24=16M,但是Intel在设计80286时提出的目标是向下兼容,所以在实模式下,系统所表现的行为应该和8086/8088所表现的完全一样,也就是说,在实模式下,80386以及后续系列应该和8086/8088完全兼容仍然使用A20地址线。所以说80286芯片存在一个BUG:它开设A20地址线。如果程序员访问100000H-10FFEFH之间的内存,系统将实际访问这块内存(没有wrap-around技术),而不是象8086/8088一样从0开始。我们来看一副图:




    为了解决上述兼容性问题,IBM使用键盘控制器上剩余的一些输出线来管理第21根地址线(从0开始数是第20根) 的有效性,被称为A20 Gate

    1> 如果A20 Gate被打开,则当程序员给出100000H-10FFEFH之间的地址的时候,系统将真正访问这块内存区域;

    如果A20 Gate被禁止,则当程序员给出100000H-10FFEFH之间的地址的时候,系统仍然使用8086/8088的方式即取模方式(8086仿真)。绝大多数IBM PC兼容机默认的A20 Gate是被禁止的。现在许多新型PC上存在直接通过BIOS功能调用来控制A20 Gate的功能。


    保护模式(Protected Mode)

    上面所述的内存访问模式都是实模式,在80286以及更高系列的PC中,即使A20 Gate被打开,在实模式下所能够访问的内存最大也只能为10FFEFH,尽管它们的地址总线所能够访问的能力都大大超过这个限制。为了能够访问10FFEFH以上的内存,则必须进入保护模式。

    (286Intel 80286的另一种叫法) 它又被称作为虚拟地址保护模式。尽管在Intel 80286手册中已经提出了虚地址保护模式,但实际上它只是一个指引,真正的32位地址出现在Intel 80386上。保护模式本身是80286及以后兼容处理器序列之后产成的一种操作模式,它具有许多特性设计为提高系统的多道任务系统的稳定性。例如内存的保护分页机制硬件虚拟存储的支持。现代多数的x86处理器操作系统都运行在保护模式下,包括Linux, Free BSD, Windows3.0(它也运行在实模式下,为了和Windows 2.x应用程序兼容)及以后的版本。

     

    80286及以后的处理器另一种工作模式是实模式(仅当系统启动的一瞬间),本着向下兼容的原则屏蔽保护模式特性,从而容许老的软件能够运行在新的芯片上。作为一个设计规范,所有的x86系列处理器,除嵌入式Intel80387之外,都是系统启动工作在实模式下,确保遗留下的操作系统向下兼容。它们都必须被启动程序(操作系统程序最初运行代码)重新设置而相应进入保护模式的,在这之前任何的保护模式特性都是无效的。在现代计算机中,这种匹配进入保护模式是操作系统启动时最前沿的动作之一。

     

    在被调停的多道任务程序中,它可以从新工作在实模式下是相当可能的。保护模式的特性是阻止被其他任务或系统内核破坏已经不健全的程序的运行,保护模式也有对硬件的支持,例如中断运行程序,移动运行进程文档到另一个进程和置空多任务的保护功能。

     

    386及以后系列处理器不仅具有保护模式又具有32位寄存器,结果导致了处理功能的混乱,因为80286虽然支持保护模式,但是它的寄存器都是16位的,它是通过自身程序设定而模拟出的32位,并非32位寄存器处理。归咎于这种混乱现象,它促使Windows/386 及以后的版本彻底抛弃80286的虚拟保护模式,以后保护模式的操作系统都是运行在80386以上,不再运行在80286(尽管80286模式支持保护模式),所以说80286是一个过渡芯片,它是一个过渡产品

     

    尽管 286386处理器能够实现保护模式和兼容以前的版本,但是内存的1M以上空间还是不易存取,由于内存地址的回绕,IBM PC XT (现以废弃)设计一种模拟系统,它能过欺骗手段访问到1M以上的地址空间,就是开通了A20地址线。在保护模式里,前32个中断为处理器异常预留,例如,中断0D(十进制13)常规保护故障和中断00是除数为零异常。

     

    如果要访问更多的内存,则必须进入保护模式,那么,在保护模式下, A20
    Gate对于内存访问有什么影响呢?

    为了搞清楚这一点,我们先来看一看A20的工作原理。A20,从它的名字就可以看出来,其实它就是对于A20(从0开始数)的特殊处理(也就是对第21根地址线的处理)。如果A20 Gate被禁止,对于80286来说,其地址为24根地址线,其地址表示为EFFFFF;对于80386极其随后的32根地址线芯片来说,其地址表示为FFEFFFFF。这种表示的意思是:



    1> 如果A20
    Gate被禁止。则其第A20CPU做地址访问的时候是无效的,永远只能被作为0。所以,在保护模式下,如果A20
    Gate被禁止,则可以访问的内存只能是奇数1M段,即1M,3M,5M…,也就是00000-FFFFF, 200000-2FFFFF,300000-3FFFFF…

    2如果A20 Gate被打开。则其第20-bit是有效的,其值既可以是0,又可以是1。那么就可以使A20线传递实际的地址信号。如果A20 Gate被打开,则可以访问的内存则是连续的。


    实模式和保护模式的区别

    从表面上看,保护模式和实模式并没有太大的区别,二者都使用了内存段、中断和设备驱动来处理硬件,但二者有很多不同之处。我们知道,在实模式中内存被划分成段,每个段的大小为 64KB ,而这样的段地址可以用 16 位来表示。内存段的处理是通过和段寄存器相关联的内部机制来处理的,这些段寄存器( CS  DS  SS ES )的内容形成了物理地址的一部分。具体来说,最终的物理地址是由 16 位的段地址和 16 位的段内偏移地址组成的。用公式表示为:物理地址 = 左移 4 位的段地址 + 偏移地址。

    在保护模式下,段是通过一系列被称之为  描述符表  的表所定义的。段寄存器存储的是指向这些表的指针。用于定义内存段的表有两种:全局描述符表 (GDT) 局部描述符表 (LDT)  GDT 是一个段描述符数组,其中包含所有应用程序都可以使用的基本描述符。在实模式中,段长是固定的 (  64KB) ,而在保护模式中,段长是可变的,其最大可达 4GB LDT 也是段描述符的一个数组。与 GDT 不同, LDT 是一个段,其中存放的是局部的、不需要全局共享的段描述符。每一个操作系统都必须定义一个 GDT ,而每一个正在运行的任务都会有一个相应的 LDT 。每一个描述符的长度是 8 个字节,格式如图 3 所示。当段寄存器被加载的时候,段基地址就会从相应的表入口获得。描述符的内容会被存储在一个程序员不可见的影像寄存器 (shadow register) 之中,以便下一次同一个段可以使用该信息而不用每次都到表中提取。物理地址由 16 位或者 32 位的偏移加上影像寄存器中的基址组成。实模式和保护模式的不同可以从下图很清楚地看出来。


    实模式地址



    保护模式地址



    总结

    保护模式同实模式的根本区别是进程内存受保护与否。可寻址空间的区别只是这一原因的果。实模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,系统程序和用户程序没有区别对待,而且每一个指针都是指向"实在"的物理地址。这样一来,用户程序的一个指针如果指向了系统程序区域或其他用户程序 区域,并改变了值,那么对于这个被修改的系统程序或用户程序,其后果就很可能是灾难性的。为了克服这种低劣的内存管理方式,处理器厂商开发出保护模式。这 样,物理内存地址不能直接被程序访问,程序内部的地址(虚拟地址)要由操作系统转化为物理地址去访问,程序对此一无所知。

    至此,进程有了严格的边界,任何其他进程根本没有办法访问不属于自己的物理内存区域,甚至在自己的虚拟地址范围内也不是可以任意访问的,因为有一些虚拟区域已经被放进一些公共系统运行库。这些区域也不能随便修改,若修改就会有: SIGSEGV(linux 段错误);非法内存访问对话框(windows 对话框)。

    CPU启动环境为16位实模式,之后可以切换到保护模式。但从保护模式无法切换回实模式 。


    对于80X86处理器来说,从80386处理器开始,除了以前的实模式外,还增添了 
    保护模式和V86模式。实模式和V86模式都是为了和8086兼容而设置的。 
      
    实模式: 
          内存寻址方式为:段式寻址,即物理地址=段地址*16   +   段内偏移地址 
          可寻址任意地址,所有指令都相当于工作在特权级。 
          dos工作在实模式下 
    保护模式: 
          内存寻址方式为:支持内存分页和虚拟内存 
          支持多任务,可依靠硬件用一条指令即可实现任务切换,不同任务可工作在 不同的优先级下,操作系统工作在最高优先级0上,应用程序则运行       在较低优先级 上。从实模式到保护模式,需要建立GDT、IDT等数据表,然后通过修改控制寄存 器CR0的控制位(位0)来实现。 
          Windows工作在保护模式下。 
    虚拟8086模式: 
            内存寻址方式:段式寻址,与实模式一样 
            支持多任务和内存分页 
            v86模式主要是为了在保护模式下兼容以前的实模式应用,即可支持多任务, 
            但每个任务都是实模式的工作方式。 

    另外,中断和异常等的处理对于不同的工作模式都是不同的,具体的可以去 参看一些相关书籍。 


    以上转自:http://blog.csdn.net/kennyrose/article/details/7563649

    以下转自:http://ahhqlrg.lofter.com/post/1ccdac85_7882945

     Intel的实模式和保护模式,必须要知道的...

    一、Intel手册如是说 

            Intel的IA-32架构支持三种基本操作模式:保护模式、实地址模式和系统管理模式。操作模式决定了哪些指令和架构相关的特性是可用的:

    • 保护模式(protected mode)——是处理器的根本模式,在保护模式下,可以为直接运行的实地址模式程序提供保护的、多任务的环境,这种特性被称作“虚拟8086模式(virtual 8086 mode)”,尽管“虚拟8086”模式并不是一种真正的处理器模式;virtual 8086 mode实际上是保护模式的一种属性,在保护模式下,可以向任何任务提供这种属性。
    • 实地址模式(real-addess mode)——该模式以扩展的方式实现了8086CPU的程序运行环境(就像切换到保护模式和系统管理模式一样)。处理器在刚刚上电或者重启后的时候,处于实地址模式。
    • 系统管理模式(system manangement mode,SMM)——该模式提供操作系统或者执行程序一种透明的机制去实现平台相关的特性,例如电源管理和系统安全。当来自外部的或者APIC控制器的SMM中断pin脚被触发时,处理器在下列情况进入SMM。在SMM下,处理器切换到一个独立的地址空间,同时保存当前运行的程序或任务的上下文。SMM相关的代码可透明的执行。当SMM模式返回时,处理器返回SMI(system manangement interrupt)前的工作模式。SMM模式在Intel 386 SL和Intel 486 SL处理器时被引入,在Pentium家族时成为标准的IA-32架构的特性。

            Intel 64 架构增加了IA-32e模式,IA-32e模式又包含两种子模式:

    • 兼容模式(Compatibility mode)——兼容模式允许绝大部分16-bit/32-bit的应用可以无需重新编译就能直接在64bits的操作系统下运行。简单的,在IA-32架构下将“兼容子模式”称为“兼容模式”。兼容模式的可执行环境与Section 3.2定义的一致。兼容模式也支持所有的特权级别,在64-bit和保护模式下。运行在虚拟8086模式下的或用到硬件任务管理的程序,在此模式下无法工作。兼容模式做代码段基础被操作系统所启动,这意味着单独的64-bit操作系统可以通过64-bit模式支持64-bit应用,也可以通过兼容模式支持32-bit程序(无需再编译)。兼容模式类似于32-bit的保护模式,应用程序只能访问前4G的线性地址空间。兼容模式使用16-bit和32-bit的地址以及操作数长度。类似于保护模式,该模式允许应用程序通过PAE(物理地址扩展,Physical Address Extention)访问超过4G的物理内存。
    • 64-bit模式(64-bit  mode)——该模式使操作系统在64-bit线性地址空间上运行应用程序,在IA-32架构下,64-bit子模式被简称为64-bit模式。64-bit模式将通用寄存器和SIMD寄存器从8个扩展到16个。通用寄存器被增加到64bits宽。该模式还引入了新的操作数前缀(REX)来访问寄存器扩展。64-bit模式作为代码段基础被操作系统激活,该模式默认地址长度是64bits,但默认操作数长度为32bits,默认的操作数长度可以在指令级别被覆盖,通过使用REX操作数前缀结合操作数大小覆盖前缀。REX前缀被用来在64-bit模式下指定一个64-bit的操作数,通过这种机制,很多已经存在的指令代码可以被直接升级到访问64-bit地址空间和64-bit寄存器。        
    二、实模式的历史原因 

            早在1978年,Intel研制了8086处理器,8086处理有16-bit寄存器和16-bit的外部数据总线,但是却能够访问20-bit的地址(包含1MByte的地址空间)。8086通过引入“分段(segmentation)机制”来解决这个问题。在分段机制下,一个16bits的段寄存器包含了一个长度是64KBytes的“段”空间的起始指针。通过1次时可用一个段寄存器,8086处理器可以无需在段间切换就能寻址高达256KBytes。因此,通过段寄存器+16bits的指针就能访问20bits的地址空间,共1Mbytes。

            虽然8086/8088处理器采用的地址空间大小与外部总线位宽不一致,但是这显得很“别扭”,并且在1985年80386时被统一,地址空间和外部总线位宽均为32bits。

            为了兼容8086,这种分段管理内存的方式一直被保留下来。当然,实模式不仅仅是分段内存管理,而是“程序的运行环境”,所为的运行环境,还包括寄存器的长度。

    三、实模式和保护模式的区别 

            个人理解,因为x86的CPU在启动时首先处于实模式,也就8086的16位模式,主要特点有:

    • 内存管理采用段+偏移的方式进行;
    • 最大寻指位2的20次方,即1Mbyte;
    • 没有安全级别,直接操作CPU的各种功能;
    • 没有分页功能,没有虚拟地址的概念,只有物理地址;
    • 各种寄存器的位宽基本上是16位的(段寄存器为20位);

            实模式就是一种比较原始的模式,能看到CPU设计的初始过程。

            保护模式是32位模式,但是可以兼容16位的实模式,保护模式的特点有:

    • 内存管理采用段式+页式的方式;
    • 最大寻指位2的32次方,即4G,还可以通过PAE模式访问超过4G的部分。
    • 有4个安全级别,内存操作时有安全检查;
    • 因为有了分页功能,因此有虚拟地址和物理地址的区别;
    • 各种寄存器的位宽基本上都是32位的,但是可以兼容同名的16位寄存器;
    • 可以兼容实地址模式,也即实地址模式的程序无需再编译即可跑在保护模式下。

            对于64位CPU来说,兼容模式和64位模式也基本上一个道理,兼容模式为了兼容32位,也就兼容了16位,而64位模式本身不去兼容32位,是纯净的64位。

    四、实模式对OS的意义 

            仅从Linux操作系统考虑。因为32位CPU一上电后,首先处于实模式下,所以实模式是OS必须经历的一种模式。既然保护模式可以兼容实模式,那么CPU为什么还要提供专门的实模式呢?是否OS可以直接从实模式跳转到保护模式呢?

            实话说,这两个问题我也不太好回答。为了钱?对Linux系统来说,从实模式到保护模式的过程,实际上主要就是为了初始化保护模式的过程。从实际模式切换到保护模式不是开开关、按按钮,而需要准备工作,主要是为了开启分页机制做准备,要分页,必须有页表、段描述符,这些都需要通过内存初始化。关于具体的过程,请参考《Linux内核启动保护模式的过程》。

            个人认为实模式就是在CPU启动的时刻的低级模式,需要OS无需在该模式上做过多的事情,而仅仅为向高级模式转化做准备。

    展开全文
  • x86 实模式与保护模式

    千次阅读 2016-07-26 16:25:47
    0386开始,CPU有三种工作方式:实模式,保护模式和虚拟8086模式。只有在刚刚启动的时候是real-mode,等到操作系统运行起来以后就切换到protected-mode。实模式只能访问地址在1M以下的内存称为常规内存,我们把地址在...

    0386开始,CPU有三种工作方式:实模式保护模式虚拟8086模式。只有在刚刚启动的时候是real-mode,等到操作系统运行起来以后就切换到protected-mode。实模式只能访问地址在1M以下的内存称为常规内存,我们把地址在1M 以上的内存称为扩展内存。在保护模式下,全部32条地址线有效,可寻址高达4G字节的物理地址空间; 扩充的存储器分段管理机制和可选的存储器分页管理机制,不仅为存储器共享和保护提供了硬件支持,而且为实现虚拟存储器提供了硬件支持; 支持多任务,能够快速地进行任务切换(switch)保护任务环境(context); 4个特权级和完善的特权检查机制,既能实现资源共享又能保证代码和数据的安全和保密及任务的隔离; 支持虚拟8086方式,便于执行8086程序。


    实模式(Real Mode)

    它是 Intel公司80286及以后的x86(80386,8048680586)兼容处理器CPU)的一种操作模式。实模式被特殊定义为20位地址内存可访问空间上,这就意味着它的容量是220次幂(1M)的可访问内存空间(物理内存和BIOS-ROM),软件可通过这些地址直接访问BIOS程序和外围硬件。实模式下处理器没有硬件级内存保护概念和多道任务的工作模式。但是为了向下兼容,所以80286及以后的x86系列兼容处理器仍然是开机启动时工作在实模式下80186和早期的处理器仅有一种操作模式,就是后来我们所定义的实模式。实模式虽然能访问到1M的地址空间,但是由于BIOS的映射作用(即BIOS占用了部分空间地址资源),所以真正能使用的物理内存空间(内存条),也就是在640k924k之间。1M 地址空间组成是由 16位的段地址和16位的段内偏移地址组成的。用公式表示为:物理地址=左移4位的段地址+偏移地址

     

    286处理器体系结构引入了地址保护模式的概念,处理器能够对内存及一些其他外围设备做硬件级的保护设置(保护设置实质上就是屏蔽一些地址的访问)。使用这些新的特性,然而必不可少一些额外的在80186及以前处理器没有的操作规程。自从最初的x86微处理器规格以后,它对程序开发完全向下兼容,80286芯片被制作成启动时继承了以前版本芯片的特性,工作在实模式下,在这种模式下实际上是关闭了新的保护功能特性,因此能使以往的软件继续工作在新的芯片下。直到今天,甚至最新的x86处理器都是在计算机加电启动时都是工作在实模式下,它能运行为以前处理器芯片写的程序.

     

    DOS操作系统(例如 MS-DOS,DR-DOS)工作在实模式下,微软Windows早期的版本(它本质上是运行在DOS上的图形用户界面应用程序,实际上本身并不是一个操作系统)也是运行在实模式下,直到Windows3.0,它运行期间既有实模式又有保护模式,所以说它是一种混合模式工作。它的保护模式运行有两种不同意义(因为80286并没有完全地实现80386及以后的保护模式功能)

     

    1〉“标准保护模式”:这就是程序运行在保护模式下;

    2〉“虚拟保护模式(实质上还是实模式,是实模式上模拟的保护模式)”:它也使用32位地址寻址方式。Windows3.1彻底删除了对实模式的支持。在80286处理器芯片以后,Windows3.1成为主流操作系统(Windows/80286不是主流产品)。目前差不多所有的X86系列处理器操作系统(LinuxWindows95 and laterOS/2等)都是在启动时进行处理器设置而进入保护模式的。

     

    实模式工作机理:

    1> 对于8086/8088来说计算实际地址是用绝对地址对1M求模8086地址线的物理结构:20,也就是它可以物理寻址的内存范围为2^20个字节,即1 M空间,但由于8086/8088所使用的寄存器都是16位,能够表示的地址范围只有0-64K,这和1M地址空间来比较也太小了,所以为了在8086/8088下能够访问1M内存,Intel采取了分段寻址的模式:16位段基地址:16位偏移EA。其绝对地址计算方法为:16位基地址左移4+16位偏移=20位地址。  比如:DS=1000H EA=FFFFH 那么绝对地址就为:10000H +
    0FFFFH = 1FFFFH 地址单元
     。通过这种方法来实现使用16位寄存器访问1M的地址空间,这种技术是处理器内部实现的,通过上述分段技术模式,能够表示的最大内存为:
     
    FFFFh: FFFFh=FFFF0h+FFFFh=10FFEFh=1M+64K-16Bytes1M多余出来的部分被称做高端内存区HMA。但8086/8088只有20位地址线,只能够访问1M地址范围的数据,所以如果访问100000h~10FFEFh之间的内存(大于1M空间),则必须有第21根地址线来参与寻址(8086/8088没有)。因此,当程序员给出超过1M100000H-10FFEFH)的地址时,因为逻辑上正常,系统并不认为其访问越界而产生异常,而是自动从0开始计算,也就是说系统计算实际地址的时候是按照对1M求模的方式进行的,这种技术被称为wrap-around

     

     

    2> 对于80286或以上的CPU通过A20 GATE来控制A20地址线  技术发展到了 80286,虽然系统的地址总线由原来的20根发展为24根,这样能够访问的内存可以达到2^24=16M,但是Intel在设计80286时提出的目标是向下兼容,所以在实模式下,系统所表现的行为应该和8086/8088所表现的完全一样,也就是说,在实模式下,80386以及后续系列应该和8086/8088完全兼容仍然使用A20地址线。所以说80286芯片存在一个BUG:它开设A20地址线。如果程序员访问100000H-10FFEFH之间的内存,系统将实际访问这块内存(没有wrap-around技术),而不是象8086/8088一样从0开始。我们来看一副图:




    为了解决上述兼容性问题,IBM使用键盘控制器上剩余的一些输出线来管理第21根地址线(从0开始数是第20根) 的有效性,被称为A20 Gate

    1> 如果A20 Gate被打开,则当程序员给出100000H-10FFEFH之间的地址的时候,系统将真正访问这块内存区域;

    如果A20 Gate被禁止,则当程序员给出100000H-10FFEFH之间的地址的时候,系统仍然使用8086/8088的方式即取模方式(8086仿真)。绝大多数IBM PC兼容机默认的A20 Gate是被禁止的。现在许多新型PC上存在直接通过BIOS功能调用来控制A20 Gate的功能。


    保护模式(Protected Mode)

    上面所述的内存访问模式都是实模式,在80286以及更高系列的PC中,即使A20 Gate被打开,在实模式下所能够访问的内存最大也只能为10FFEFH,尽管它们的地址总线所能够访问的能力都大大超过这个限制。为了能够访问10FFEFH以上的内存,则必须进入保护模式。

    (286Intel 80286的另一种叫法) 它又被称作为虚拟地址保护模式。尽管在Intel 80286手册中已经提出了虚地址保护模式,但实际上它只是一个指引,真正的32位地址出现在Intel 80386上。保护模式本身是80286及以后兼容处理器序列之后产成的一种操作模式,它具有许多特性设计为提高系统的多道任务系统的稳定性。例如内存的保护分页机制硬件虚拟存储的支持。现代多数的x86处理器操作系统都运行在保护模式下,包括Linux, Free BSD, Windows3.0(它也运行在实模式下,为了和Windows 2.x应用程序兼容)及以后的版本。

     

    80286及以后的处理器另一种工作模式是实模式(仅当系统启动的一瞬间),本着向下兼容的原则屏蔽保护模式特性,从而容许老的软件能够运行在新的芯片上。作为一个设计规范,所有的x86系列处理器,除嵌入式Intel80387之外,都是系统启动工作在实模式下,确保遗留下的操作系统向下兼容。它们都必须被启动程序(操作系统程序最初运行代码)重新设置而相应进入保护模式的,在这之前任何的保护模式特性都是无效的。在现代计算机中,这种匹配进入保护模式是操作系统启动时最前沿的动作之一。

     

    在被调停的多道任务程序中,它可以从新工作在实模式下是相当可能的。保护模式的特性是阻止被其他任务或系统内核破坏已经不健全的程序的运行,保护模式也有对硬件的支持,例如中断运行程序,移动运行进程文档到另一个进程和置空多任务的保护功能。

     

    386及以后系列处理器不仅具有保护模式又具有32位寄存器,结果导致了处理功能的混乱,因为80286虽然支持保护模式,但是它的寄存器都是16位的,它是通过自身程序设定而模拟出的32位,并非32位寄存器处理。归咎于这种混乱现象,它促使Windows/386 及以后的版本彻底抛弃80286的虚拟保护模式,以后保护模式的操作系统都是运行在80386以上,不再运行在80286(尽管80286模式支持保护模式),所以说80286是一个过渡芯片,它是一个过渡产品

     

    尽管 286386处理器能够实现保护模式和兼容以前的版本,但是内存的1M以上空间还是不易存取,由于内存地址的回绕,IBM PC XT (现以废弃)设计一种模拟系统,它能过欺骗手段访问到1M以上的地址空间,就是开通了A20地址线。在保护模式里,前32个中断为处理器异常预留,例如,中断0D(十进制13)常规保护故障和中断00是除数为零异常。

     

    如果要访问更多的内存,则必须进入保护模式,那么,在保护模式下, A20
    Gate对于内存访问有什么影响呢?

    为了搞清楚这一点,我们先来看一看A20的工作原理。A20,从它的名字就可以看出来,其实它就是对于A20(从0开始数)的特殊处理(也就是对第21根地址线的处理)。如果A20 Gate被禁止,对于80286来说,其地址为24根地址线,其地址表示为EFFFFF;对于80386极其随后的32根地址线芯片来说,其地址表示为FFEFFFFF。这种表示的意思是:



    1> 如果A20
    Gate被禁止。则其第A20CPU做地址访问的时候是无效的,永远只能被作为0。所以,在保护模式下,如果A20
    Gate被禁止,则可以访问的内存只能是奇数1M段,即1M,3M,5M…,也就是00000-FFFFF, 200000-2FFFFF,300000-3FFFFF…

    2如果A20 Gate被打开。则其第20-bit是有效的,其值既可以是0,又可以是1。那么就可以使A20线传递实际的地址信号。如果A20 Gate被打开,则可以访问的内存则是连续的。


    实模式和保护模式的区别

    从表面上看,保护模式和实模式并没有太大的区别,二者都使用了内存段、中断和设备驱动来处理硬件,但二者有很多不同之处。我们知道,在实模式中内存被划分成段,每个段的大小为 64KB ,而这样的段地址可以用 16 位来表示。内存段的处理是通过和段寄存器相关联的内部机制来处理的,这些段寄存器( CS  DS  SS ES )的内容形成了物理地址的一部分。具体来说,最终的物理地址是由 16 位的段地址和 16 位的段内偏移地址组成的。用公式表示为:物理地址 = 左移 4 位的段地址 + 偏移地址。

    在保护模式下,段是通过一系列被称之为  描述符表  的表所定义的。段寄存器存储的是指向这些表的指针。用于定义内存段的表有两种:全局描述符表 (GDT) 局部描述符表 (LDT)  GDT 是一个段描述符数组,其中包含所有应用程序都可以使用的基本描述符。在实模式中,段长是固定的 (  64KB) ,而在保护模式中,段长是可变的,其最大可达 4GB LDT 也是段描述符的一个数组。与 GDT 不同, LDT 是一个段,其中存放的是局部的、不需要全局共享的段描述符。每一个操作系统都必须定义一个 GDT ,而每一个正在运行的任务都会有一个相应的 LDT 。每一个描述符的长度是 8 个字节,格式如图 3 所示。当段寄存器被加载的时候,段基地址就会从相应的表入口获得。描述符的内容会被存储在一个程序员不可见的影像寄存器 (shadow register) 之中,以便下一次同一个段可以使用该信息而不用每次都到表中提取。物理地址由 16 位或者 32 位的偏移加上影像寄存器中的基址组成。实模式和保护模式的不同可以从下图很清楚地看出来。


    实模式地址



    保护模式地址



    总结

    保护模式同实模式的根本区别是进程内存受保护与否。可寻址空间的区别只是这一原因的果。实模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,系统程序和用户程序没有区别对待,而且每一个指针都是指向"实在"的物理地址。这样一来,用户程序的一个指针如果指向了系统程序区域或其他用户程序 区域,并改变了值,那么对于这个被修改的系统程序或用户程序,其后果就很可能是灾难性的。为了克服这种低劣的内存管理方式,处理器厂商开发出保护模式。这 样,物理内存地址不能直接被程序访问,程序内部的地址(虚拟地址)要由操作系统转化为物理地址去访问,程序对此一无所知。

    至此,进程有了严格的边界,任何其他进程根本没有办法访问不属于自己的物理内存区域,甚至在自己的虚拟地址范围内也不是可以任意访问的,因为有一些虚拟区域已经被放进一些公共系统运行库。这些区域也不能随便修改,若修改就会有: SIGSEGV(linux 段错误);非法内存访问对话框(windows 对话框)。

    CPU启动环境为16位实模式,之后可以切换到保护模式。但从保护模式无法切换回实模式 。


    对于80X86处理器来说,从80386处理器开始,除了以前的实模式外,还增添了 
    保护模式和V86模式。实模式和V86模式都是为了和8086兼容而设置的。 
      
    实模式: 
          内存寻址方式为:段式寻址,即物理地址=段地址*16   +   段内偏移地址 
          可寻址任意地址,所有指令都相当于工作在特权级。 
          dos工作在实模式下 
    保护模式: 
          内存寻址方式为:支持内存分页和虚拟内存 
          支持多任务,可依靠硬件用一条指令即可实现任务切换,不同任务可工作在 不同的优先级下,操作系统工作在最高优先级0上,应用程序则运行       在较低优先级 上。从实模式到保护模式,需要建立GDT、IDT等数据表,然后通过修改控制寄存 器CR0的控制位(位0)来实现。 
          Windows工作在保护模式下。 
    虚拟8086模式: 
            内存寻址方式:段式寻址,与实模式一样 
            支持多任务和内存分页 
            v86模式主要是为了在保护模式下兼容以前的实模式应用,即可支持多任务, 
            但每个任务都是实模式的工作方式。 

    另外,中断和异常等的处理对于不同的工作模式都是不同的,具体的可以去 参看一些相关书籍。 


    以上转自:http://blog.csdn.net/kennyrose/article/details/7563649

    以下转自:http://ahhqlrg.lofter.com/post/1ccdac85_7882945

     Intel的实模式和保护模式,必须要知道的...

    一、Intel手册如是说 

            Intel的IA-32架构支持三种基本操作模式:保护模式、实地址模式和系统管理模式。操作模式决定了哪些指令和架构相关的特性是可用的:

    • 保护模式(protected mode)——是处理器的根本模式,在保护模式下,可以为直接运行的实地址模式程序提供保护的、多任务的环境,这种特性被称作“虚拟8086模式(virtual 8086 mode)”,尽管“虚拟8086”模式并不是一种真正的处理器模式;virtual 8086 mode实际上是保护模式的一种属性,在保护模式下,可以向任何任务提供这种属性。
    • 实地址模式(real-addess mode)——该模式以扩展的方式实现了8086CPU的程序运行环境(就像切换到保护模式和系统管理模式一样)。处理器在刚刚上电或者重启后的时候,处于实地址模式。
    • 系统管理模式(system manangement mode,SMM)——该模式提供操作系统或者执行程序一种透明的机制去实现平台相关的特性,例如电源管理和系统安全。当来自外部的或者APIC控制器的SMM中断pin脚被触发时,处理器在下列情况进入SMM。在SMM下,处理器切换到一个独立的地址空间,同时保存当前运行的程序或任务的上下文。SMM相关的代码可透明的执行。当SMM模式返回时,处理器返回SMI(system manangement interrupt)前的工作模式。SMM模式在Intel 386 SL和Intel 486 SL处理器时被引入,在Pentium家族时成为标准的IA-32架构的特性。

            Intel 64 架构增加了IA-32e模式,IA-32e模式又包含两种子模式:

    • 兼容模式(Compatibility mode)——兼容模式允许绝大部分16-bit/32-bit的应用可以无需重新编译就能直接在64bits的操作系统下运行。简单的,在IA-32架构下将“兼容子模式”称为“兼容模式”。兼容模式的可执行环境与Section 3.2定义的一致。兼容模式也支持所有的特权级别,在64-bit和保护模式下。运行在虚拟8086模式下的或用到硬件任务管理的程序,在此模式下无法工作。兼容模式做代码段基础被操作系统所启动,这意味着单独的64-bit操作系统可以通过64-bit模式支持64-bit应用,也可以通过兼容模式支持32-bit程序(无需再编译)。兼容模式类似于32-bit的保护模式,应用程序只能访问前4G的线性地址空间。兼容模式使用16-bit和32-bit的地址以及操作数长度。类似于保护模式,该模式允许应用程序通过PAE(物理地址扩展,Physical Address Extention)访问超过4G的物理内存。
    • 64-bit模式(64-bit  mode)——该模式使操作系统在64-bit线性地址空间上运行应用程序,在IA-32架构下,64-bit子模式被简称为64-bit模式。64-bit模式将通用寄存器和SIMD寄存器从8个扩展到16个。通用寄存器被增加到64bits宽。该模式还引入了新的操作数前缀(REX)来访问寄存器扩展。64-bit模式作为代码段基础被操作系统激活,该模式默认地址长度是64bits,但默认操作数长度为32bits,默认的操作数长度可以在指令级别被覆盖,通过使用REX操作数前缀结合操作数大小覆盖前缀。REX前缀被用来在64-bit模式下指定一个64-bit的操作数,通过这种机制,很多已经存在的指令代码可以被直接升级到访问64-bit地址空间和64-bit寄存器。        
    二、实模式的历史原因 

            早在1978年,Intel研制了8086处理器,8086处理有16-bit寄存器和16-bit的外部数据总线,但是却能够访问20-bit的地址(包含1MByte的地址空间)。8086通过引入“分段(segmentation)机制”来解决这个问题。在分段机制下,一个16bits的段寄存器包含了一个长度是64KBytes的“段”空间的起始指针。通过1次时可用一个段寄存器,8086处理器可以无需在段间切换就能寻址高达256KBytes。因此,通过段寄存器+16bits的指针就能访问20bits的地址空间,共1Mbytes。

            虽然8086/8088处理器采用的地址空间大小与外部总线位宽不一致,但是这显得很“别扭”,并且在1985年80386时被统一,地址空间和外部总线位宽均为32bits。

            为了兼容8086,这种分段管理内存的方式一直被保留下来。当然,实模式不仅仅是分段内存管理,而是“程序的运行环境”,所为的运行环境,还包括寄存器的长度。

    三、实模式和保护模式的区别 

            个人理解,因为x86的CPU在启动时首先处于实模式,也就8086的16位模式,主要特点有:

    • 内存管理采用段+偏移的方式进行;
    • 最大寻指位2的20次方,即1Mbyte;
    • 没有安全级别,直接操作CPU的各种功能;
    • 没有分页功能,没有虚拟地址的概念,只有物理地址;
    • 各种寄存器的位宽基本上是16位的(段寄存器为20位);

            实模式就是一种比较原始的模式,能看到CPU设计的初始过程。

            保护模式是32位模式,但是可以兼容16位的实模式,保护模式的特点有:

    • 内存管理采用段式+页式的方式;
    • 最大寻指位2的32次方,即4G,还可以通过PAE模式访问超过4G的部分。
    • 有4个安全级别,内存操作时有安全检查;
    • 因为有了分页功能,因此有虚拟地址和物理地址的区别;
    • 各种寄存器的位宽基本上都是32位的,但是可以兼容同名的16位寄存器;
    • 可以兼容实地址模式,也即实地址模式的程序无需再编译即可跑在保护模式下。

            对于64位CPU来说,兼容模式和64位模式也基本上一个道理,兼容模式为了兼容32位,也就兼容了16位,而64位模式本身不去兼容32位,是纯净的64位。

    四、实模式对OS的意义 

            仅从Linux操作系统考虑。因为32位CPU一上电后,首先处于实模式下,所以实模式是OS必须经历的一种模式。既然保护模式可以兼容实模式,那么CPU为什么还要提供专门的实模式呢?是否OS可以直接从实模式跳转到保护模式呢?

            实话说,这两个问题我也不太好回答。为了钱?对Linux系统来说,从实模式到保护模式的过程,实际上主要就是为了初始化保护模式的过程。从实际模式切换到保护模式不是开开关、按按钮,而需要准备工作,主要是为了开启分页机制做准备,要分页,必须有页表、段描述符,这些都需要通过内存初始化。关于具体的过程,请参考《Linux内核启动保护模式的过程》。

            个人认为实模式就是在CPU启动的时刻的低级模式,需要OS无需在该模式上做过多的事情,而仅仅为向高级模式转化做准备。

    展开全文
  • 实模式到保护模式

    千次阅读 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汇编 · 从实模式到保护模式》文中图片多直接引用其中

     

     

    展开全文
  • 地址模式和保护模式

    千次阅读 2018-11-26 18:24:57
    实模式:cpu复位或者加电网的时候是以是实模式启动的,在实模式下,内存寻址方式和8086相同,由16位段寄存器的内容乘以16(10H)当做段基地址,加上16位偏移地址形成20位的物理地址,最大寻址空间1MB,最大分段64KB...

    实模式:cpu复位或者加电网的时候是以是实模式启动的,在实模式下,内存寻址方式和8086相同,由16位段寄存器的内容乘以16(10H)当做段基地址,加上16位偏移地址形成20位的物理地址,最大寻址空间1MB,最大分段64KB。可以使用32位指令。32位的x86 CPU用做高速的8086。在实模式下,所有的段都是可以读、写和可执行的。 

    保护模式与实模式相比,主要是两个差别:一是提供了段间的保护机制,防止程序间胡乱访问地址带来的问题,二是访问的内存空间变大。

    保护模式,是一种80286系列和之后的x86兼容CPU操作模式。保护模式有一些新的特色,设计用来增强多工和系统稳定度,像是 内存保护,分页 系统,以及硬件支援的 虚拟内存。大部分的现今 x86 操作系统 都在保护模式下运行,包含 Linux、FreeBSD、以及 微软 Windows 2.0 和之后版本。

    展开全文
  • 06.实模式进入保护模式

    千次阅读 2018-10-30 21:17:30
    上一节我们实现了从内核加载器中加载其它扇区代码并执行,但始终工作在实模式状态下。内存寻址方式和8086相同,由16位段寄存器的内容乘以16(10H)当做段基地址,加上16位偏移地址形成20位的物理地址,最大寻址空间1...
  • 实模式和保护模式区别及寻址方式

    万次阅读 多人点赞 2013-05-16 08:08:49
     我记得大学的汇编课程,组成原理课里老师讲过实模式和保护模式的区别,在很多书本上也有谈及,无奈本人理解和感悟能力实在太差,在很长一段时间里都没真正的明白它们的内含,更别说为什么实模式下最大寻址空间为1...
  • 目前项目募集资金主要有几种方式,比如传统的IPO、链圈的ICO、IDO等,这几种方式不能说绝对的好坏,都是自适应环境下的产物,今天给大家分享的IPCO,是一种的股权发行模式,其结合传统与现代,可以最大可能的...
  • 任务切换的方法——《x86汇编语言:从实模式到保护模式》读书笔记37 1. 中断门和陷阱门 在实模式下,内存最低端的1M是中断向量表,保存着256个中断处理过程的段地址和偏移。当中断发生时,处理器把中断号乘以4...
  • x86 汇编语言:从实模式到保护模式

    千次阅读 2018-07-11 14:13:33
    在 32 位模式下,传统的段寄存器,如 CS、SS、DS、ES,保存的不再是 16位段基地址,而是段的选择子,即,用于选择所要访问的段,因此,严格地说,它的名字叫做段选择器。除了段选择器之外,每个段寄存器还包括一个...
  • c05_mbr.asm ;代码清单5-1 ;文件名:c05_mbr.asm ;文件说明:硬盘主引导扇区代码 ;创建日期:2011-3-31 21:15 mov ax,0xb800 ;指向文本模式的显示缓冲区 mov es,ax ...
  • 上午有时间,继续上一篇文章,本篇的主要内容是如何启动保护模式,这样我们可以利用更大的内存来编程了。一、我们创建一个顶层Makefile文件,方便之后我们的编译调试OBJ := system.o loader.o TOP_DIR := $(PWD) OBJ...
  • 媒体营销是指利用媒体平台进行营销的方式。在web2.0带来巨大革新的时代,营销方式也带来变革,沟通性(communicate)、差异性...小涛一起谈谈媒体营销的十大模式! 一、饥饿营销 饥饿营销是指商品提供者有意
  • 但是,一旦返回,因为需要改变特权级,CPU就会则检查DS,ES,FS和GS的内容,如果段选择子指向数据段或者非一致代码段且段描述符的DPL在数值上小于返回后的CPL,那么就把数值0传送到该段寄存器。 如果忘了这个知识...
  • CPU 实模式 保护模式 和虚拟8086模式

    万次阅读 2012-05-14 09:59:59
    从80386开始,CPU有三种工作方式:实模式,保护模式和虚拟8086模式。只有在刚刚启动的时候是real-mode,等到操作系统运行起来以后就切换到protected-mode。实模式只能访问地址在1M以下的内存称为常规内存,我们把...
  • Linux实模式和保护模式

    千次阅读 2012-06-06 20:00:52
    段寄存器在实模式下和保护模式下的作用   先说点相关的:80x86中除8086/8088只能在实模式下工作之外,其他微处理器均可在实模式和保护模式下工作。 好了,从定义和区别开始吧。 先从实模式下入手:存储器地址的...
  • 从80386开始,CPU有三种工作方式:实模式,保护模式和虚拟8086模式。只有在刚刚启动的时候是real-mode,等到操作系统运行起来以后就切换到protected-mode。实模式只能访问地址在1M以下的内存称为常规内存,我们把...
  • 之前已经了一些理论上的铺垫,这次我们就可以看代码了。 一、代码清单 ;代码清单11-1 ;文件名:c11_mbr.asm ;文件说明:硬盘主引导扇区代码 ;创建日期:2011-5-16 19:54 ;设置堆栈段和栈指针 mov ax,cs ...
  • 实模式与保护模式

    千次阅读 2014-11-15 17:56:54
    80386处理器有3种工作模式实模式、保护模式和虚拟86模式实模式和虚拟86模式是为了和8086处理器兼容而设置的。在实模式下,80386处理器就相当于一个快速的8086处理器。保护模式是80386处理器的主要工作模式。在此...
  • 每当发生任务切换时,LDTR的内容被更新,以指向任务的LDT。 当向段寄存器加载段选择子的时候,段选择子的 TI (Bit2)位是表指示器(Table Indicator)。 TI=0:处理器从GDT中加载描述符 TI=1:处理器从LDT中...
  • Mine【为从实模式跳转到保护模式的准备工作】 [BITS 16] LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h ; 下一行指令的解说: ; 待会要跳回到实模式,这里是保存...
  • 实模式相比,保护模式下的中断处理有两点最大的区别。首先,中断向量表可以在内存中自由浮动。其次,中断的功能获得了拓展。  在实模式下,中断向量表占据内存最低的1KB,共256个表项。每个表项4子节,包含一个2...
  • 设计模式笔记--新模式

    千次阅读 2016-01-18 14:02:41
    设计模式笔记--新模式
  • 地址模式和保护模式的理解

    千次阅读 2014-04-23 16:46:03
    系统启动时处理器处于实模式,只能访问1M空间,经过处理可进入保护模式,访问空间扩大到16M,但是要想从保护模式返回到实模式,你只有重新启动机器。还有一个致命的缺陷是80286虽然扩大了访问空间,但是每个段的大小...
  • 概述:实模式和保护模式是处理器发展的两个非常重要的阶段。这两个模式下的编程也有着显著的不同,弄明实模式与保护模式的区别是理解操作系统运行原理和编写操作系统的基础。本文主要讲解了实模式和保护模式的区别和...
  • CPU实模式和保护模式 cpu的保护模式由来 分段机制 8086的诞生,标志着Intel 正式进入了x86时代,这是个多么具有纪念意义的日子:1978-6-8。同时,8086的诞生也是处理器内存寻址技术的第一次飞跃。 对于...
  • Java常见设计模式总结

    万次阅读 多人点赞 2021-09-18 17:18:54
    设计模式是一套经过反复使用的代码设计经验,目的是为了重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式于己于人于系统都是多赢的,它使得代码编写真正工程化,它是软件工程的基石,如同大厦的一块块...
  • 自从最初的x86微处理器规格以后,它对程序开发完全向下兼容,80286芯片被制作成启动时继承了以前版本芯片的特性,工作在实模式下,在这种模式下实际上是关闭了的保护功能特性,因此能使以往的软件继续工作在的...
  • Linux实模式与保护模式下的寄存器

    千次阅读 2012-06-06 20:07:50
    段寄存器在实模式下和保护模式下的作用 先说点相关的:80x86中除8086/8088只能在实模式下工作之外,其他微处理器均可在实模式和保护模式下工作。 好了,从定义和区别开始吧。 先从实模式下入手:存储器地址的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 320,792
精华内容 128,316
关键字:

做实新模式