深入理解Linux内核之内存寻址笔记-2

来源:互联网 发布:程序员团队名称和口号 编辑:程序博客网 时间:2024/06/05 19:42
    一个逻辑地址由两个部分组成,一个是由段标识符和一个指定段内相对地址的偏移量,而这个段标识符是一个16位长的字段来表示,偏移量是32位的字段描述。操作过程中,为了能够快速地找到所需要的段,处理器提供了段寄存器,这个段的唯一标识形式是段寄存器,分为cs,ss,ds,es,fs与gs。
    六个寄存器中有三个是有专门用处:
    cs  代码段寄存器,指向包含程序指令的段。
    ss  堆段寄存器,指向包含当前程序栈的段。
    ds  数据段寄存器,指向包含静态数据或全局数据段。
    每个段描述符由一个8字节的段描述符(Segment Descriptor)表示,它描述了段的特征。段描述符放在全局描述符(GDT)中,或局部描述符(LDT)中。cs段寄存器还有一个重要的功能,它含有一个两位的字段,用来指明当前CPU的特权级。如果其值是0代表是最高的优先级,3代表最低的优先级,在Linux中只有两个优先级,这两个就是通常所说的用户态和内核态。
    下面来看看Linux-2.6.30.4版本的内核代码中,段寄存器的描述符是怎么样,结合起来分析。打开目录/arch/x86/include/asm/desc_defs.h中的文件 
/* 8 byte segment descriptor */
struct desc_struct {
 union {
  struct {
   unsigned int a;
   unsigned int b;
  };
  struct {
   u16 limit0;  
 //这里的16字节表示段最后一个字节的偏移量,在这里还有一个g位,如果这个位是0,则段大小在1个字节到1MB之间,否则将在4K到4G大小变化
   u16 base0;   
 //作为段的首4个字节,base0,base0,base1,base2形成所在段的线性地址,其中base0占用16个字节
   unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1; 
//base2占用8个字节,type描述段类型特征和存取权限,dpl是描述符特权级
                                                   //Linux总是把p置为1,因为它不会把整个段都交换到磁盘上去
   unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;  
 //base2占用8个字节,avl是被Linux忽略,段的偏移量是32位则d置为1
  };
 };
 
快速访问段描述符:
    我们知道,段描述符是由一个16位段选择符和一个32位的偏移量组成,而段寄存器仅仅保存段选择符。80X86提供一种附加的非编程的寄存器,供6个可编程的段寄存器使用,每一个非编程的寄存器含有8个段描述符,由相应的段寄存器中的段选择符来指定。每当一个段选择符被装入段寄存器时,相应的段描述符就由内存装入到对应的非编程CPU寄存器,此时处理器直接引用存放在段寄存器中的CPU寄存器即可。
    因为一个段描述符是8字节长,因此它在GDT或者LDT内的相对地址是由段选择符的最高13位的值乘于8得到。例如GDT在0x00020000(这个值是保存在gdtr中)且由段选择符所指定的索引号为2,那么相应的段描述符地址是0x00020000 + (2 * 8),或者是0x00020010。
 
分段单元:
把一个逻辑地址转换成一个线性地址,分段单元将进行如何操作:
1. 先检查段选择符的TI字段,以决定段描述符保存在哪一个描述符表中。TI字段指明描述符是在GDT中还是在激活的LDT中。
2. 从段选择符的index字段计算段描述符的地址,index字段的值乘于8(一个段描述符的大小),这个结果与gdtr或者是ldtr寄存器中的内容相加。
3. 把逻辑地址的偏移量与段描述符base字段的值相加就可以得到线性地址。
    到了这里应该对如何把逻辑地址转换为线性地址有一个比较清晰的了解。
    当然需要注意一点:有了与段寄存器相关的不可编程的寄存器,只有当段寄存器的内容被改变时才需要执行前面的两个操作。
 
 
Linux的分段:
    我们知道,内核不能寻址超过1G大小的RAM空间,因此当RAM的空间大于1G的时候,就会被引入一个比较模糊的概念---分段。80x86体系的处理器中,它们鼓励程序员把程序化分成逻辑上相关的实体,例如子程序或者全局与局部数据区。但是我们的Linux并不是完全地使用这个机制,它只是以极为有限的方式引入这种方式。分段可以把每一个进程分配不同的线性地址空间,而分页则可以把相同的线性地址空间映射到不同的物理空间。在Linux的2.6版本中,进行在内核态的所有Linux进程都使用一对相同的段对指令和数据寻址:它们分别叫做内核代码段和用户数据段。
注意:与段相关的线性地址从0开始,这可以达到2^32 -1的寻址限长。也就是说在用户态或者内核态下的所有进程可以使用相同的逻辑地址。Linux下逻辑地址和线性地址都是一样的。
 
硬件中的分页:
   分页单元把线性地址转换为物理地址。一个关键的任务是把所请求的的访问类型与线性地址的访问权限相比较,如果这些访问无效则产生一个缺页异常。为了效率起见,线性地址被分为以固定长度为单位的组,称为页(page)。分页单元把所有RAM都分成固定长度的页框(page frame),有时也叫做物理页。每一个页框包含一个页,即一个页框跟一个页的长度是一至的。页框是主存的一部分,是一个存储区域。
   区分一个页和一个页框是很重要的,一页是一个数据块,它可以存放在任何一个页框或者磁盘中。把线性地址映射到物理地址的数据结构称为页表(page table)。页表存放在主存中,并在启用分页之前必须由内核页表进行适当的初始化。从80386开始,所有的80X86处理器支持分页,它通过设置cr寄存器的PG标志启用。当PG=0时,线性地址就被解释成物理地址。线性地址转换成物理地址分为两步完成,每一个都是基于转换表,第一种转换表称为页目录表(page directory),第二种转换表称为页表(page table).每一个活动进程都必须有一个分配给它的页目录。
 
Linux中的分页:
    Linux采用了一种同时适用于32位和64位系统的的普通分页模型。但是到了2.6.11版本开始,Linux采用了四级分页的模型:
        页全局目录 (page global directory)
        页上级目录 (page upper directory)
        页中间级目录 (page middle directory)
        页表  (page table)
在内核的初始化阶段,内核必须建立一个物理地址映射来指定哪些物理地址范围对内核是可用而哪些不可用。一般来说,Linux内核安装在RAM中从物理地址0x00100000开始的地方,即第二个RAM开始。它之所以没有安装在在RAM的第一个MB开始,是因为PC体系结构有几个独立的地方:
    1. 页框0由BIOS使用,存放加电自检期间检查到的系统硬件配置。
    2. 物理地址从0x000a0000到0x000fffff的范围通常留给BIOS例程,并且映射ISA图形卡上的内部内存。
    3. 第一个MB内的其他页框可能由特定计算机模型保留。
 
 
 
原创粉丝点击