当前位置:首页 > > IOT物联网小镇
[导读]实模式:bootloader为程序计算段的基地址保护模式:bootloader为自己创建段描述符确定GDT的地址创建代码段的描述符创建数据段的描述符创建栈段的描述符段描述符是如何确保段的安全的?段寄存器高速缓存对段寄存器本身的保护对段界限的检查在上一篇文章中,我们已经顺利的从实模...


  • 实模式:bootloader 为程序计算段的基地址


  • 保护模式:bootloader 为自己创建段描述符


    • 确定 GDT 的地址


    • 创建代码段的描述符


    • 创建数据段的描述符


    • 创建栈段的描述符


  • 段描述符是如何确保段的安全的?


    • 段寄存器高速缓存


    • 对段寄存器本身的保护


    • 对段界限的检查


在上一篇文章中,我们已经顺利的从实模式,过渡到了保护模式


保护模式与实模式最本质的区别就是:保护模式使用了全局描述符表,用来保存每一个程序(bootloader,操作系统,应用程序)使用到的每个段信息:开始地址,长度,以及其他一些保护参数。


这篇文章,我们来看一下bootloader是如何来进行自我进化到保护模式的,然后深入看一下保护模式是如何对内存进行安全保护的。


作为背景知识,我们先来看一下x86中的地址变换过程:


Linux从头学09:x86 处理器如何进行-层层的内存保护?x86 处理器中的分页机制是可以被关闭的,此时线性地址就等于物理地址,这也是我们一直讨论的情况。


下一篇文章,我们就把 x86 中的分页机制打开,并与 Linux 中的分段和分页机制进行对比。


实模式:bootloader 为程序计算段的基地址

在之前的文章:Linux从头学06:16张结构图,彻底理解【代码重定位】的底层原理中,我们讨论了bootloader是如何把应用程序读取到内存中,最后跳入到程序的入口地址的。


这里所说的程序,可以是操作系统,也可以是应用程序。


下面这张图,是程序被加载到内存中之后,header中的信息:


Linux从头学09:x86 处理器如何进行-层层的内存保护?因为程序是被bootloader动态读取到内存中的,它是不知道自己被放在内存中的什么位置,因此它也不知道自己代码段、数据段、栈的开始地址。


但是,程序要想能够正常执行,就必须要知道这些信息,那怎么办?


只有bootloader才能解决问题,因为是它来把程序从硬盘加载到内存中的。


因此,bootloader在跳入程序的入口地址之前,必须把其中的代码段、数据段、栈段的基地址计算出来,然后写入到程序的header中,如下图所示:


Linux从头学09:x86 处理器如何进行-层层的内存保护?这样的话,程序开始执行时,就可以从自己的header中获取到这3个段基地址,并且赋值给相应的寄存器,从而顺利的执行程序。


也就是说:程序的header空间,充当了bootloader与它进行信息交互的媒介,用来传递3个段寄存器的基地址。


以上的这个过程,一直工作在实模式,因此就没有段描述符什么事情。


在以后文章中,我们还会看到在保护模式下,bootloader仍然会利用OS的header空间,来传递段的索引号。然后OS利用这个段索引号,去查找GDT表,从而找到每一个段的基地址以及其他一些保护信息。


保护模式:bootloader 为自己创建段描述符

bootloader从BIOS接管系统之后,刚开始是运行在实模式下的。


当它完成一些准备工作之后,就可以进入保护模式了,也就是把CR0寄存器的bit0设置为1。


这个准备工作中,最重要的就是:建立 GDT 这个表,并且把 GDT 的开始地址,存储到寄存器 GDTR 中


下面这张图,是bootloader被加载到内存中的布局图:


Linux从头学09:x86 处理器如何进行-层层的内存保护?bootloader被加载到0x0000_7C00地址处。


它最少需要创建3个段描述符:代码段、数据段和栈段。


确定 GDT 的地址

在创建段描述符之前,需要先确定: 把 GDT 表放在内存中的什么位置?


暂且就把它放在0x0001_0000这个地址吧,距离零地址64K的位置。


按照处理器的要求,在第1个表项(称之为 item 或者 entry,每本书上都不一样)必须为空描述符(index = 0)。


Linux从头学09:x86 处理器如何进行-层层的内存保护?

创建代码段描述符

bootloader的代码放在0x0000_7C00开始的地址,长度是512B。


根据这些信息,就可以构造出代码段的描述符了:


