linux分段内存管理中的GDT,LDT,GDTR,LDTR_linux gdt_土豆西瓜大芝麻的博客-程序员秘密

技术标签: linux  文件系统  

本文是对上一篇文章《逻辑地址、线性地址、物理地址的关系以及段寄存器在不同位数CPU中的用途演变以及GDT LDT PGD PT的关系》的补充。

一. 寻址方式:实地址模式和保护地址模式

我们知道,内存寻址模式在早期是采用的实地址模式(intel 80286之前),后面发展到了保护模式(80286开始)。在8086的时候,也就是16位cpu的时候,CPU配备了4个16位段寄存器(CS代码段寄存器、DS数据段寄存器、SS堆栈段寄存器和ES附加寄存器),当时设计的寻址空间为1M。而1M是2^{20},因此需要20位宽,而寄存器是16位,怎么办呢?

段寄存器里面存储的是实际物理内存地址的高16位,CPU要处理的访存指令里给出的是低16位的地址(内部地址)。这样,高16位的地址后面加上0000得到基址,再和低16位地址(偏移量)相加,就得到了物理内存地址,这就是所谓的[段寄存器基址:段内偏移量]方式的寻址。注意,这里得到的是物理内存地址,可不上上文说的虚拟地址等等。

而在8086年代,用户可以随意修改四个段寄存器的内容,同时低16位地址可以是有效范围内的任意值(64K空间)。这样,就意味着你可以随意(读和写)访问物理内存中的任何位置的数据,就问你怕不怕?从上面的表述,你能发现,在8086年代,系统是对物理内存没有任何保护和访问控制的吧。这就是所谓的实地址模式(和保护模式相对应的)

到了80286,考虑到实地址模式的潜在危险(这个已经被人利用过了),intel将寻址方式改成了保护模式,但改的不够彻底,能从实地址模式转到保护模式,但不能反向转换。从80386开始才算是彻底转换成功,从此开始了32位cpu的时代。考虑到向前兼容性,80386还得支持实地址模式,所以只能基于原来的4个段寄存器进行修修补补。但是这一次不是小打小闹,而是进行了重大改进。上文说过了,80386增加了段寄存器的数量。设计思想是:在保护模式下,改变段寄存器的功能,不再表示单一的一个基地址,变成指向一个数据结构的指针。这个结构是啥呢?就是段描述符Segment Descripter了(它长度为64bit,8个字节,每一描述符描述一个段的信息),而段描述符是聚集在一起存储的,那个结构叫段描述符表,Segment Descripter table。又根据这个段描述符表里存的是全局共同的,还是进程私有的,分成了全局段描述符表(GDT)和局部段描述符表(LDT)。

那么80386的段寄存器里存的是什么呢?虽然说要存指向段描述符的指针,但是发现用不到16位那么多,那就不能浪费啊。用1位表示从LDT还是GDT找段描述符,找哪个呢?LDT和GDT都可以看做数据(顺序连续存储的)用13位表示下标吧。剩下2位当成访问控制吧。这就是所谓的“段选择符”或者“段选择子”。

二. 描述符表

全局描述符表GDT

在整个系统中只有一张(一个处理器对应一个GDT),GDT可以被放在内存的任何位置,但CPU必须知道GDT的入口,也就是基地址放在哪里,Intel的设计者提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此寄存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。GDTR中存放的是GDT在内存中的基地址和其表长界限。

基地址指定GDT表中字节0在线性地址空间中的地址,表长度指明GDT表的字节长度值。指令LGDT和SGDT分别用于加载和保存GDTR寄存器的内容。在机器刚加电或处理器复位后,基地址被默认地设置为0,而长度值被设置成0xFFFF。在保护模式初始化过程中必须给GDTR加载一个新值。

GDTR

段选择子(Selector)

由GDTR访问全局描述符表是通过“段选择子”(实模式下的段寄存器)来完成的。段选择子是一个16位的寄存器(同实模式下的段寄存器相同)

段选择子

段选择子包括三部分:描述符索引(index)、TI、请求特权级(RPL)。它的index(描述符索引)部分表示所需要的段的描述符在描述符表的位置,由这个位置再根据在GDTR中存储的描述符表基址就可以找到相应的描述符。然后用描述符表中的段基址加上逻辑地址(SEL:OFFSET)的OFFSET就可以转换成线性地址,段选择子中的TI值只有一位0或1,0代表选择子是在GDT选择,1代表选择子是在LDT选择。请求特权级(RPL)则代表选择子的特权级,共有4个特权级(0级、1级、2级、3级)。

关于特权级的说明:任务中的每一个段都有一个特定的级别。每当一个程序试图访问某一个段时,就将该程序所拥有的特权级与要访问的特权级进行比较,以决定能否访问该段。系统约定,CPU只能访问同一特权级或级别较低特权级的段。

例如给出逻辑地址:21h:12345678h转换为线性地址

a. 选择子SEL=21h=0000000000100 0 01 (b) 代表的意思是:选择子的index=4即0100,选择GDT中的第4个描述符;TI=0代表选择子是在GDT选择;最后的01代表特权级RPL=1

