1.前言
本文所述关于内存管理的系列文章主要是对陈莉君老师所讲述的内存管理知识讲座的整理。
本讲座主要分三个主题展开对内存管理进行讲解:内存管理的硬件基础、虚拟地址空间的管理、物理地址空间的管理.
本文将主要以X86架构为例来介绍Linux内存管理的段机制。
2.段机制
- 段是虚拟地址空间的基本单位
- 段机制必须把虚拟地址空间的一个地址转换为线性地址空间的一个线性地址
- 可以从三个方面来描述段(如下图)
段的基地址(Base):在线性地址空间中段的起始地址;
段的界限(Limit):在虚拟地址空间中,段内可以使用的最大偏移量;
段的保护属性:表示段的特性。例如该段是否可以被读出或写入。或者该段是否可以作为一个程序来执行,以及段的特权等级
图 段描述符的一般格式
- 段描述符表
图 段描述符表(段表)
段号描述虚拟地址空间段的编号;基地址是线性地址空间段的起始地址
- 保护模式下其它描述符表
全局描述符表(GDT)
中断描述符表(IDT)
局部描述符表(LDT)
注:为加快对如上描述符表的访问,intel为之设计了专门的寄存器,存放描述符表的基地址及表的长度界限
- 保护模式下的段寄存器
段寄存器存放段描述符表的索引(段号,也叫选择符),即从段描述符表中选择某个段.
其中:
索引(13位指明段号);
TI(Table Index)指明使用全局描述符表还是局部描述符表;
RPL(Requestor Previlege Level):2位指定4种特权级,用来决定内核态还是用户态
图 保护模式下的段寄存器
- 保护模式下的特权级
保护模式提供了四个特权级,用0~3四个数字表示
一般操作系统只使用了其中最低和最高两个,即0表示最高特权级,内核态;3表示最低特权级,用户态
保护模式规定高特权级可随意访问低特权级,低特权级不能随便访问高特权级
- 地址转换及保护
图 地址转换
程序中的虚拟地址可以表示为“选择符:偏移量”这样的形式,通过以下步骤可以把一个虚拟地址转换为线性地址:
(1)在段寄存器中装入段选择符,同时把32位地址偏移量装入某个寄存器(比如ESI、EDI等)中。
(2)根据选择符中的索引值、TI及RPL值,再根据相应描述符表中的段基地址和段界限,进行一系列合法性检查(如特权级检查、界限检查),如果该段无问题,就取出相应的描述符放入段描述符高速缓冲寄存器3中。
(3)将描述符中的32位段基地址和放在ESI、EDI等中的32位有效地址相加,就形成了32位线性地址。
注意,在上面的地址转换过程中,从两个方面对段进行了保护:
(1) 在一个段内,如果偏移量大于段界限,虚拟地址将没有意义,系统将产生异常。
(2) 如果要对一个段进行访问,系统会根据段的保护属性检查访问者是否具有访问权限,如果没有,则产生异常。例如,如果要在只读段中进行写入,系统将根据该段的属性检测到这是一种违规操作,则产生异常。
3.Linux如何处理段
Linux将整个虚拟地址空间划分为一个段,段的基地址为0,段的界限为4G。
由于很多体系结构并不要求段机制必选,因此这样处理即使用了段机制又巧妙的化解了段机制
- IA32上设计操作系统时无法回避使用段机制
在IA32上任意给出的地址都是一个虚拟地址,即任意一个地址都是通过“选择符:偏移量”的方式给出的,这是段机制存访问模式的基本特点
所以在IA32上设计操作系统时无法回避使用段机制,IA32规定段机制是不可禁止的,因此不可能绕过它直接给出线性地址空间的地址
一个虚拟地址最终会通过“段基地址+偏移量”的方式转化为一个线性地址
- 为何要绕过段机制
由于绝大多数硬件平台都不支持段机制,只支持分页机制,所以为了让Linux具有更好的可移植性,我们需要去掉段机制而只使用分页机制
- Linux如何绕过段机制
Linux的设计人员干脆让段的基地址为0,而段的界限为4GB,这时任意给出一个偏移量,则等式为“0+偏移量=线性地址”,也就是说“偏移量=线性地址”。
另外由于段机制规定“偏移量 <4GB”,所以偏移量的范围为0H~FFFFFFFFH,这恰好是线性地址空间范围,也就是说虚拟地址直接映射到了线性地址,
我们以后所提到的虚拟地址和线性地址指的也就是同一地址。
看来,Linux在没有回避段机制的情况下巧妙地把段机制给绕过去了。
- Linux所必须创建哪些段
由于IA32段机制还规定,必须为代码段和数据段创建不同的段,所以Linux必须为代码段和数据段分别创建一个基地址为0,段界限为4GB的段描述符。
不仅如此,由于Linux内核运行在特权级0,而用户程序运行在特权级别3,根据IA32的段保护机制规定,特权级3的程序是无法访问特权级为0的段的,所以Linux必须为内核和用户程序分别创建其代码段和数据段。
这就意味着Linux必须创建4个段描述符——特权级0的代码段和数据段,特权级3的代码段和数据段。
- Linux通过分页机制来保护内存
如果这么定义段,则上一节所说的段保护的第一个作用就失去了,因为这些段使用完全相同的线性地址空间(0~4GB),它们互相覆盖。
可以设想,如果不使用分页的话,线性地址空间直接被映射到物理空间,则你修改任何一个段的数据,都会同时修改其它段的数据
段机制所提供的通过“基地址:界限”方式本来将线性地址空间分割,以让段与段之间完全隔离,这种实现段保护的方式根本就不起作用了。
那么,这是不是意味着用户可以随意修改内核数据?显然不是的,这是因为,一方面用户段和内核段具有不同的特权级别,另一方面,Linux之所以这么定义段,正是为了实现一个纯的分页,而分页机制会提供给我们所需要的保护。
4.参考文献
[1] Linux内存管理讲座PPT-陈莉君