“段寄存器”的故事[转](彻底搞清内存段/elf段/实模式保护模式以及段寄存器)
http://blog.csdn.net/michael2012zhao/article/details/5554023
一、 段寄存器的产生
段寄存器的产生源于Intel 8086 CPU体系结构中数据总线与地址总线的宽度不一致。
数据总线的宽度,也即是ALU(算数逻辑单元)的宽度,平常说一个CPU是“16位”或者“32位”指的就是这个。8086CPU的数据总线是16位。
地址总线的宽度不一定要与ALU的宽度相同。因为ALU的宽度是固定的,它受限于当时的工艺水平,当时只能制造出16位的ALU;但地址总线不一样,它可以设计得更宽。地址总线的宽度如果与ALU相同当然是不错的办法,这样CPU的结构比较均衡,寻址可以在单个指令周期内完成,效率最高;而且从软件的解决来看,一个变量地址的长度可以用整型或者长整型来表示会比较方便。
但是,地址总线的宽度还要受制于需求,因为地址总线的宽度决定了系统可寻址的范围,即可以支持多少内存。如果地址总线太窄的话,可寻址范围会很小。如果地址总线设计为16位的话,可寻址空间是2^16=64KB,这在当时被认为是不够的;Intel最终决定要让8086的地址空间为1M,也就是20位地址总线。
地址总线宽度大于数据总线会带来一些麻烦,ALU无法在单个指令周期里完成对地址数据的运算。有一些容易想到的可行的办法,比如定义一个新的寄存器专门用于存放地址的高4位,但这样增加了计算的复杂性,程序员要增加成倍的汇编代码来操作地址数据而且无法保持兼容性。
Intel想到了一个折中的办法:把内存分段,并设计了4个段寄存器,CS,DS,ES和SS,分别用于指令、数据、其它和堆栈。把内存分为很多段,每一段有一个段基址,当然段基址也是一个20位的内存地址。不过段寄存器仍然是16位的,它的内容代表了段基址的高16位,这个16位的地址后面再加上4个0就构成20位的段基址。而原来的16位地址只是段内的偏移量。这样,一个完整的物理内存地址就由两部分组成,高16位的段基址和低16位的段内偏移量,当然它们有12位是重叠的,它们两部分相加在一起,才构成完整的物理地址。
Base | b15 ~ b12 | b11 ~ b0 | |
Offset | o15 ~ o4 | o3 ~ o0 | |
Address | a19 ~ a0 |
这种寻址模式也就是“实地址模式”。在8086中,段寄存器还只是一个单纯的16位寄存器,而且操作寄存器的指令也不是特权指令。通过设置段寄存器和段内偏移,程序就可以访问整个物理内存,无安全性可言。
总之一句话,段寄存器的设计是一个权宜之计,现在看来可以说是一个临时性的解决方案,设计它的目的是为了把地址空间从64KB扩展为1MB,仅此而已。但是它的加入却为日后Intel系列芯片的发展带来诸多不便,也为理解i386体系带来困扰。
二、 实现保护模式
到了80386问世的时候,工艺已经有了很大的进步,386的ALU有已经从16位跃升为32位,也就是说,38086是32位的CPU,而且结构也已经比较成熟,接下来的80486一直到Pentium系列虽然速度提高了几个数量级,但并没有质的变化,所以被统称为i386结构。
对于32位的CPU来说,只要地址总线宽度与数据总线宽度相同,就可以寻址2^32=4GB的内存空间,这已经足够用,已经不再需要段寄存器来帮助扩展。但这时Intel已经无法把段寄存器从产品中去掉,因为新的CPU也是产品系列中的一员,根据兼容性的需要,段寄存器必须保留下来。
这时,技术的发展需求Intel在其CPU中实现“保护模式”,用户程序的可访问内存范围必须受到限制,不能再任意地访问内存所有地址。Intel决定利用段寄存器来实现他们的保护模式,把保护模式建立在段寄存器的基础之上。
对于段的描述不再只是一个20位的起始地址,而是全新地定义了“段描述项”。段描述项的结构如下:
B31 ~ B24 | DES1 (4 bit) | L19 ~ L16 | |
DES2 (8 bit) | B23 ~ B16 | ||
B15 ~ B0 | |||
L15 ~ L0 |
每一行是两个字节,总共8个字节,64位。
DES1和DES2分别是一些描述信息,用于描述本段是数据段还是代码段,以及读写权限等等。B0~B31是段的基地址,L0~L19是段的长度。
注意,规定段的长度是非常必要的,如果不限定段长度,“保护”就无从谈起,用户程序的访问至少不能超过段的范围。另外,段长度只有20位,所代表的最大可能长度为2^20=1M,而整个地址空间是2^32=4GB,这样来看,段的长度是不是太短了?其实,在DES1中,有一位用于表示段长度的单位,当它被置1时(一般情况下都是如此),表示长度单位为4KB,这样,一个段的最大可能尺寸就成了1M*4K=4G,与地址空间相稳合。4KB也正是一个内存页的大小,说明段的大小也是向页对齐的。
另外,注意到一个有趣的现象吗?段描述项的结构被设计得不连续,不论是段基地址还是段长度,都被分成了两节表示。这样的设计与80286的过渡有关。上面的段描述项结构去掉第一行后剩下的三行正是286的段描述项。286被设计为24位地址总线,所以段基址是24位,相应地段长是16位。在386的地址总线扩展为32位之后,还必须兼容286产品的设计,所以只好在段描述项上“打补丁”。
在386中,段寄存器还是16位,那么16位的段寄存器如何存放得下64位的段描述项? 段描述项不再由段寄存器直接持有。段描述项存放在内存里,系统中可以有很多个段描述项,这些项连续存放,共同构成一张表,16位的段寄存器里只是含有这张表里的一个索引,但也并不仅是一个简单的序号,而是存储了一种数据结构,这种结构的定义如下:
index (b15 ~ b3) | TI (b2) | RPL (b1 ~ b0) |
其中index是段描述表的索引,它指向其中的某一个段描述项。RPL表示权限,00最高,11最低。
还有一个关键的问题,内存中的段描述表的起始地址在哪里?显然光有索引是有不够的。为此,Intel又设计了两个新的寄存器:GDTR(global descriptor table register)和LDTR(local descriptor table register),分别用来存储段描述表的地址。段寄存器中的TI位正是用于指示使用GDTR还是LDTR。
当用户程序要求访问内存时,CPU根据指令的性质确定使用哪个段寄存器,转移指令中的地址在代码段,取数指令中的地址在数据段;根据段寄存器中的索引值,找到段描述项,取得段基址;指令中的地址是段内偏移,与段长比较,确保没有越界;检查权限;把段基址和偏移相加,构成物理地址,取得数据。
新的设计中处处有权限与范围的限制,用户程序只能访问被授权的内存空间,从而实现了保护机制。就这样,在段寄存器的基础上,Intel实现了自己的“保护模式”。
三、 与页式存管并存
现代操作系统的发展要求CPU支持页式存储管理。
页式存管本身是与段式存管分立的,两者没有什么关系。但对于Intel来说,同样是由于“段寄存器”这个历史的原因,它必须把页式存管建立在段式存管的基础之上,尽管这从设计的角度来说这是没有道理,也根本没有必要的。
在段式存管中,由程序发出的变量地址经映射(段基址+段内偏移)之后,得到的32位地址就是一个物理地址,是可以直接放到地址总线是去取数的。
在页式存管中,过程也是相似的,由程序发出的变量地址并不是实际的物理地址,而是一个三层的索引结构,这个地址经过一系统的映射之后才可以得到物理地址。
现在对于Intel CPU来说,以上两个映射过程就要先后各做一次。由程序发出的变量地址称为“逻辑地址”,先经过段式映射成为“线性地址”,线性地址再做为页式映射的输入,最后得到“物理地址”。
Linux内核实现了页式存储管理,而且并没有因为两层存管的映射而变得更复杂。Linux更关注页式内存管理,对于段式映射,采用了特殊的方式把它简化。让每个段寄存器都指向同一个段描述项,即只设了一个段,而这个段的基地址为0,段长度设为最大值4G,这个段就与整个物理内存相重合,逻辑地址经映射之后就与线性地址相同,从而把段式存管变成“透明”的。
这,就是Intel处理器中“段寄存器”的故事。
(总结:实模式的段式管理是坑爹的,不是真正的基址+偏移,386下的才是,这样的话,可以实现一定程度的离散内存管理。但是被linux绕过了,不知道分页能不能关掉,即使关掉了整个程序也是一个大段,不知道elf是否可以根据段式管理来编译,因为linux c一站式里面的elf没有段式的地址。内存的段式管理可以结合elf的段来实现)
参考:
http://blog.163.com/njut_wangjian/blog/static/16579642520123144377608/ (段间跳转指令jmpi和实模式寻址)
后续学习:80286和x64貌似挺有意思
汇编:http://www.feiesoft.com/asm/
“段寄存器”的故事[转](彻底搞清内存段/elf段/实模式保护模式以及段寄存器)的更多相关文章
- x86CPU 实模式 保护模式 傻傻分不清楚? 基于Xv6-OS 分析CR0 寄存器
基于Xv6-OS 分析CR0 寄存器 之前一直认为晕乎乎的...啥?什么时候切换real model,怎么切换,为什么要切换? ------------------------------------ ...
- 软件调试——IA-32 保护模式下寄存器一览
最近在看张银奎先生的<调试软件>一书,想将关键的技术记录下来,以便日后查阅,也分享给想看之人吧. 1 通用寄存器 EAX,EBX,ECX,EDX:用于运算的通用寄存器,可以使用AX,BX等 ...
- ASM:《X86汇编语言-从实模式到保护模式》1-4章:处理器,内存和硬盘基础
其实很久之前就学完了实模式了,但是一直没有总结,感觉现在直接在书上做笔记的弊端就是有些知识点不能很很深刻地记下来(毕竟手写最明显的优点就是能深刻地记住知识,但是就是用太多的时间罢了).一下内容都是一些 ...
- Linux操作系统基础(四)保护模式内存管理(2)【转】
转自:http://blog.csdn.net/rosetta/article/details/8570681 Linux操作系统基础(四)保护模式内存管理(2) 转载请注明出处:http://blo ...
- 数据段描述符和代码段描述符(一)——《x86汇编语言:从实模式到保护模式》读书笔记10
一.段描述符的分类 在上一篇博文中已经说过,为了使用段,我们必须要创建段描述符.80X86中有各种各样的段描述符,下图展示了它们的分类. 看了上图,你也许会说:天啊,怎么这么多段描述符啊!我可怎么记住 ...
- x86保护模式 任务状态段和控制门
x86保护模式 任务状态段和控制门 每个任务都有一个任务状态段TSS 用于保存任务的有关信息 在任务内权变和任务切换时 需要用到这些信息 任务内权变的转移和任务切换 一 ...
- windows游戏编程X86 32位保护模式下的内存管理概述(一)
本系列文章由jadeshu编写,转载请注明出处.http://blog.csdn.net/jadeshu/article/details/22445945 作者:jadeshu 邮箱: jades ...
- ELF Format 笔记(十四)—— 段内容
ilocker:关注 Android 安全(新手) QQ: 2597294287 一个段 (segment) 由一个或多个节 (section) 组成,但这对 android linker 是透明的, ...
- ELF Format 笔记(十二)—— 段类型(segment types)
ilocker:关注 Android 安全(新手) QQ: 2597294287 PT_NULL:如果段类型是 PT_NULL,那相应程序头结构体的其它成员都无意义,该程序头项可被忽略. 暂时还没遇到 ...
随机推荐
- Qt实现桌面动态背景雪花飘落程序
曾经收到过一份礼物,一个雪花飘落的程序,觉得效果很炫,通过前几篇的学习,我们已经掌握了贴图的一些技巧了,那么现在就可以自己实现了(当然你必须先拥有qt信号与槽的基础知识),这里先看效果 ...
- [转]Excel生成批量SQL语句,处理大量数据的好办法
当有大量重复体力工作写入或修改数据到数据库中时,可以 第一,将Excel数据整理好了之后,通过SQL的导入功能直接导进数据库,但是得保证数据库字段和Excel的字段一致. 第二,通过Excel来生成对 ...
- c++,C# 转换
//C++中的DLL函数原型为 //extern "C" __declspec(dllexport) bool 方法名一(const char* 变量名1, unsi ...
- Redis的PHP操作手册
String 类型操作 string是redis最基本的类型,而且string类型是二进制安全的.意思是redis的string可以包含任何数据.比如jpg图片或者序列化的对象 $redis-> ...
- PHP中session的使用
1.初始化(使用session前都要使用,一个页面用一个就可以了) session_start(); 2.保存 $_SESSION[$sessionName]=$value; (value可以是dou ...
- ValueError: No JSON object could be decoded?此种异常的解决方案之一
第一次遇到这样的异常,实在不知道如何是好?进行了测试发现报错的json出没有问题,而且每次出现异常的位置不一样 于是我认为这样的问题可能是因为程序执行过快,所以很简单的解决办法是: def deal_ ...
- 快速编译Delphi XE3 项目工程组
Embarcadero 做了个好事.工程组可以直接使用 MSBuild 进行编译,让发布更简单.在Bin目录中 rsvars.bat 用于设置编译的环境变量,结合使用就可以顺利进行编译. call r ...
- Android判断用户是平板还是手机的方法
public boolean isTabletDevice() { TelephonyManager telephony = (TelephonyManager) mContext.ge ...
- RegisterClientScriptBlock CommandName 模块列 操作完成 提示
this.ClientScript.RegisterClientScriptBlock(this.GetType(), "Remind", "alert('获取成功!') ...
- MyEcpilise引入Maven项目目录不正常,无JRE,无Maven Dependencies
右键项目--> Maven4MyEclipse --> Update Project Configuration