• 逻辑地址、线性地址、物理地址


    一、基本概念

    1)物理地址(physical address)
         用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。
    2)逻辑地址(logical address)
         Intel为了兼容,将远古时代的段式内存管理方式保留了下来。逻辑地址指的是机器语言指令中,用来指

         定一个操作数或者是一条指令的地址。

    3)线性地址(linear address)

         总的来说,CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步:

         首先将给定一个逻辑地址,CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线性地

         址,  再利用其页式内存管理单元,转换为最终物理地址。

    二、逻辑地址—>线性地址—段式内存管理

         一个逻辑地址由两部份组成:段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。

    索引号,即段描述符表索引。段选择符的前13位,通过索引可以在段描述符表中找到一个具体的段描述符,这个描述符描述了某个段的相关信息:

     

    Intel设计的本意是,一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。那究竟什么时候该用GDT,什么时候该用LDT呢?这是由段选择符中的T1字段表示的:

    (1)为0,表示用GDT

    (2)为1,表示用LDT
    GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。

     

    给定一个完整的逻辑地址[段选择符:段内偏移地址]
    1>看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。
    2>拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。
    3>把Base + offset,就是要转换的线性地址了。

     

    三、Linux段式管理

    include/asm-i386/segment.h

     

    #define GDT_ENTRY_DEFAULT_USER_CS        14
    
    #define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3)
    
     
    
    #define GDT_ENTRY_DEFAULT_USER_DS        15
    
    #define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)
    
     
    
    #define GDT_ENTRY_KERNEL_BASE        12
    
     
    
    #define GDT_ENTRY_KERNEL_CS                (GDT_ENTRY_KERNEL_BASE + 0)
    
    #define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
    
     
    
    #define GDT_ENTRY_KERNEL_DS                (GDT_ENTRY_KERNEL_BASE + 1)
    
    #define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8)

     

     

    把其中的宏替换成数值,则为:

     

    1 #define __USER_CS 115        [00000000 1110  0  11]
    2 
    3 #define __USER_DS 123        [00000000 1111  0  11]
    4 
    5 #define __KERNEL_CS 96       [00000000 1100  0  00]
    6 
    7 #define __KERNEL_DS 104      [00000000 1101  0  00]

     

     

    方括号后是这四个段选择符的16位二制表示,它们的索引号和T1字段值也可以算出来了

    __USER_CS             index= 14   T1=0

    __USER_DS             index= 15   T1=0

    __KERNEL_CS           index= 12   T1=0

    __KERNEL_DS           index= 13   T1=0

    T1均为0,则表示都使用了GDT,再来看初始化GDT的内容中相应的12-15项(arch/i386/head.S):

    .quad 0x00cf9a000000ffff        /* 0x60 kernel 4GB code at 0x00000000 */

    .quad 0x00cf92000000ffff        /* 0x68 kernel 4GB data at 0x00000000 */

    .quad 0x00cffa000000ffff        /* 0x73 user 4GB code at 0x00000000 */     

    .quad 0x00cff2000000ffff        /* 0x7b user 4GB data at 0x00000000 */

    根据段描述符各字段展开,发现16-31位全为0,即四个段的基地址全为0
    于是,给定一个段内偏移地址,按照前面转换公式,0 + 段内偏移==>为线性地址,可以得出重要的结论:

    在Linux下,逻辑地址与线性地址总是一致的,即逻辑地址的偏移量字段的值与线性地址的值总是相同的。

     

    四、CPU的页式内存管理

        CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址。

        从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有2的20个次方个页。这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址——对应的页的地址。

         另一类“页”,我们称之为物理页,或者是页框、页桢的。是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。

    这里注意到,这个total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单要表示这么一个数组,就要占去4MB的内存空间。为了节省空间,引入了一个二级管理模式的机器来组织分页单元

     

    1、分页单元中,页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。
    2、每一个活动的进程,因为都有其独立的对应的虚似内存,那么它也对应了一个独立的页目录地址。

    3、每一个32位的线性地址被划分为三部分,页目录索引(10位):页表索引(10位):偏移(12位)
    依据以下步骤进行转换:
    1、从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);
    2、根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。真正

        的页的地址被放到页表中去了。

    3、根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;
    4、将页的起始地址与线性地址中最后12位相加,得到最终的物理地址;

  • 相关阅读:
    solr的学习
    springMvc学习地址新
    mybatis教程
    spring学习地址
    spring思想分析
    java中反射讲解及实例
    学习地址
    linux常用命令
    XML Data Type Methods(一)
    Ruby中 使用Builder Xml Markup 操作XML
  • 原文地址:https://www.cnblogs.com/X-W-LIU/p/3455734.html
Copyright © 2020-2023  润新知