Linux从头学09:x86 处理器如何进行-层层的内存保护?

创建数据段描述符

bootloader待会需要把操作系统或其他应用程序,从硬盘读取到内存中,例如:读取到0x0002_0000的位置。


那么bootloader就必须能够访问到这个位置,并且是以数据段的读写方式。


为了利用全部的4G内存空间,bootloader可以把这4G空间,作为一个数据段来定义它的描述符,如下:


Linux从头学09:x86 处理器如何进行-层层的内存保护?

创建栈段描述符

理论上,bootloader可以使用内存中的任意一块空闲空间,来作为自己的栈。


因为栈在push操作的时候,是向低地址方向增长的。


因此很多书籍都会把栈顶基地址设置为bootloader的开始地址,也就是0x0000_7C00地址处,并且把栈的空间大小限制在4K的范围。


Linux从头学09:x86 处理器如何进行-层层的内存保护?根据以上这些信息,就可以创建出栈的段描述符,如下:


Linux从头学09:x86 处理器如何进行-层层的内存保护?当以上这几个段的描述符都创建好之后,就可以把GDT的地址(0x0001_0000),设置到 GDTR 寄存器中了。


最后,再把CR0寄存器的bit0设置为1,就正式的进入保护模式来执行bootloader中后面的代码了。


段描述符是如何确保段的安全访问的?

段寄存器高速缓存

进入保护模式之后,虽然对段寄存器中内容的解释改变了,但是执行每一条指令,还是需要使用到这些段寄存器的: cs, ds, ss等等。


想象一下:每执行一条指令,都会从逻辑地址中,获取到段索引号,然后去查找GDT表,从而定位到段的基地址


大家都知道程序有个“局部性”原理,也就是连续执行的代码,都是集中在一段连续的程序空间中的。


这个连续的程序空间,它们都是在同一个代码段中,因此段的基地址都是相同的,那么它们都属于GDT中同一个代码段描述符所代表的段空间。


如果每一条指令都去查表,就会影响到程序的执行效率。


所以,处理器内部就为每一个段寄存器,安排了一个高速缓存


拿代码段寄存器cs来说:当执行一条指令的时候,如果它与上一条指令中的段索引号不同,才会根据新的段索引号到GDT中查找相应的段描述符表项。


查找到之后,就把这个表项的内容复制到 cs 寄存器的高速缓存中


当继续执行后面的指令时,如果逻辑地址中的段索引号没有变化,处理器就直接从高速缓存中读取段描述,从而避免了查表操作,提升了系统效率。


对段寄存器本身的保护

当逻辑地址中段寄存器的索引号改变时,就会根据新的索引号,到GDT中去查表。


当然了,这个索引号不能超过 GDT 的界限。


当定位到某一个描述符表项之后,就开始进行一系列检查。


再来看一下每一个段描述符中8个字节的内容:


Linux从头学09:x86 处理器如何进行-层层的内存保护?bit8 ~ bit11定义了当前这个段的类型。


假如: 我们在切换代码段空间的时候,不小心犯错,定位到了GDT中的一个数据段描述符表项,那么处理器就能够及时发现:


“当前这个段描述符的类型是数据段,你却把它当做代码段来使用,禁止,杀无赦!”


因此,处理器就会拒绝把这个段描述符复制到代码段的高速缓存中,从而对代码段寄存器进行了保护。


对段界限的检查

在通过了第一层的段类型保护之后,还会继续对段的界限进行检查,这就要使用到逻辑地址中的偏移地址(EIP)了。


如果偏移地址超过了描述符中规定的界限,那么就说明发生错误了。


例如:在bootloader的代码段描述符中,最大的界限是512B,如果把EIP设置为0x0000_1000,那就肯定错误了。


因为这个地址压根就不属于代码段的空间范围。


对于数据段来说比较有意思,因为我们把数据段描述符的基地址设置为0x0000_0000,段的界限是整个4G的空间,所以它可以对整个内存进行操作。


多想一步:


代码段也是属于这4G空间,因此可以通过数据段,来改写代码段空间中的指令内容。


也就是说:如果你想修改代码段的指令,直接通过代码段来操作是不可以的。


因为代码段描述符中规定了:代码段的内容只能被读取、执行,但是不能被写入


此时,就可以另辟蹊径:代码段也放在4G的空间,那么就可以通过数据段的可写特性,来改写代码段中的指令。


想一想gdb的调试过程,是不是就利用了这个道理?


在文末的
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