b. OFFSET=12345678h若此时GDT第四个描述符中描述的段基址(Base)为11111111h,则线性地址=11111111h+12345678h=23456789h

局部描述符表LDT

局部描述符表LDT(Local Descriptor Table)局部描述符表可以有若干张,每个任务可以有一张。我们可以这样理解GDT和LDT:GDT为一级描述符表,LDT为二级描述符表。如图

LDT和GDT从本质上说是相同的,只是LDT嵌套在GDT之中。LDTR记录局部描述符表的起始位置,与GDTR不同,LDTR的内容是一个段选择子。由于LDT本身同样是一段内存,也是一个段,所以它也有个描述符描述它,这个描述符就存储在GDT中,对应这个表述符也会有一个选择子,LDTR装载的就是这样一个选择子。LDTR可以在程序中随时改变,通过使用lldt指令。如上图,如果装载的是Selector 2则LDTR指向的是表LDT2。举个例子:如果我们想在表LDT2中选择第三个描述符所描述的段的地址12345678h。

1. 首先需要装载LDTR使它指向LDT2, 使用指令lldt将Select2装载到LDTR

2. 通过逻辑地址(SEL:OFFSET)访问时SEL的index=3代表选择第三个描述符;TI=1代表选择子是在LDT选择,此时LDTR指向的是LDT2,所以是在LDT2中选择,此时的SEL值为1Ch(二进制为11 1 00b)。OFFSET=12345678h。逻辑地址为1C:12345678h

3. 由SEL选择出描述符,由描述符中的基址(Base)加上OFFSET可得到线性地址,例如基址是11111111h,则线性地址=11111111h+12345678h=23456789h

4. 此时若再想访问LDT1中的第三个描述符,只要使用lldt指令将选择子Selector 1装入再执行2、3两步就可以了(因为此时LDTR又指向了LDT1)

由于每个进程都有自己的一套程序段、数据段、堆栈段,有了局部描述符表则可以将每个进程的程序段、数据段、堆栈段封装在一起,只要改变LDTR就可以实现对不同进程的段进行访问

当进行任务切换时,处理器会把新任务LDT的段选择符和段描述符自动地加载进LDTR中。在机器加电或处理器复位后,段选择符和基地址被默认地设置为0,而段长度被设置成0xFFFF。

1:访问GDT

段描述符在GDT中

当TI=0时表示段描述符在GDT中,如上图所示:

①先从GDTR寄存器中获得GDT基址。

②然后再GDT中以段选择器高13位位置索引值得到段描述符。

③段描述符符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址(基址),再以基址加上偏移地址yyyyyyyy才得到最后的线性地址。

2:访问LDT

当TI=1时表示段描述符在LDT中,如上图所示:

①还是先从GDTR寄存器中获得GDT基址。

②从LDTR寄存器中获取LDT所在段的位置索引(LDTR高13位)。

③以这个位置索引在GDT中得到LDT段描述符从而得到LDT段基址。

④用段选择器高13位位置索引值从LDT段中得到段描述符。

⑤段描述符符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址(基址),再以基址加上偏移地址yyyyyyyy才得到最后的线性地址。

扩展

除了GDTR、LDTR外还有IDTR和TR

(1)中断描述符表寄存器IDTR 

与GDTR的作用类似,IDTR寄存器用于存放中断描述符表IDT的32位线性基地址和16位表长度值。指令LIDT和SIDT分别用于加载和保存IDTR寄存器的内容。在机器刚加电或处理器复位后,基地址被默认地设置为0,而长度值被设置成0xFFFF。

(2)任务寄存器TR

TR用于寻址一个特殊的任务状态段(Task State Segment,TSS)。TSS中包含着当前执行任务的重要信息。

TR寄存器用于存放当前任务TSS段的16位段选择符、32位基地址、16位段长度和描述符属性值。它引用GDT表中的一个TSS类型的描述符。指令LTR和STR分别用于加载和保存TR寄存器的段选择符部分。当使用LTR指令把选择符加载进任务寄存器时,TSS描述符中的段基地址、段限长度以及描述符属性会被自动加载到任务寄存器中。当执行任务切换时,处理器会把新任务的TSS的段选择符和段描述符自动加载进任务寄存器TR中。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/jinking01/article/details/105831888

智能推荐

Spring @Async和@EnableAsync详细理解[email protected]限制_思维的深度的博客-程序员秘密

