精华内容
下载资源
问答
  • 这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,...

    1、概念解释

    物理地址:
      用于内存芯片级的单元寻址,与地址总线相对应。这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,但是事实上,这只是一个硬件提供给软件的抽像,内存的寻址方式并不是这样。所以,说它是“与地址总线相对应”,是更贴切一些,不过抛开对物理内存寻址方式的考虑,直接把物理地址与物理的内存一一对应,也是可以接受的。也许错误的理解更利于形而上的抽像。

    虚拟内存:
      这是对整个内存(不要与机器上插那条对上号)的抽像描述。它是相对于物理内存来讲的,可以直接理解成“不直实的”,“假的”内存,例如,一个0x08000000内存地址,它并不对就物理地址上那个大数组中0x08000000 - 1那个地址元素;有了这样的抽像,一个程序,就可以使用比真实物理地址大得多的地址空间。(拆东墙,补西墙,银行也是这样子做的),甚至多个进程可以使用相同的地址。不奇怪,因为转换后的物理地址并非相同的。
      ——可以把连接后的程序反编译看一下,发现连接器已经为程序分配了一个地址,例如,要调用某个函数A,代码不是call A,而是call 0x0811111111 ,也就是说,函数A的地址已经被定下来了。没有这样的“转换”,没有虚拟地址的概念,这样做是根本行不通的。
    打住了,这个问题再说下去,就收不住了。

    逻辑地址:
      可以认为是cpu执行程序过程中的一种中间地址。Intel为了兼容,将远古时代的段式内存的管理方式保留了下来,至于为什么会产生段式内存的管理方式,参见[2]。一个逻辑地址,是由一个段标识符加上一个指定段内的相对地址的偏移量,表示为[段标识符:段内偏移量],也就是说上面例子中的那个0x08111111应该表示为 [A的代码的段标识符:0x08111111] 这样才完整一些。

    线性地址:
      线性地址,也即虚拟地址,如果逻辑地址对应的是硬件平台段式管理转换前的地址的话,那么线性地址则对应了硬件页式内存的转换前的地址。

    2、初步理解

    对上面的各种地址的阶段性总结如下:
      CPU将一个虚拟地址空间的地址转换为物理地址,需要进行两步:首先将给定的逻辑地址,即[段标识符:段内偏移量]这样的形式,利用段式管理单元,转化为线性地址,然后利用页式内存管理单元,转化为最终的物理地址。图形表示如下(下图中的左半部分):
    这里写图片描述

    3、深层次的理解

    更进一步的对于段式内存管理和页式内存管理的解释如下:
     - 段标识符也即段选择符,它用来从段描述符表中选择一个具体的段,某个段描述符表项的base字段描述了一个段的开始位置的线性地址。
     - gdt是全局段描述符表,
     - ldt是局部段描述符表,
     - 段选择符中的TI = 0表示用GDT,TI=1表示用LDT
     - gdt在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而ldt则在ldtr寄存器中。

    3.1、段式管理单元

    有了上面的这些概念,对于上图的右半部分的段式管理单元就好理解了:
      程序过来一个逻辑地址,使用其段标识符(也即段选择符)的Index字段去索引段描述符表,若TI=0,索引全局段描述符表,TI=1,索引局部段描述符表,表的地址在相应的寄存器中。通过Index字段和段描述符表的位置能找到某项具体的段描述符。将段描述符中的base字段和逻辑地址中的offset字段合并即得到了线性地址。
      按照Intel的本意,全局的用GDT,每个进程自己的用LDT——不过Linux则对所有的进程都使用了相同的段来对指令和数据寻址。即用户数据段,用户代码段,对应的,内核中的是内核数据段和内核代码段。[1]中有介绍,四个段的基地址全为0。这样,给定一个段内偏移地址,按照前面转换公式,0 + 段内偏移,转换为线性地址,可以得出重要的结论,“在Linux下,逻辑地址与线性地址总是一致(是一致,不是有些人说的相同)的,即逻辑地址的偏移量字段的值与线性地址的值总是相同的。!!!”所以如果做linux下内核开发,对于上述的x86的段式管理可以完全不用理会,我们可以认为linux根本没有用intel弄出来的这个段式管理,而是以页式管理完成了所有的内存管理工作。

    3.2、页式管理单元:

      CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址。从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有2的20个次方个页。这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址——对应的页的地址。
      另一类“页”,我们称之为物理页,或者是页框、页桢的。是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。
      这里注意到,这个total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单要表示这么一个数组,就要占去4MB的内存空间。为了节省空间,引入了一个二级管理模式的机器来组织分页单元。如上图中的页式管理单元部分,我们单独拿出来看:
    这里写图片描述
    1、分页单元中,页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。万里长征就从此长始了。
    2、每一个活动的进程,因为都有其独立的对应的虚似内存(页目录也是唯一的),那么它也对应了一个独立的页目录地址。——运行一个进程,需要将它的页目录地址放到cr3寄存器中,将别个的保存下来。
    3、每一个32位的线性地址被划分为三部份,面目录索引(10位):页表索引(10位):偏移(12位)
    4、依据以下步骤进行转换:
     ① 从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);
     ② 根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。(又引入了一个数组),页的地址被放到页表中去了。
     ③ 根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;
     ④ 将页的起始地址与线性地址中最后12位相加,得到最终我们想要的葫芦;

    参考:
    [1] LINUX 逻辑地址、线性地址、物理地址和虚拟地址
     (原文地址:http://bbs.chinaunix.net/thread-2083672-1-1.html,并且评论的第4页有错误修正)
    [2] 为什么会有段式内存管理机制

    疑问解答:
    1、我理解的所谓的内存映射,不过是将虚拟地址空间 和 可执行文件建立映射关系,这种映射关系的建立,可能是做一张表,来记录虚拟地址空间中的地址和可执行文件在磁盘上的地址的对应关系。这样当程序执行第一条指令时,会发生缺页中断,根据之前建立的映射关系从磁盘中拿到需要的东西搬移到物理内存中,并且将物理内存的地址和此时的第一条指令的虚拟地址这一对映射关系写到页表中。
      该异常是虚拟内存机制赖以存在的基本保证——它会告诉内核去真正为进程分配物理页,并建立对应的页表,这之后虚拟地址才实实在在地映射到了系统的物理内存上。——《Linux内存管理(最透彻的一篇)
    2、程序的内存映像从低地址到高地址依次是:
    · txt段
    · data段: 已初始化的全局变量和已初始化的static变量;
    · bss段: 未初始化的全局变量和未初始化的static变量以及初始化为零的全局变量和静态变量(参考这里这里)(未初始化的全局变量和static变量,系统自动赋值为零。这个段在编译成 .exe可执行文件时,只是标记了一下这个段的大小,并没有实际的分配全为零的页框。
      例如:一个程序的txt段的大小是8kB,初始化数据段的大小是8kB,未初始化的数据段(BSS)的大小是4kB,那么可执行文件的大小是 16kB(代码+初始化的数据)加上一个很短的头部来告诉系统在初始化的数据后再另外分配4KB,同时在程序启动之后把他们初始化零为0。这个技巧巧妙的避免了在可执行文件中中存储4kB的0.
      更进一步,为了避免分配一个全是0的物理页框,在初始化的时候,linux就分配了一个静态零页面,即一个全为零的写保护页面。当加载程序的时候,未初始化的数据区域被设置为指向该零页面。当一个进程真正要写这个区域的时候,写时复制机制就开始起作用,一个实际的页框就被分配给该进程。——《现代操作系统P428》
    · 堆: 通常情况下堆也是请求二进制零的页面** ,往上生长——《深入理解计算机系统P585, P587》
    · 栈: 通常情况下往下生长
    · 共享库的内存映射区域:在用户堆和栈之间存在一个共享库的内存映射区域,比如标准C库的libc.so,这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。

      不管怎么样,在进程切换的时候,要将进程的 页表基地址 装入到 页表基址寄存器中,这个页表基址寄存器在X86架构中是cr3寄存器,在arm架构中是TTB寄存器,详细见:ARM协处理器CP15
      并且,为了在切换进程时,不完全的刷新TLB,还需要将进程id记录到TLB中,只有这样才能知道TLB中当前的表项是否是是当前进程的(因为此时TLB的寻址是使用的虚拟寻址,两个不同的进程可能使用同一个虚拟地址来寻址),在X86架构中实现这种功能的一个结构叫做PCID(进程上下文标识符),在ARM结构中叫做ASID,详见:什么是TLB和PCID?为什么要有PCID?为什么Linux现在才开始使用它?swapper进程修改页表项 vs kvm中guest页表的写保护

    展开全文
  • 本贴涉及的硬件平台是X86,如果是其它平台,不保证能...物理地址(physical address) 用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。 ——这个概念应该是这几个概念中最好理解的一个,但是值得一

    参考:http://bbs.chinaunix.net/thread-2083672-1-1.html

    本贴涉及的硬件平台是X86,如果是其它平台,不保证能一一对号入座,但是举一反三,我想是完全可行的。


    一、概念

    物理地址(physical address)
    用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。
    ——这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,但是事实上,这只是一个硬件提供给软件的抽像,内存的寻址方式并不是这样。所以,说它是“与地址总线相对应”,是更贴切一些,不过抛开对物理内存寻址方式的考虑,直接把物理地址与物理的内存一一对应,也是可以接受的。也许错误的理解更利于形而上的抽像。

    虚拟内存(virtual memory)
    这是对整个内存(不要与机器上插那条对上号)的抽像描述。它是相对于物理内存来讲的,可以直接理解成“不直实的”,“假的”内存,例如,一个0x08000000内存地址,它并不对就物理地址上那个大数组中0x08000000 - 1那个地址元素;
    之所以是这样,是因为现代操作系统都提供了一种内存管理的抽像,即虚拟内存(virtual memory)。进程使用虚拟内存中的地址,由操作系统协助相关硬件,把它“转换”成真正的物理地址。这个“转换”,是所有问题讨论的关键。
    有了这样的抽像,一个程序,就可以使用比真实物理地址大得多的地址空间。(拆东墙,补西墙,银行也是这样子做的),甚至多个进程可以使用相同的地址。不奇怪,因为转换后的物理地址并非相同的。
    ——可以把连接后的程序反编译看一下,发现连接器已经为程序分配了一个地址,例如,要调用某个函数A,代码不是call A,而是call 0x0811111111 ,也就是说,函数A的地址已经被定下来了。没有这样的“转换”,没有虚拟地址的概念,这样做是根本行不通的。
    打住了,这个问题再说下去,就收不住了。

    逻辑地址(logical address)
    Intel为了兼容,将远古时代的段式内存管理方式保留了下来。逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。以上例,我们说的连接器为A分配的0x08111111这个地址就是逻辑地址。
    ——不过不好意思,这样说,好像又违背了Intel中段式管理中,对逻辑地址要求,“一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量],也就是说,上例中那个0x08111111,应该表示为[A的代码段标识符: 0x08111111],这样,才完整一些”

    线性地址(linear address)或也叫虚拟地址(virtual address)
    跟逻辑地址类似,它也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件页式内存的转换前地址。

    -------------------------------------------------------------
    CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址(其实是段内偏移量,这个一定要理解!!!),CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。

    这样做两次转换,的确是非常麻烦而且没有必要的,因为直接可以把线性地址抽像给进程。之所以这样冗余,Intel完全是为了兼容而已。

    2、CPU段式内存管理,逻辑地址如何转换为线性地址
    一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节,如图:
    Snap1.jpg 
    最后两位涉及权限检查,本贴中不包含。

    索引号,或者直接理解成数组下标——那它总要对应一个数组吧,它又是什么东东的索引呢?这个东东就是“段描述符(segment descriptor)”,呵呵,段描述符具体地址描述了一个段(对于“段”这个字眼的理解,我是把它想像成,拿了一把刀,把虚拟内存,砍成若干的截——段)。这样,很多个段描述符,就组了一个数组,叫“段描述符表”,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段,我刚才对段的抽像不太准确,因为看看描述符里面究竟有什么东东——也就是它究竟是如何描述的,就理解段究竟有什么东东了,每一个段描述符由8个字节组成,如下图:
    Snap2.jpg 
    这些东东很复杂,虽然可以利用一个数据结构来定义它,不过,我这里只关心一样,就是Base字段,它描述了一个段的开始位置的线性地址。

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

    GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。

    好多概念,像绕口令一样。这张图看起来要直观些:
    Snap3.jpg 
    首先,给定一个完整的逻辑地址[段选择符:段内偏移地址],
    1、看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。
    2、拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。
    3、把Base + offset,就是要转换的线性地址了。

    还是挺简单的,对于软件来讲,原则上就需要把硬件转换所需的信息准备好,就可以让硬件来完成这个转换了。OK,来看看Linux怎么做的。

    3、Linux的段式管理
    Intel要求两次转换,这样虽说是兼容了,但是却是很冗余,呵呵,没办法,硬件要求这样做了,软件就只能照办,怎么着也得形式主义一样。
    另一方面,其它某些硬件平台,没有二次转换的概念,Linux也需要提供一个高层抽像,来提供一个统一的界面。所以,Linux的段式管理,事实上只是“哄骗”了一下硬件而已。

    按照Intel的本意,全局的用GDT,每个进程自己的用LDT——不过Linux则对所有的进程都使用了相同的段来对指令和数据寻址。即用户数据段,用户代码段,对应的,内核中的是内核数据段和内核代码段。这样做没有什么奇怪的,本来就是走形式嘛,像我们写年终总结一样。
    include/asm-i386/segment.h
    1. #define GDT_ENTRY_DEFAULT_USER_CS        14
    2. #define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3)

    3. #define GDT_ENTRY_DEFAULT_USER_DS        15
    4. #define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)

    5. #define GDT_ENTRY_KERNEL_BASE        12

    6. #define GDT_ENTRY_KERNEL_CS                (GDT_ENTRY_KERNEL_BASE + 0)
    7. #define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)

    8. #define GDT_ENTRY_KERNEL_DS                (GDT_ENTRY_KERNEL_BASE + 1)
    9. #define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8)
    复制代码

    把其中的宏替换成数值,则为:
    1. #define __USER_CS 115        [00000000 1110  0  11]
    2. #define __USER_DS 123        [00000000 1111  0  11]
    3. #define __KERNEL_CS 96      [00000000 1100  0  00]
    4. #define __KERNEL_DS 104    [00000000 1101  0  00]
    复制代码


    方括号后是这四个段选择符的16位二制表示,它们的索引号和T1字段值也可以算出来了
    1. __USER_CS              index= 14   T1=0
    2. __USER_DS               index= 15   T1=0
    3. __KERNEL_CS           index=  12  T1=0
    4. __KERNEL_DS           index= 13   T1=0
    复制代码


    T1均为0,则表示都使用了GDT,再来看初始化GDT的内容中相应的12-15项(arch/i386/head.S):
    1.         .quad 0x00cf9a000000ffff        /* 0x60 kernel 4GB code at 0x00000000 */
    2.         .quad 0x00cf92000000ffff        /* 0x68 kernel 4GB data at 0x00000000 */
    3.         .quad 0x00cffa000000ffff        /* 0x73 user 4GB code at 0x00000000 */
    4.         .quad 0x00cff2000000ffff        /* 0x7b user 4GB data at 0x00000000 */
    复制代码


    按照前面段描述符表中的描述,可以把它们展开,发现其16-31位全为0,即四个段的基地址全为0。

    这样,给定一个段内偏移地址,按照前面转换公式,0 + 段内偏移,转换为线性地址,可以得出重要的结论,“在Linux下,逻辑地址与线性地址总是一致(是一致,不是有些人说的相同)的,即逻辑地址的偏移量字段的值与线性地址的值总是相同的。!!!”

    忽略了太多的细节,例如段的权限检查。呵呵。

    Linux中,绝大部份进程并不例用LDT,除非使用Wine ,仿真Windows程序的时候。

    4.CPU的页式内存管理

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

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

    这里注意到,这个total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单要表示这么一个数组,就要占去4MB的内存空间。为了节省空间,引入了一个二级管理模式的机器来组织分页单元。文字描述太累,看图直观一些:
    Snap1.jpg 
    如上图,
    1、分页单元中,页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。万里长征就从此长始了。
    2、每一个活动的进程,因为都有其独立的对应的虚似内存(页目录也是唯一的),那么它也对应了一个独立的页目录地址。——运行一个进程,需要将它的页目录地址放到cr3寄存器中,将别个的保存下来。
    3、每一个32位的线性地址被划分为三部份,面目录索引(10位):页表索引(10位):偏移(12位)
    依据以下步骤进行转换:
    1、从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);
    2、根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。(又引入了一个数组),页的地址被放到页表中去了。
    3、根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;
    4、将页的起始地址与线性地址中最后12位相加,得到最终我们想要的葫芦;

    这个转换过程,应该说还是非常简单地。全部由硬件完成,虽然多了一道手续,但是节约了大量的内存,还是值得的。那么再简单地验证一下:
    1、这样的二级模式是否仍能够表示4G的地址;
    页目录共有:2^10项,也就是说有这么多个页表
    每个目表对应了:2^10页;
    每个页中可寻址:2^12个字节。
    还是2^32 = 4GB

    2、这样的二级模式是否真的节约了空间;
    也就是算一下页目录项和页表项共占空间 (2^10 * 4 + 2 ^10 *4) = 8KB。哎,……怎么说呢!!!

    红色错误,标注一下,后文贴中有此讨论。。。。。。
    按<深入理解计算机系统>中的解释,二级模式空间的节约是从两个方面实现的:
    A、如果一级页表中的一个页表条目为空,那么那所指的二级页表就根本不会存在。这表现出一种巨大的潜在节约,因为对于一个典型的程序,4GB虚拟地址空间的大部份都会是未分配的;
    B、只有一级页表才需要总是在主存中。虚拟存储器系统可以在需要时创建,并页面调入或调出二级页表,这就减少了主存的压力。只有最经常使用的二级页表才需要缓存在主存中。——不过Linux并没有完全享受这种福利,它的页表目录和与已分配页面相关的页表都是常驻内存的。

    值得一提的是,虽然页目录和页表中的项,都是4个字节,32位,但是它们都只用高20位,低12位屏蔽为0——把页表的低12屏蔽为0,是很好理解的,因为这样,它刚好和一个页面大小对应起来,大家都成整数增加。计算起来就方便多了。但是,为什么同时也要把页目录低12位屏蔽掉呢?因为按同样的道理,只要屏蔽其低10位就可以了,不过我想,因为12>10,这样,可以让页目录和页表使用相同的数据结构,方便。

    本贴只介绍一般性转换的原理,扩展分页、页的保护机制、PAE模式的分页这些麻烦点的东东就不啰嗦了……可以参考其它专业书籍。

    5.Linux的页式内存管理
    原理上来讲,Linux只需要为每个进程分配好所需数据结构,放到内存中,然后在调度进程的时候,切换寄存器cr3,剩下的就交给硬件来完成了(呵呵,事实上要复杂得多,不过偶只分析最基本的流程)。

    前面说了i386的二级页管理架构,不过有些CPU,还有三级,甚至四级架构,Linux为了在更高层次提供抽像,为每个CPU提供统一的界面。提供了一个四层页管理架构,来兼容这些二级、三级、四级管理架构的CPU。这四级分别为:

    页全局目录PGD(对应刚才的页目录)
    页上级目录PUD(新引进的)
    页中间目录PMD(也就新引进的)
    页表PT(对应刚才的页表)。

    整个转换依据硬件转换原理,只是多了二次数组的索引罢了,如下图:
    Snap2.jpg 
    那么,对于使用二级管理架构32位的硬件,现在又是四级转换了,它们怎么能够协调地工作起来呢?嗯,来看这种情况下,怎么来划分线性地址吧!
    从硬件的角度,32位地址被分成了三部份——也就是说,不管理软件怎么做,最终落实到硬件,也只认识这三位老大。
    从软件的角度,由于多引入了两部份,,也就是说,共有五部份。——要让二层架构的硬件认识五部份也很容易,在地址划分的时候,将页上级目录和页中间目录的长度设置为0就可以了。
    这样,操作系统见到的是五部份,硬件还是按它死板的三部份划分,也不会出错,也就是说大家共建了和谐计算机系统。

    这样,虽说是多此一举,但是考虑到64位地址,使用四层转换架构的CPU,我们就不再把中间两个设为0了,这样,软件与硬件再次和谐——抽像就是强大呀!!!

    例如,一个逻辑地址已经被转换成了线性地址,0x08147258,换成二制进,也就是:
    0000100000 0101000111 001001011000
    内核对这个地址进行划分
    PGD = 0000100000
    PUD = 0
    PMD = 0
    PT = 0101000111
    offset = 001001011000

    现在来理解Linux针对硬件的花招,因为硬件根本看不到所谓PUD,PMD,所以,本质上要求PGD索引,直接就对应了PT的地址。而不是再到PUD和PMD中去查数组(虽然它们两个在线性地址中,长度为0,2^0 =1,也就是说,它们都是有一个数组元素的数组),那么,内核如何合理安排地址呢?
    从软件的角度上来讲,因为它的项只有一个,32位,刚好可以存放与PGD中长度一样的地址指针。那么所谓先到PUD,到到PMD中做映射转换,就变成了保持原值不变,一一转手就可以了。这样,就实现了“逻辑上指向一个PUD,再指向一个PDM,但在物理上是直接指向相应的PT的这个抽像,因为硬件根本不知道有PUD、PMD这个东西”。

    然后交给硬件,硬件对这个地址进行划分,看到的是:
    页目录 = 0000100000
    PT = 0101000111
    offset = 001001011000
    嗯,先根据0000100000(32),在页目录数组中索引,找到其元素中的地址,取其高20位,找到页表的地址,页表的地址是由内核动态分配的,接着,再加一个offset,就是最终的物理地址了。

    展开全文
  • 本文转自论文点击打开链接,主要讲述了操作系统中人们应该熟悉但是容易混淆的逻辑地址、线性地址和物理地址: 1、逻辑地址:  逻辑地址(Logical Address) :是指由程序产生的与段相关的偏移地址部分。例如,你...

     本文转自论文点击打开链接,主要讲述了操作系统中人们应该熟悉但是容易混淆的逻辑地址、线性地址和物理地址:

    1、逻辑地址:

          逻辑地址(Logical Address) :是指由程序产生的与段相关的偏移地址部分。例如,你在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址,不和绝对物理地址相干。只有在Intel实模式下,逻辑地址才和物理地址相等(因为实模式没有分段或分页机制,Cpu不进行自动地址转换);逻辑也就是在Intel 保护模式下程序执行代码段限长内的偏移地址(假定代码段、数据段如果完全一样)。程序员仅需与逻辑地址打交道,而分段和分页机制对您来说是完全透明的,仅由系统编程人员涉及。应用程序员虽然自己可以直接操作内存,那也只能在操作系统给你分配的内存段操作。

         如果是程序员,那么逻辑地址对你来说应该是轻而易举就可以理解的。我们在写C代码的时候经常说我们定义的结构体首地址的偏移量,函数的入口偏移量,数组首地址等等。当我们在考究这些概念的时候,其实是相对于你这个程序而言的。并不是对于整个操作系统而言的。也就是说,逻辑地址是相对于你所编译运行的具体的程序(或者叫进程吧,事实上在运行时就是当作一个进程来执行的)而言。你的编译好的程序的入口地址可以看作是首地址,而逻辑地址我们通常可以认为是在这个程序中,编译器为我们分配好的相对于这个首地址的偏移,或者说以这个首地址为起点的一个相对的地址值。
       当我们双击一个可执行程序时,就是给操作系统提供了这个程序运行的入口地址。之后shell把可执行文件的地址传入内核。进入内核后,会fork一个新的进程出来,新的进程首先分配相应的内存区域。这里会碰到一个著名的概念叫做Copy On Write,即写时复制技术。这里不详细讲述,总之新的进程在fork出来之后,新的进程也就获得了整个的PCB结构,继而会调用exec函数转而去将磁盘中的代码加载到内存区域中。这时候,进程的PCB就被加入到可执行进程的队列中,当CPU调度到这个进程的时候就真正的执行了。
       我们大可以把程序运行的入口地址理解为逻辑地址的起始地址,也就是说,一个程序的开始的地址。以及以后用到的程序的相关数据或者代码相对于这个起始地址的位置(这是由编译器事先安排好的),就构成了我们所说的逻辑地址。逻辑地址就是相对于一个具体的程序(事实上是一个进程,即程序真正被运行时的相对地址)而言的。尽管我们这样理解可能有一些细节上的偏差,但是比起网上一些含糊其辞,让人不知所云的描述要好得多,实用得多,等到自己对这个地址有更加深刻的理解的时候,再对上面的理解进行一些补充或者纠正。
       总之一句话,逻辑地址是相对于应用程序而言的。
    逻辑地址产生的历史背景:
        追根求源,Intel的8位机8080CPU,数据总线(DB)为8位,地址总线(AB)为16位。那么这个16位地址信息也是要通过8位数据总线来传送,也是要在数据通道中的暂存器,以及在CPU中的寄存器和内存中存放的,但由于AB正好是 
    DB的整数倍,故不会产生矛盾!
         但当上升到16位机后,Intel8086/8088CPU的设计由于当年IC集成技术和外封装及引脚技术的限制,不能超过40个引脚。但又感觉到8位机原来的地址寻址能力2^16=64KB太少了,但直接增加到16的整数倍即令AB=32位又是达不到的。故而只能把AB暂时增加4条成为20条。则 
    2^20=1MB的寻址能力已经增加了16倍。但此举却造成了AB的20位和DB的16位之间的矛盾,20位地址信息既无法在DB上传送,又无法在16位的CPU寄存器和内存单元中存放。于是应运而生就产生了CPU段结构的原理。

    2、线性地址:

       线性地址(linear address)或也叫虚拟地址(virtual address): 线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址可以再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。Intel 80386的线性地址空间容量为4G(2的32次方即32根地址总线寻址)。
       我们知道每台计算机有一个CPU(我们从单CPU来说吧。多CPU的情况应该是雷同的),最终所有的指令操作或者数据等等的运算都得由这个CPU来进行,而与CPU相关的寄存器就是暂存一些相关信息的存储记忆设备。因此,从CPU的角度出发的话,我们可以将计算机的相关设备或者部件简单分为两类:一是数据或指令存储记忆设备(如寄存器,内存等等),一种是数据或指令通路(如地址线,数据线等等)。线性地址的本质就是“CPU所看到的地址”(进程虚拟地址空间)。

        如果我们追根溯源,就会发现线性地址的就是伴随着Intel的X86体系结构的发展而产生的。当32位CPU出现的时候,它的可寻址范围达到4GB,而相对于内存大小来说,这是一个相当巨大的数字,我们也一般不会用到这么大的内存。那么这个时候CPU可见的4GB空间和内存的实际容量产生了差距。而线性地址就是用于描述CPU可见的这4GB空间。我们知道在多进程操作系统中,每个进程拥有独立的地址空间,拥有独立的资源。但对于某一个特定的时刻,只有一个进程运行于CPU之上。此时,CPU看到的就是这个进程所占用的4GB空间,就是这个线性地址。而CPU所做的操作,也是针对这个线性空间而言的。之所以叫线性空间,大概是因为人们觉得这样一个连续的空间排列成一线更加容易理解吧。其实就是CPU的可寻址范围。

    3、物理地址:

       物理地址(Physical Address) :是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了。
      
       逻辑地址与物理地址的“差距”是0xC0000000,是由于虚拟地址->线性地址->物理地址映射正好差这个值。这个值是由操作系统指定的。
       虚拟地址到物理地址的转化方法是与体系结构相关的。一般来说有分段、分页两种方式。以现在的x86 cpu为例,分段分页都是支持的。
        Memory Mangement Unit负责从虚拟地址到物理地址的转化。逻辑地址是段标识+段内偏移量的形式,MMU通过查询段表,可以把逻辑地址转化为线性地址。如果cpu没有开启分页功能,那么线性地址就是物理地址;如果cpu开启了分页功能,MMU还需要查询页表来将线性地址转化为物理地址:
                                            逻辑地址 ----(段表)---> 线性地址 — (页表)—> 物理地址
      
         不同的逻辑地址可以映射到同一个线性地址上;不同的线性地址也可以映射到同一个物理地址上;所以是多对一的关系。另外,同一个线性地址,在发生换页以后,也可能被重新装载到另外一个物理地址上。所以这种多对一的映射关系也会随时间发生变化。














    展开全文
  • 逻辑地址、线性地址、物理地址和虚拟地址 本贴涉及的硬件平台是X86,如果是其它平台,嘻嘻,不保证能一一对号入座,但是举一反三,我想是完全可行的。 一、概念 物理地址(physical address) 用于...
     逻辑地址、线性地址、物理地址和虚拟地址


    本贴涉及的硬件平台是X86,如果是其它平台,嘻嘻,不保证能一一对号入座,但是举一反三,我想是完全可行的。


    一、概念


    物理地址(physical address)
    用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。
    ——这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到 最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,但是事实上,这只是一个硬件提供给软件的抽像,内存的寻址方式并不是这样。所以,说它是“与 地址总线相对应”,是更贴切一些,不过抛开对物理内存寻址方式的考虑,直接把物理地址与物理的内存一一对应,也是可以接受的。也许错误的理解更利于形而上 的抽像。


    虚拟内存(virtual memory)
    这是对整个内存(不要与机器上插那条对上号)的抽像描述。它是相对于物理内存来讲的,可以直接理解成“不直实的”,“假的”内存,例如,一个0x08000000内存地址,它并不对就物理地址上那个大数组中0x08000000 - 1那个地址元素;
    之所以是这样,是因为现代操作系统都提供了一种内存管理的抽像,即虚拟内存(virtual memory)。进程使用虚拟内存中的地址,由操作系统协助相关硬件,把它“转换”成真正的物理地址。这个“转换”,是所有问题讨论的关键。
    有了这样的抽像,一个程序,就可以使用比真实物理地址大得多的地址空间。(拆东墙,补西墙,银行也是这样子做的),甚至多个进程可以使用相同的地址。不奇怪,因为转换后的物理地址并非相同的。
    ——可以把连接后的程序反编译看一下,发现连接器已经为程序分配了一个地址,例如,要调用某个函数A,代码不是call A,而是call 0x0811111111 ,也就是说,函数A的地址已经被定下来了。没有这样的“转换”,没有虚拟地址的概念,这样做是根本行不通的。
    打住了,这个问题再说下去,就收不住了。


    逻辑地址(logical address)
    Intel为了兼容,将远古时代的段式内存管理方式保留了下来。逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。以上例,我们说的连接器为A分配的0x08111111这个地址就是逻辑地址。
    ——不过不好意思,这样说,好像又违背了Intel中段式管理中,对逻辑地址要求,“一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量, 表示为 [段标识符:段内偏移量],也就是说,上例中那个0x08111111,应该表示为[A的代码段标识符: 0x08111111],这样,才完整一些”


    线性地址(linear address)或也叫虚拟地址(virtual address)
    跟逻辑地址类似,它也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件页式内存的转换前地址。


    -------------------------------------------------------------
    CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址(其实是段内偏移量,这个一定要理解!!!),CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。


    这样做两次转换,的确是非常麻烦而且没有必要的,因为直接可以把线性地址抽像给进程。之所以这样冗余,Intel完全是为了兼容而已。


    2、CPU段式内存管理,逻辑地址如何转换为线性地址
    一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节,如图:
    Snap1.jpg 
    最后两位涉及权限检查,本贴中不包含。


    索引号,或者直接理解成数组下标——那它总要对应一个数组吧,它又是什么东东的索引呢?这个东东就是“段描述符(segment descriptor)”,呵呵,段描述符具体地址描述了一个段(对于“段”这个字眼的理解,我是把它想像成,拿了一把刀,把虚拟内存,砍成若干的截—— 段)。这样,很多个段描述符,就组了一个数组,叫“段描述符表”,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描 述符就描述了一个段,我刚才对段的抽像不太准确,因为看看描述符里面究竟有什么东东——也就是它究竟是如何描述的,就理解段究竟有什么东东了,每一个段描 述符由8个字节组成,如下图:
    Snap2.jpg 
    这些东东很复杂,虽然可以利用一个数据结构来定义它,不过,我这里只关心一样,就是Base字段,它描述了一个段的开始位置的线性地址。


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


    GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。


    好多概念,像绕口令一样。这张图看起来要直观些:
    Snap3.jpg 
    首先,给定一个完整的逻辑地址[段选择符:段内偏移地址],
    1、看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。
    2、拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。
    3、把Base + offset,就是要转换的线性地址了。


    还是挺简单的,对于软件来讲,原则上就需要把硬件转换所需的信息准备好,就可以让硬件来完成这个转换了。OK,来看看Linux怎么做的。


    3、Linux的段式管理
    Intel要求两次转换,这样虽说是兼容了,但是却是很冗余,呵呵,没办法,硬件要求这样做了,软件就只能照办,怎么着也得形式主义一样。
    另一方面,其它某些硬件平台,没有二次转换的概念,Linux也需要提供一个高层抽像,来提供一个统一的界面。所以,Linux的段式管理,事实上只是“哄骗”了一下硬件而已。


    按照Intel的本意,全局的用GDT,每个进程自己的用LDT——不过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)
    复制代码


    把其中的宏替换成数值,则为:
    #define __USER_CS 115        [00000000 1110  0  11]
    #define __USER_DS 123        [00000000 1111  0  11]
    #define __KERNEL_CS 96      [00000000 1100  0  00]
    #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下,逻辑地址与线性地址总是一致(是一致,不是有些人说的相同)的,即逻辑地址的偏移量字段的值与线性地址的值总是相同的。!!!”


    忽略了太多的细节,例如段的权限检查。呵呵。


    Linux中,绝大部份进程并不例用LDT,除非使用Wine ,仿真Windows程序的时候。


    4.CPU的页式内存管理


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


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


    这里注意到,这个total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单要表示这么一个数组,就要占去4MB的内存空间。为了节省空间,引入了一个二级管理模式的机器来组织分页单元。文字描述太累,看图直观一些:
    Snap1.jpg 
    如上图,
    1、分页单元中,页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。万里长征就从此长始了。
    2、每一个活动的进程,因为都有其独立的对应的虚似内存(页目录也是唯一的),那么它也对应了一个独立的页目录地址。——运行一个进程,需要将它的页目录地址放到cr3寄存器中,将别个的保存下来。
    3、每一个32位的线性地址被划分为三部份,面目录索引(10位):页表索引(10位):偏移(12位)
    依据以下步骤进行转换:
    1、从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);
    2、根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。(又引入了一个数组),页的地址被放到页表中去了。
    3、根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;
    4、将页的起始地址与线性地址中最后12位相加,得到最终我们想要的葫芦;


    这个转换过程,应该说还是非常简单地。全部由硬件完成,虽然多了一道手续,但是节约了大量的内存,还是值得的。那么再简单地验证一下:
    1、这样的二级模式是否仍能够表示4G的地址;
    页目录共有:2^10项,也就是说有这么多个页表
    每个目表对应了:2^10页;
    每个页中可寻址:2^12个字节。
    还是2^32 = 4GB


    2、这样的二级模式是否真的节约了空间;
    也就是算一下页目录项和页表项共占空间 (2^10 * 4 + 2 ^10 *4) = 8KB。哎,……怎么说呢!!!
    红色错误,标注一下,后文贴中有此讨论。。。。。。
    按<深入理解计算机系统>中的解释,二级模式空间的节约是从两个方面实现的:
    A、如果一级页表中的一个页表条目为空,那么那所指的二级页表就根本不会存在。这表现出一种巨大的潜在节约,因为对于一个典型的程序,4GB虚拟地址空间的大部份都会是未分配的;
    B、只有一级页表才需要总是在主存中。虚拟存储器系统可以在需要时创建,并页面调入或调出二级页表,这就减少了主存的压力。只有最经常使用的二级页表才需要缓存在主存中。——不过Linux并没有完全享受这种福利,它的页表目录和与已分配页面相关的页表都是常驻内存的。


    值得一提的是,虽然页目录和页表中的项,都是4个字节,32位,但是它们都只用高20位,低12位屏蔽为0——把页表的低12屏蔽为0,是很好理解的,因 为这样,它刚好和一个页面大小对应起来,大家都成整数增加。计算起来就方便多了。但是,为什么同时也要把页目录低12位屏蔽掉呢?因为按同样的道理,只要 屏蔽其低10位就可以了,不过我想,因为12>10,这样,可以让页目录和页表使用相同的数据结构,方便。


    本贴只介绍一般性转换的原理,扩展分页、页的保护机制、PAE模式的分页这些麻烦点的东东就不啰嗦了……可以参考其它专业书籍。


    5.Linux的页式内存管理
    原理上来讲,Linux只需要为每个进程分配好所需数据结构,放到内存中,然后在调度进程的时候,切换寄存器cr3,剩下的就交给硬件来完成了(呵呵,事实上要复杂得多,不过偶只分析最基本的流程)。


    前面说了i386的二级页管理架构,不过有些CPU,还有三级,甚至四级架构,Linux为了在更高层次提供抽像,为每个CPU提供统一的界面。提供了一个四层页管理架构,来兼容这些二级、三级、四级管理架构的CPU。这四级分别为:


    页全局目录PGD(对应刚才的页目录)
    页上级目录PUD(新引进的)
    页中间目录PMD(也就新引进的)
    页表PT(对应刚才的页表)。


    整个转换依据硬件转换原理,只是多了二次数组的索引罢了,如下图:
    Snap2.jpg 
    那么,对于使用二级管理架构32位的硬件,现在又是四级转换了,它们怎么能够协调地工作起来呢?嗯,来看这种情况下,怎么来划分线性地址吧!
    从硬件的角度,32位地址被分成了三部份——也就是说,不管理软件怎么做,最终落实到硬件,也只认识这三位老大。
    从软件的角度,由于多引入了两部份,,也就是说,共有五部份。——要让二层架构的硬件认识五部份也很容易,在地址划分的时候,将页上级目录和页中间目录的长度设置为0就可以了。
    这样,操作系统见到的是五部份,硬件还是按它死板的三部份划分,也不会出错,也就是说大家共建了和谐计算机系统。


    这样,虽说是多此一举,但是考虑到64位地址,使用四层转换架构的CPU,我们就不再把中间两个设为0了,这样,软件与硬件再次和谐——抽像就是强大呀!!!


    例如,一个逻辑地址已经被转换成了线性地址,0x08147258,换成二制进,也就是:
    0000100000 0101000111 001001011000
    内核对这个地址进行划分
    PGD = 0000100000
    PUD = 0
    PMD = 0
    PT = 0101000111
    offset = 001001011000


    现在来理解Linux针对硬件的花招,因为硬件根本看不到所谓PUD,PMD,所以,本质上要求PGD索引,直接就对应了PT的地址。而不是再到PUD和 PMD中去查数组(虽然它们两个在线性地址中,长度为0,2^0 =1,也就是说,它们都是有一个数组元素的数组),那么,内核如何合理安排地址呢?
    从软件的角度上来讲,因为它的项只有一个,32位,刚好可以存放与PGD中长度一样的地址指针。那么所谓先到PUD,到到PMD中做映射转换,就变成了保 持原值不变,一一转手就可以了。这样,就实现了“逻辑上指向一个PUD,再指向一个PDM,但在物理上是直接指向相应的PT的这个抽像,因为硬件根本不知 道有PUD、PMD这个东西”。


    然后交给硬件,硬件对这个地址进行划分,看到的是:
    页目录 = 0000100000
    PT = 0101000111
    offset = 001001011000
    嗯,先根据0000100000(32),在页目录数组中索引,找到其元素中的地址,取其高20位,找到页表的地址,页表的地址是由内核动态分配的,接着,再加一个offset,就是最终的物理地址了。
    展开全文
  • 浅析逻辑地址与物理地址映射关系

    千次阅读 2018-01-23 17:06:03
    基本概念: ...我们都知道任何一个独立运行的程序都需要系统分配单独的内存空间,大多数情况下这个工作是由系统完成,方便程序访问变量,程序不需要关心变量的物理地址。因此现代操作系统都提供了一种内存管
  • 虚拟地址 、物理地址 、线性地址 、逻辑地址
  • Linux中逻辑地址等于线性...因为Linux所有的段(用户代码段、用户数据段、内核代码段、内核数据段)的线性地址都是从 0x00000000 开始,长度4G,这样 线性地址=逻辑地址+ 0x00000000,也就是说逻辑地址等于线性地址了。
  • 内存地址(逻辑地址、线性地址、物理地址)概念(转) 逻辑地址(Logical Address)  是指由程序产生的与段相关的偏移地址部分。例如,你在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个...
  • 在8086 的实模式下,把某一段寄存器(段基址)左移4 位,然后与地址ADDR 相加后被直接送到内存总线上,这个相加后的地址(20位)就是内存单元的物理地址,而程序中的这个地址ADDR就叫逻辑地址(或叫虚地址)。...
  • 逻辑地址是CPU在执行过程中生成的地址,而物理地址是指存储单元(已加载到内存中的单元)中的位置。请注意,用户仅处理逻辑地址(虚拟地址)。逻辑地址尤其由MMU或地址转换单元进行转换。该过程的输出是适当的物理...
  • 《Linux内核完全剖析—基于0.12内核》第5章Linux内核体系结构,本章...本节为大家介绍的是虚拟地址、线性地址和物理地址之间的关系。 5.3.6 虚拟地址、线性地址和物理地址之间的关系 前面我们根
  • 发现有人说vm虚拟机中桥接模式的“”复制物理网络连接状态“是否勾选会影响虚拟机是否可以上网。但在尝试选择勾选后并没有解决问题,最后我自己发现是其它问题导致我的虚拟机无法上网,这里暂且不说。 等我的虚拟机...
  • 如果在笔记本电脑或其他移动设备上使用虚拟机,请选择复制物理网络连接状态。 当您在有线或无线网络之间进行移动时,该设置会导致 IP 地址续订。 解释: 复制网络连接状态选项仅用于笔记本电脑在有线网络与无线...
  • 复制物理网络连接状态是在桥接模式下方有一个选项,选择这个。 解释: VM上的网卡是虚拟的,也就是可以说是假的,而选择了这个选项后,就是把你电脑的真实网卡信息传输过去,虚拟网卡,就好比自己电脑的真实网卡。 ...
  • 内存管理中的虚拟地址到物理地址翻译  在虚存映射中,建立了文件到虚存区的映射,那么我们知道数据都是要放到物里内存中才能执行的,为了完成从虚拟内存到物理内存的映射,引入了请页机制。同虚拟存储器...
  • 一、概念 物理地址(physical address)...——这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编
  • 在网上找了很多的讲解桥接模式下“复制物理连接状态”选项是什么意思,我都没有找到一个正确的答案,这个我要详细说一下,这个选项具体的作用。作用:如果选择了复制物理连接状态选项,当您在有线或无线网络之间进行...
  • 总结: 如果要虚拟机上网,勾不勾该选项,没什么区别。 如果不勾的话,无线和有线切换,很有可能IP地址发生变化,需要重新查看。
  • 段管理机制实现虚拟地址(由段和偏移构成的逻辑地址)到线性地址的转换,分页管理机制实现线性地址到物理地址的转换。如果不启用分页管理机制,那么线性地址就是物理地址。本文将介绍80386的存储器分页管理机制和线性...
  • 原地址、目的地址、拷贝长度均有:0x71234567,0x345343,200,但是就是拷贝出现异常,找了好久,由于0x72333333是随意使用的一个地址,而它是虚拟地址,所以导致从虚拟地址中拷贝数据到实际分配的物理地址中出现异常,...
  • Linux用户空间访问物理地址

    万次阅读 2014-03-03 13:37:55
    因为项目需要,需要在Linux userspace 读写访问实际物理地址。   一)用户空间可以直接通过打开 /dev/mem 设备文件,然后mmap() 影射进行访问   static int read_type() { void * map_base; FILE *f; int type,...
  • 复制物理网卡连接状态,就是说把你指定的、本机的、真是网卡的状态信息复制给虚拟机的虚拟网卡,比如说你的本机真是网卡链接到了家用路由器的LAN口上,获得到了DHCP分配的地址,那么你的虚拟网卡就好像和真是网卡接...
  • 这几个地址之间的转换
  • 1.查看mysql 数据存储物理地址。在mysql命令行下输入: show global variables like "%datadir%";结果如下: 2.更改mysql物理位置。 具体操作:  一、停止MySQL  开始-cmd  net stop mysql5  二、复制原来...
  • GetAdaptersAddresses是获取物理网卡的MAC,这里我们通过HOOK,随机的改变它,效果如下图,获取本节MAC可以采用精易模块的MAC_取本机MAC (1) GetAdaptersAddresses .版本 2 .DLL命令 GetAdaptersAddresses, ...
  • 与虚拟地址空间和虚拟地址相对应的则是物理地址空间和物理地址,大多数时候我们的系统所具备的物理地址空间只是虚拟地址空间的一个子集。这里举一个最简单的例子直观地说明这两者,对于一台内存为256M的32bit x86...
  • 基本的思路:linux下的/proc/self是对自身进程映射的文件夹,里面的pagemap允许查看到每个虚拟页映射到的物理页。#include #include #include intptr_t Mytop(uintptr_t vaddr) { FILE *pagemap; intptr_t ...
  • 用RMAN复制 搭建 物理 Data Gurad 环境

    万次阅读 2010-07-23 01:21:00
    <br />Data Guard 环境: 操作系统: redhat 4.7  Primary数据库: IP地址:10.85.10.1 数据库SID:orcl DB_UNIQUE_NAME:orcl_pd   Standby数据库: IP地址:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 155,575
精华内容 62,230
关键字:

复制物理地址