一、同步与异步同步:就是调用某个东西是,调用方得等待这个调用返回结果才能继续往后执行。异步:在调用发出后调用者可用继续执行后续操作,被调用者通过状体来通知调用者,或者通过回掉函数来处理这个调用。示例:前提导入spring web相关依赖包1.定义Service层定义Service层,一个普通的方法,一个由@Async注释的方法[email protected] class CommonService { public v...

时间序列数据分析--Time Series--时序模型--ARIMA_time series amima_bboysky45的博客-程序员秘密

ARIMA模型参数选择检查序列是否平稳 若不平稳,使用差分平稳化序列,确定差分阶数d ARMA定阶 通过PACF确定AR的阶数p 通过ACF确定MA的阶数q 根据参数p,d,q建立模型ARIMA(p,d,q)# ARIMA模型# 平稳性import pandas as pdimport numpy as npimport matplotlib.pyp...

2018年2月iOS招人心得(附面试题)_次世代群901739356的博客-程序员秘密

( 其实作为一个开发者有一个学习的氛围跟一个交流圈子特别重要,这是我的一个iOS交流群319819749,不管是小白还是大牛都欢迎入驻,大家一起交流成长! )   从筛选第一份简历,准备面试题,到成功招到两个人一共花了两个星期多一点,总体来说还是比较顺利的。两位通过者都比较稳重踏实,而且对技术也比较有追求。这也可能和我筛选简历比较谨慎有关系,这次筛选简历所花费的精力是不比面试花费的少的。虽然时间跨...

Hyperscan 之 pcapscan示例解读_技术探索者的博客-程序员秘密

源码下载:https://github.com/intel/hyperscan/releases/1. 概述此示例实现一个简单的数据包匹配性能测量程序。pcapscan使用libpcap从pcap文件中读取数据包,并根据一个规则文件中指定的多个正则表达式对报文进行匹配,并输出匹配结果和一些统计信息。pcapscan使用并对比了两种匹配模式:BLOCK和STREAM。BLOCK模式时它对单个数据包进行匹配;而STREAM模式下它通过五元组将数据包进行简单分流,并对每条流中的数据进行匹配。STR

Spring系列第37篇:@EnableAsync &_m0_67306446的博客-程序员秘密

将@EnableAsync添加在spring配置类上,此时@Async注解才会起效。常见2种用法无返回值的可以获取返回值的4、无返回值的用法方法返回值不是Future类型的,被执行时,会立即返回,并且无法获取方法返回值,如:@Asyncpublic void log(String msg) throws InterruptedException {System.out.println(“开始记录日志,”&nbsp.

【收藏】Linux系统常用命令速查手册(附PDF下载链接)_linux速查手册_微思xmws的博客-程序员秘密

给大家收集整理了日常常用的Linux系统命令,仅供大家参考。大家如果觉得文章看起来不太方便,可以在+qq. 2 3 5 53 3 1 0 4 6 备注“liunx命令”,即可获取《Linux系统常用命令速查手册》.PDF版。系统信息arch #显示机器的处理器架构(1)uname -m #显示机器的处理器架构(2)uname -r #显示正在使用的内核版本dmidecode -q #显示硬件系统部件 - (SMBIOS / DMI)hdparm -.

随便推点

(三)洞悉linux下的Netfilter&iptables:内核中的rule,match和target_confirmwz的博客-程序员秘密

作为ipchains的后继者,iptables具有更加优越的特性,良好的可扩展功能、更高的安全性以及更加紧凑、工整、规范的代码风格。在2.6的内核中默认维护了三张表(其实是四张,还有一个名为raw的表很少被用到,这里不对其进行分析介绍了):filter过滤表,nat地址转换表和mangle数据包修改表,每张表各司其职。我们对这三张表做一下简要说明:1)、filter...

addEventListener() 方法兼容所有浏览器的写法(事件监听)_Serena_tz的博客-程序员秘密

Internet Explorer 8 及更早IE版本不支持 addEventListener() 方法,我们可以定义一个方法实现所有浏览器兼容。<script>var x = document.getElementById("myBtn");if (x.addEventListener) { // 所有主流浏览器,除了 IE 8 及更早版本 x.addEventListener("click", myFunction);} else if

《python编程从入门到实践》第八章函数_动手试一试(4)_A小面的博客-程序员秘密

说在前面。分享交流,乐于探讨。《python编程从入门到实践》自学。8-9 魔术师:创建一个包含魔术师名字的列表,并将其传递给一个名为show_magicians()的函数,这个函数打印列表中每个魔术师的名字。8-10 了不起的魔术师**:在你为完成练习 8-9 而编写的程序中,编写一个名为make_great()的函数,对魔术师列表进行修改,在每个魔术师的名字中都加入字样“the Grea...

1.我和python的第一次亲密接触_GoJawee的博客-程序员秘密

1.shell交互式python 在窗口中打印(1)打印字符串>>> print "Hello,python!" Hello,python!(2)字符串相加>>> print( "well water" + "river" ) >>> print "well water" + "river" well waterriver>>> print( "well water" + "riv

【Java】@[email protected]使用总结_后端码匠的博客-程序员秘密

在使用多线程的时候,往往需要继承Thread类,或者,如果要使用到线程池,还需要来创建Executors,在Spring中已经做了很好的支持。只要要就可以使用多线程。使用@Async就可以定义一个线程任务。通过Spring提供的就可以使用线程池。默认情况下,Spring将搜索相关的线程池定义:要么在上下文中搜索唯一的,要么搜索名为“”的。如果两者都无法解析,则将使用来处理异步方法调用。

推荐文章

热门文章

相关标签