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

    一、概念
    物理地址(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.  
    4. #define GDT_ENTRY_DEFAULT_USER_DS        15
    5. #define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)
    6.  
    7. #define GDT_ENTRY_KERNEL_BASE        12
    8.  
    9. #define GDT_ENTRY_KERNEL_CS                (GDT_ENTRY_KERNEL_BASE + 0)
    10. #define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
    11.  
    12. #define GDT_ENTRY_KERNEL_DS                (GDT_ENTRY_KERNEL_BASE + 1)
    13. #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,就是最终的物理地址了。

     

    ------------------------------------------------------分割线-------------------------------------------------------------------------

    2019-2-23更新

     

    数据总线

    数据总线是计算机中各组成部件之间进行数据传输时的公共通道。“内数据总线宽度”是指CPU芯片内部数据传送的宽度;“外数据总线宽度”是指CPU与外部数据交换时的数据宽度。

    地址总线

    地址总线是載对存储器或I/O端口进行访问时,传送由CPU提供的要访问的存储单元或I/O端口的地址信息的总线,其宽度决定了处理器能直接访问的主存容量的大小。

    现在的微型计算机系统采用下图的三级存储器组织结构,即缓冲存储器Cache、主存、和外存。高速缓冲存储器Cache的使用,大大减少了CPU读取指令和操作数所需的时间,使CPU的执行速度显著提高。

    在硬件工程师和普通用户看来,内存就是固化在主板上的内存条,但在应用程序员眼中,并不关心主板上内存条的容量,而是他们可以使用的内存空间,而对于OS开发者来说,则是介于二者之间,既需要知道物理内存的细节,也要为应用程序员提供一个内存空间。逻辑地址、线性地址和物理地址是计算机原理中很重要的三个概念。这三者密切联系又有本质不同。从逻辑地址到物理地址经过了这样的变换:逻辑地址->分段->线性地址->分页->物理地址。

    逻辑地址

    逻辑地址(Logic Address)是指由程序产生的与段相关的偏移地址部分。比如,在C程序中,可以使用&操作读取指针变量本身的值,实际上这个值就是逻辑地址。它是相对于你当前进程数据段的地址,和绝对物理地址不相干。只有在intel实模式下,逻辑地址才等同于物理地址。应用程序员仅需要和逻辑地址打交道,分段和分页机制对他们来说是透明的,仅由系统编程人员涉及。编译好的程序的入口地址可以看作是首地址,逻辑地址通常可以认为是编译器为我们分配好的相对于这个首地址的偏移。一个逻辑地址由段标识符和段内偏移量组成。总之,逻辑地址是相对于应用程序而言的。 逻辑地址有时也称虚拟地址。

    线性地址

    线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址能再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。Intel 80386的线性地址空间容量为4G(2的32次方即32根地址总线寻址)。 需要注意的是,线性地址是抽象的,对于同一平台是大小固定的,比如32位地址总线,线性地址空间为4GB,对于每个进程都是这样。也就是说线性地址空间不是进程共享的,而是进程隔离的,每个进程都有相同大小的线性空间,一个进程对某一线性地址的访问与其他进程对同一线性地址的访问不冲突,得到的值也不尽相同。对于CPU来说,某一时刻只有一个进程在运行,这个进程就认为自己独占4GB的线性空间。当进程切换的时候,线性空间也随之切换。尽管线性空间的大小和内存大小之间没有关系,但是任何线性地址最后还是要转换为物理地址才能被CPU使用去访问物理内存。基于分页机制,4GB的线性地址一部分被映射到物理内存,一部分被映射到磁盘上的交换文件,一部分什么也没有映射。 逻辑地址到线性地址的转换如下图:

    物理地址

    物理地址(Physical Address)是指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了。

    分段机制

    分段提供了隔绝各个代码段/数据段和堆栈段的机制,因此多个任务可以运行在同一处理器上而不互相干扰。从逻辑地址(虚拟地址)到线性地址经过了分段处理。分断机制来源于早期的intel 8086处理器,早期的处理器工艺无法在一个处理器上面封装40引脚,8086处理器地址总线宽度为20位,而寄存器的宽度只有16位。20位可寻址空间为1MB,而16位的寄存器无法一次读取20位的地址,这就产生了矛盾。为了解决这一矛盾,使CPU能够寻址1MB空间,这就引入了分段机制。将段寄存器的地址左移4位,加上16位的偏移就形成了一个20位的地址。 在Linux中没有分段管理,逻辑地址就是线性地址。

    分页机制

    准确的说分页是CPU提供的一种机制,Linux只是根据这种机制的规则,利用它实现了内存管理。分页的基本原理是把内存划分成大小固定的若干单元,每个单元称为一页(page),每页包含4k字节的地址空间(为简化分析,我们不考虑扩展分页的情况)。这样每一页的起始地址都是4k字节对齐的。为了能转换成物理地址,我们需要给CPU提供当前任务的线性地址转物理地址的查找表,即页表(page table)。注意,为了实现每个任务的平坦的虚拟内存,每个任务都有自己的页目录表和页表

    为了节约页表占用的内存空间,x86将线性地址通过页目录表和页表两级查找转换成物理地址。

    32位的线性地址被分成3个部分:

    最高10位 Directory 页目录表偏移量,中间10位 Table是页表偏移量,最低12位Offset是物理页内的字节偏移量

    页目录表的大小为4k(刚好是一个页的大小),包含1024项,每个项4字节(32位)叫做页目录项,页目录项里存储的内容就是页表的物理地址。如果页目录表中的页表尚未分配,则物理地址填0。

    页表的大小也是4k,同样包含1024项,每个项4字节叫做页表项,页表项的内容为最终物理页的物理内存起始地址。

    最低的12位表示物理页内的偏移。

    对于一个要转换成物理地址的虚拟地址,CPU首先根据CR3中的值,找到页目录所在的物理页。然后根据虚拟地址的第22位到第31位这10位(最高的10bit)的值作为索引,找到相应的页目录项(PDE,page directory entry),页目录项中有这个虚拟地址所对应页表的物理地址。有了页表的物理地址,根据虚拟地址的第12位到第21位这10位的值作为索引,找到该页表中相应的页表项(PTE,page table entry),页表项中就有这个虚拟地址所对应物理页的物理地址。最后用虚拟地址的最低12位,也就是页内偏移,加上这个物理页的物理地址,就得到了该虚拟地址所对应的物理地址。

    下面是一个例子:

    参考资料

     

    展开全文
  • 逻辑地址,线性地址和物理地址转换

    千次阅读 2019-08-02 18:50:14
    一、逻辑地址转线性地址 机器语言指令中出现的内存地址,都是逻辑地址,需要转换成线性地址,再经过MMU(CPU中的内存管理单元)转换成物理地址才能够被访问到 我们写个最简单的hello world程序,用gcc编译,再反汇编...

    一、逻辑地址转线性地址

    机器语言指令中出现的内存地址,都是逻辑地址,需要转换成线性地址,再经过MMU(CPU中的内存管理单元)转换成物理地址才能够被访问到

    我们写个最简单的hello world程序,用gcc编译,再反汇编后会看到以下指令:

    mov    0x80495b0, %eax

    这里的内存地址0x80495b0 就是一个逻辑地址,必须加上隐含的DS 数据段的基地址,才能构成线性地址。也就是说 0x80495b0 是当前任务的DS数据段内的偏移。

    在x86保护模式下,段的信息(段基线性地址、长度、权限等)即段描述符占8个字节,段信息无法直接存放在段寄存器中(段寄存器只有2字节)。Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值(index)。

    Linux中逻辑地址等于线性地址。为什么这么说呢?因为Linux所有的段(用户代码段、用户数据段、内核代码段、内核数据段)的线性地址都是从 0x00000000 开始,长度4G,这样 线性地址=逻辑地址+ 0x00000000,也就是说逻辑地址等于线性地址了。

    这样的情况下Linux只用到了GDT,不论是用户任务还是内核任务,都没有用到LDT。GDT的第12和13项段描述符是 __KERNEL_CS 和__KERNEL_DS,第14和15项段描述符是 __USER_CS 和__USER_DS。内核任务使用__KERNEL_CS 和__KERNEL_DS,所有的用户任务共用__USER_CS 和__USER_DS,也就是说不需要给每个任务再单独分配段描述符。内核段描述符和用户段描述符虽然起始线性地址和长度都一样,但DPL(描述符特权级)是不一样的。__KERNEL_CS 和__KERNEL_DS 的DPL值为0(最高特权),__USER_CS 和__USER_DS的DPL值为3。

    用gdb调试程序的时候,用info reg 显示当前寄存器的值:

    cs             0x73     115

    ss             0x7b     123

    ds             0x7b     123

    es             0x7b     123

    可以看到ds值为0x7b, 转换成二进制为 00000000 01111011,TI字段值为0,表示使用GDT,GDT索引值为 01111,即十进制15,对应的就是GDT内的__USER_DS用户数据段描述符。

    从上面可以看到,Linux在x86的分段机制上运行,却通过一个巧妙的方式绕开了分段(即逻辑地址=线性地址)。Linux主要以分页的方式实现内存管理

    1.--CPU的段寄存器:

      在CPU中,跟段有关的CPU寄存器一共有6个:cs,ss,ds,es,fs,gs,它们保存的是段选择符(或者叫段描述符)。而同时这六个寄存器每个都有一个对应的非编程寄存器,它们对应的非编程寄存器中保存的是段描述符。系统可以把同一个寄存器用于不同的目的,方法是先将其寄存器中的值保存到内存中,之后恢复。而在系统中最主要的是cs,ds,ss这三个寄存器。

    • CS 代码段寄存器:指向包含程序指令的段,在CS寄存器中RPL用于表示当前CPU的特权级(CPL),CPL为0是最高权限(内核态使用),CPL为3是用户态使用。

    • SS栈段寄存器:指向当前程序的栈的段。

    • DS 数据段寄存器:指向保存着静态数据和全局数据的段(静态区)。

    2.--段描述符

      段描述符就是保存在全局描述符表或者局部描述符表中,当某个段寄存器试图通过自己的段选择符获取对于的段描述符时,会将获取到的段描述符放到自己的非编程寄存器中,这样就不用每次访问段都要跑到内存中的段描述符表中获取。

     

    • BASE(32位):段首地址的线性地址。

    • G:为0代表此段长度以字节为单位,为1代表此段长度以4K为单位。

    • LIMIT(20位):此最后一个地址的偏移量,也相当于长度,G=0,段大小在1~1MB,G=1,段大小为4KB~4GB。

    • S:为0表示是系统段,否则为代码段或数据段。

    • Type:描述段的类型和存取权限。

    • DPL:描述符特权级,表示访问这个段CPU要求的最小优先级(保存在cs寄存器的CPL特权级),当DPL为0时,只有CPL为0才能访问,DPL为3时,CPL为0为3都可以访问这个段。

    • P:表示此段是否被交换到磁盘,总是置为1,因为linux不会把一个段都交换到磁盘中。

    • D或B:如果段的LIMIT是32位长,则置1,如果是16位长,置0。(详见intel手册)

    • AVL:忽略。

    2.1--数据段描述符:

      表示这个段描述符代表一个数据段,这种描述符可以放在GDT或者LDT。该描述符的S标志位为1,也就是非系统段。需要注意内核数据段属于数据段描述符,并不属于系统段描述符。

    2.2--代码段描述符:

      表示这个段描述符代表一个数据段,这种描述符可以放在GDT或者LDT。该描述符的S标志位为1,也就是非系统段。需要注意内核代码段属于代码段描述符,并不属于系统段描述符

    3.--全局描述符表与局部描述符表

      全局描述符表和局部描述符表保存的都是段描述符,记住要把段描述符和段选择符区别开来,保存在寄存器中的是段选择符,这个段选择符会到描述符表中获取对于的段描述符,然后将段描述符保存到对应寄存器的非编程寄存器中。

      系统中每个CPU有属于自己的一个全局描述符表(GDT),其所在内存的基地址和其大小一起保存在CPU的gdtr寄存器中。其大小为64K,一共可保存8192个段描述符,不过第一个一般都会置空,也就是能保存8191个段描述符。第一个置空的原因是防止加电后段寄存器未经初始化就进入保护模式而使用GDT。

      而对于局部描述符表,CPU设定是每个进程可以创建属于自己的局部描述符表(LDT),当前被使用的LDT的基地址和大小一起保存在ldtr寄存器中。不过大多数用户态的liunx程序都不使用局部描述符表,所以linux内核只定义了一个缺省的LDT供大多数进程共享。描述这个局部描述符表的局部描述符表描述符保存在GDT中

    4.--分段机制将逻辑地址转化为线性地址的步骤:

    1)使用段选择符中的偏移值(段索引)在GDT或LDT表中定位相应的段描述符.(仅当一个新的段选择符加载到段寄存器中是才需要这一步)

    2)利用段选择符检验段的访问权限和范围,以确保该段可访问。

    3)把段描述符中取到的段基地址加到偏移量(也就是上述汇编语言汇中直接出现的操作地址)上,最后形成一个线性地址。

    二, 线性地址转物理地址

    逻辑地址:是相对于段而言的,需要段描述符和段内偏移来组成。所有段都从0x00000000开始,只需关注段内偏移即可。而段内偏移的值恰好等于线性地址的值。
           线性地址:是进程使用的地址,虚拟的地址。人为抽象出一大片地址空间给进程使用,为了方便32位地址总线存取,linux内核定义为了4G。
           物理地址:是采用32位总线存取物理内存某个字节时,地址总线上电位的高低。

           分段单元将逻辑地址转换成线性地址,分页单元将线性地址转换成物理地址。此处分析后者

    CPU通过地址来访问内存中的单元,地址有虚拟地址和物理地址之分,如果CPU没有MMU(Memory Management Unit,内存管理单元),或者有MMU但没有启用,CPU核在取指令或访问内存时发出的地址将直接传到CPU芯片的外部地址引脚上,直接被内存芯片(以下称为物理内存,以便与虚拟内存区分)接收,这称为物理地址(Physical Address,以下简称PA),如下图所示。

    如果CPU启用了MMU,CPU核发出的地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address,以下简称VA),而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址,如下图所示

     

    虚拟内存地址和物理内存地址的分离,给进程带来便利性和安全性。虚拟地址必须和物理地址建立一一对应的关系,才可以正确的进行地址转换。

    记录对应关系最简单的办法,就是把对应关系记录在一张表中。为了让翻译速度足够地快,这个表必须加载在内存中。不过,这种记录方式惊人地浪费。

    因此,Linux采用了分页(paging)的方式来记录对应关系。所谓的分页,就是以更大尺寸的单位页(page)来管理内存。在Linux中,通常每页大小为4KB。如果想要获取当前树莓派的内存页大小,可以使用命令:

    baiheng@baiheng-OptiPlex-5055-Ryzen-CPU:~$ getconf PAGE_SIZE
    4096
    地址转换过程
    32bit 分页机制下虚拟地址是由32bit组成的,常规4KB分页,32位的虚拟地址被分成3个域。


    具体的地址转换过程,文字描述太累,看图直观一些:

    依据以下步骤进行转换:

    1. 从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);
    2. 根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。(又引入了一个数组),页的地址被放到页表中去了。
    3. 根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;
    4. 将页的起始地址与线性地址中最后12位相加,得到最终我们想要的葫芦;

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

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

    整个转换依据硬件转换原理,只是多了二次数组的索引罢了,如下图:

    Snap2.jpg

    展开全文
  •  在逻辑地址、线性地址和物理地址一节中,已经对逻辑地址、线性地址和物理地址的概念做了详细的讲解。现在在这篇文章中,我们可以详细的对段式、页式、段页式内存管理方式以及三种地址之间的转化做一个详细且深入的...

           首先说明一点,本篇的文章是根据自己的理解总结,但是图可能是在已有的博客中截图的,在此对那些对我理解该部分知识提供帮助的博客博主表示感谢!

           在逻辑地址、线性地址和物理地址一节中,已经对逻辑地址、线性地址和物理地址的概念做了详细的讲解。现在在这篇文章中,我们可以详细的对段式、页式、段页式内存管理方式以及三种地址之间的转化做一个详细且深入的说明。

           文章按照由简单到深入的顺序分别对以上三种内存管理方式进行说明。这里以32位系统为例进行举例说明,至于64位系统,其原理是大致类似的。

    段式内存管理方式:

           段式内存管理方式就是直接将逻辑地址转换成物理地址,也就是CPU不支持分页机制。其地址的基本组成方式是段号+段内偏移地址。

           在介绍段式内存管理方式之前首先介绍逻辑空间。逻辑空间分为若干个段,其中每一个段都定义了一组具有完整意义的信息,逻辑地址对应于逻辑空间,如(主程序的main())函数,如图1所示。

    图 1 逻辑地址空间

           段是对程序逻辑意义上的一种划分,一组完整逻辑意义的程序被划分成一段,所以段的长度是不确定的。

           如图1所示,段式内存管理方式经过段表映射到内存空间。先说明一下段表的概念,可以将段表抽象成一个大的数组集合,数组中的元素是什么呢?就是“段描述符”----用于描述一个段的详细信息的结构。段描述符一般是由8个字节组成,也就是64位。操作系统使用的不同的段描述符如图2所示。


    图 2 段描述符

           前面已经说过,将逻辑地址转换成下一个环节的地址(物理地址,不适用分页或者使用分页的线性地址)需要使用段表,而获得段表中一个特定的段描述符需要使用段选择符,这里需要区分的概念就是“段选择符"和“段描述符"。段描述符描述了一个段的详细信息,例如,起始地址(BASE的32位,长度20位),适用于转换下一个环节的地址所需要的详细信息;而段选择符是用于找到对应的段描述符的。

           下面说一下段选择符。段选择符是一个由16位长的字段组成的,其中前13位是一个索引号,后面三位包含一些硬件细节,如图3所示。

            
    图 3 段选择符结构图

           根据段选择符可以获取段描述符,虽然段描述符比较复杂,但是对于寻址而言,我们只关注Base的32位,它 描述了一个段的开始位置的线性地址。Intel设计的本意是,一些全局的段描述符,就放在"全局段描述符(GDT)"中,一些局部的,例如每个进程自己的,就放在所谓的"局部段描述符表(LDT)中"。那么,究竟什么时候该用GDT,什么时候该用LDT呢?这是由段选择描述符中的T1字段表示的,=0标识使用GDT,=1表示使用LDT。GDT在内存中的大小和地址存放在CPU的gdtr寄存器中,而LDT则在ldtr寄存器中。如下图所示。


    图 4 逻辑地址转换过程

          首先给定一个完整的逻辑地址[段选择符:段内偏移地址],

          1.看段选择描述符中的T1字段是0还是1,可以知道当前要转换的是GDT中的段,还是LDT中的段,再根据指定的相应的寄存器,得到其地址和大小,我们就有了一个数组了。

          2.拿出段选择符中的前13位,可以在这个数组中查找到对应的段描述符,这样就有了Base,即基地址就知道了。

          3.把基地址Base+Offset,就是要转换的下一个阶段的物理地址。

    页式内存管理方式:

            CPU的页式内存管理单元负责把一个线性地址转换为物理地址。从管理和效率的角度出发,线性地址被划分成固定长度单位的数组,称为页(page)。例如,一个32位的机器,线性地址可以达到4G,用4KB为一个页来划分,这样,整个线性地址就被划分为一个2^20次方的的大数组,共有2的20次方个页,也就是1M个页,我们称之为页表,改页表中每一项存储的都是物理页的基地址。

           这里不得不说的是另一个“页”,我们称之为物理页,或者页框、页桢。是分页单元将所有的物理内存都划分成了固定大小的单元为管理单位,其大小一般与内存页大小一致。

           如果内存页按照这种方式进行管理,管理内存页需要2^20次方的数组,其中每个数组都是32bit,也就是4B(其中前20位存储物理内存页的基地址,后面的12位留空,用于与给定的线性地址的后12位拼接起来一起组成一个真实的物理地址,寻找数据的所在。这样就需要为每个进程维护4B*2^20=4MB的内存空间,极大地消耗了内存。

           为了能够尽可能的节约内存,CPU在页式内存管理方式中引入了两级的页表结构,如图5所示。


    图 5 二级页表结构

           如图5所示,这种页式管理方式中,第一级的页表称之为“页目录”,用于存放页表的基地址;第二级才是真正的“页表”用于存放物理内存中页框的基地址。

           1、二级页目录的页式内存管理方式中,第一级的页目录的基址存放在CPU寄存器CR3中,这也是转换的开始点;

           2、每一个活动的进程,都有其对应的独立虚拟内存(页目录也是唯一的),那么它对应一个独立的页目录地址。--运行一个进程,需要将它的页目录地址放到CR3寄存器中,将别的页目录的基址暂时换到内存中;

           3、每个32位的线性地址被划分成三部分,页目录索引(10位),页表索引(10位),偏移量(12位)。

    线性地址转换成物理地址的过程如下:

           1、从CR3中取出进程的页目录的地址(操作系统在负责进程的调度的时候,将这个地址装入对应的CR3地址寄存器),取出其前20位,这是页目录的基地址;

           2、根据取出来的页目录的基地址以及线性地址的前十位,进行组合得到线性地址的前十位的索引对应的项在页目录中地址,根据该地址可以取到该地址上的值,该值就是二级页表项的基址;当然你说地址是32位,这里只有30位,其实当取出线性地址的前十位之后还会该该前十位左移2位,也就是乘以4,一共32位;之所以这么做是因为每个地址都是4B的大小,因此其地址肯定是4字节对齐的,因此左移两位之后的32位的值恰好就是该前十位的索引项的所对应值的起始地址,只要从该地址开始向后读四个字节就得到了该十位数字对应的页目录中的项的地址,取该地址的值就是对应的页表项的基址;

           3、根据第二步取到的页表项的基址,取其前20位,将线性地址的10-19位左移2位(原因和第2步相同),按照和第2步相同的方式进行组合就可以得到线性地址对应的物理页框在内存中的地址在二级页表中的地址的起始地址,根据该地址向后读四个字节就得到了线性地址对应的物理页框在内存中的地址在二级页表中的地址,然后取该地址上的值就得到线性地址对应的物理页框在内存中的基地址;(这一步的地址比较绕,还请仔细琢磨,反复推敲)

           4、根据第3步取到的基地址,取其前20位得到物理页框在内存中的基址,再根据线性地址最后的12位的偏移量得到具体的物理地址,取该地址上的值就是最后要得到值;

           其实,对比一级页表机制和二级页表机制可以发现,二级页表机制中同样需要1024个二级页表,每个页表都有1024项,每项的大小都是4B,因此一个二级页表需要4KB,1024个二级页表需要4MB的空间,再加上页目录,好像比只有一级页表机制占用了更多的内存,而且寻址方式变得更复杂了,似乎是在自己给自己找麻烦。从CPU的开销中可以看到,如果是一级页表机制,那么CPU取到一个数需要访问内存两次,而使用二级页表机制之后,CPU想要取得一个操作数,需要访问内存三次。其实,使用二级页表机制还是有很多优点的,如下:

          1、二级表结构也许页表分散在内存的各个页面中,而不需要保存在连续的4M的内存块中;

          2、不需要位不存在的线性地址空间分配二级页表,虽然目录表页面总是必须存在于物理内存中,但是二级页表可以在需要的时候再分配,这使得页表结构的大小对应于实际使用的线性地址空间的大小;

          3、页目录和页表中的每个表项都有一个存在属性,页目录中的存在属性指明对应的页表结构是否存在。如果页目录指明对应的二级页表存在,那么通过访问二级表,表查找过程就像上面的查找过程一样进行下去;如果存在标志表明对应的二级表项不存在,那么处理器就会产生一个缺页异常来通知操作系统。页目录表项的存在属性使得操作系统可以根据实际使用的线性地址范围来分配二级页表页面;当然,页目录表项中的存在位还可以在虚拟内存中存放二级页表,这意味着在任何的时候只有部分二级页表需要存放在物理内存中,其余部分可以保存在磁盘中。处于物理内存中的页表对应的页目录项可以被标注为存在,以表明可用它们进行分页转换。处于磁盘上的页表对应的页目录项被标注为不存在。由于二级页表不存在而引发的异常会通知操作系统将缺少的页表从磁盘上加载进物理内存。将页表存储在虚拟内存中减少了保存页表所需要的物理内存;

    段页式内存管理方式:

           上面已经对段式内存管理方式和页式内存管理方式做了详细的说明和介绍,段页式内存管理方式就是结合段式内存管理方式和页式内存管理方式,将逻辑地址先转换为线性地址,再将线性地址转换为物理内存中的地址。

           下面结合linux系统的具体寻址方式对段页式内存管理做一个介绍。

            linux下的逻辑地址与线性地址是一致的,之所以说一致而不是说完全相同,是因为linux使用巧妙的线性地址的基地址“欺骗了”CPU,导致转换之后的线性地址和逻辑地址在数值上是一致的,但是逻辑地址转换成线性地址的过程中CPU使用了分段机制中的某些功能。

            按照Intel的本意,全局的使用GDT,每个进程有自己的LDT---不过linux则对所有的进程都使用了相同的段来对指令和数据进行寻址。即用户数据段、用户代码段(用户态使用);对应的还有内核数据段和内核代码段(对应内核态)。

            为了理解linux下逻辑地址和线性地址之间的转换,需要知道几个寄存器:

            1、8个通用寄存器,名字不重要,记住这8个寄存器是32位的;

            2、4个段寄存器,记住这四个家伙是16位的,在32位的CPU体系结构中,这几个寄存器根本存放不下段的地址,所以这四个寄存器中存放了一个叫做段选择符的东西,当然,这个段选择符就是16位的,后面会提到这个段选择符,这四个段寄存器分别是CS、DS、SS、ES。分别存放着代码段、数据段、堆栈段、还有其他段的段选择符;

            3、4个控制寄存器,都是32位的,这四兄弟的名字很好记,分别是CR0、CR1、CR2和CR3。其中,CR1是个无用的家伙,它里面全是0。其他三个控制寄存器,比较重要的是CR0和CR3。

           当CPU处于用户态的时候,使CS和DS存放的是用户代码段和用户数据段的段选择符;当用户处于内核态的时候,CS和DS存放的是内核代码段和内核数据段的段选择符。因此linux下的段选择符根据CPU所处的状态可以确定。之所以这么说是因为在linux系统的include/asm-is86/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 GDT_ENTRY_KERNEL_BASE 12
    #define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)
    #define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
    #define GDT_ENTRY_KERNEL_CS (GDT_ENTRY_KERNEL_BASE + 0)
    /*将上述的段选择符展开成二进制如下:*/
    #define GDT_ENTRY_KERNEL_DS (GDT_ENTRY_KERNEL_BASE + 1)
    #define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8)
    #define __KERNEL_CS 96 [00000000 0 1100 0 00]
    #define __USER_CS 115 [00000000 0 1110 0 11]
    #define __USER_DS 123 [00000000 0 1111 0 11]
    #define __KERNEL_DS 104 [00000000 0 1101 0 00]

            之所以定义_USER_CS、_USER_DS、_KERNEL_CS和_KERNEL_DS分别为上述GDT_ENTRY_DEFAULT_USER_CS_、GDT_ENTRY_DEFAULT_USER_DS、GDT_ENTRY_KENEL_CS以及GDT_ENTRY_KERNEL_DS的8倍,这是因为段选择额符的最低三位为标志位,并不是段选择符,所以需要将12、13、14、15等右移三位,当然也就是乘以8;但是用户状态的+3,而内核空间的加0,这是因为该两位对应的是标志位RPL的数值,RPL=3表示这是用户态,具有最低的权限,为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 */

            根据段描述符的结构可知,上述四项的BASE字段的值全为0x00000000,这样基址就是0x00000000。所以,linux下给出的逻辑地址(32位)其实是偏移量,根据逻辑地址转线性地址的方式,可知转换之后的逻辑地址和线性地址的数值是一致的,但是二者并不是概念上的一直,之所以这么说是因为逻辑地址转线性地址的时候,需要查看段选择符的权限位,这是中间的一个必经过程。

            那么逻辑地址转换为线性地址的过程可以描述如下:

            1、CPU根据进程所处的状态将上述的选择描述符装入到那四个16位段寄存器中的一个;

            2、然后将逻辑地址的偏移量装入到8个32位的通用寄存器中的某一个中;

            3、然后MMU中的分段部件开始对逻辑地址进行处理;

            4、首先根据选择描述符中的前13位得到段描述符的索引,根据这个索引可以得到对应的段描述符;

            5、找到段描述符之后,这几乎等于是找到了所有相应段的信息,当然我们可以提取出段的基地址,有了这个基地址,再加上逻辑地址的偏移量就得到了新的地址,这就是线性地址;

           至此,逻辑地址转换成线性地址的步骤完毕。到目前,我们已经有了线性地址,那么根据线性地址得到物理地址的步骤,就和上述的线性地址转换成物理地址的步骤是一致的,此处不再赘述。用一张图概括逻辑地址转换成物理地址的过程如图6。




    图 6 逻辑地址转换成物理地址的过程
    展开全文
  • 线性地址转换物理地址

    千次阅读 2019-05-16 09:36:16
    当开启页映射时,我们所有的线性地址都是虚拟地址,只是给我们看的,但是真正的物理地址需要经过线性地址一系列的映射才会到真正的物理地址处。 一般是通过三级或者四级映射。 也就是10-10-12或者2-9-9-12。 今天...

    PDE 页目录表

    P(Present)位 , 置为1表示该页是否有效,否则表示该页无效
    R/W位:置为1,表示该页可读可写,否则为0表示只可读。
    U/S位: 置为1,表示该页0,1,2,3环的程序都可以访问该页,置为0表示该页只能0环程序访问。
    A位:表示该页是否被访问过
    PS位:Page Size,为1表示一页4MB大小,此时是一个大页面。为0表示该页大小是4KB
    G位:指示全局页Global
    AVL:保留字段

    PTE 页表结构
    D位:表示该页是否被写入

    当开启页映射时,我们所有的线性地址都是虚拟地址,只是给我们看的,但是真正的物理地址需要经过线性地址一系列的映射才会到真正的物理地址处。

    一般是通过三级或者四级映射。
    也就是10-10-12或者2-9-9-12

    今天先实验10-10-12。

    首先我们要知道页表的概念,页表是一个存储索引号的表,每个元素是4B,对于10-10-12而言,第一个10bit用210就可以表示就可以表示全,第二个也是如此,这样一来,一个一级页表是4 * 210B 大小,也就是4KB的大小,一级页表都可以索引一个二级页表,一个二级页表也是4KB大小,二级页表大小是210 * 4KB ,占了4MB。

    通过每一个页表可以确定下一个表的基地址,而线性地址中的每一个部分则是对这个地址所在的表的一个查询工作。

    实战

    下面实战下线性地址转换实验

    #include <stdio.h>
    #include <stdlib.h>
    #include <Windows.h>
    
    //线性地址转换实验
    int main()
    {
    	char *s = (char *)malloc(sizeof(char) * 1024);
    	memset(s, 0, sizeof(char) * 1024);
    	memcpy(s, "This is Conversion of linear address to phythic address!\n", strlen("This is Conversion of linear address to phythic address!\n" + 1));
    	printf("%p", s);
    	_asm{
    		int 3
    	}
    
    	return 0;
    }
    

    键入命令 !process 0 0 可显示所有进程
    在这里插入图片描述

    其实也就是cr3的值
    在这里插入图片描述

    看出它的一级页表的基址是0x40e1f000,首先看下s的地址,是0x00156FB0
    在这里插入图片描述
    在这里插入图片描述
    现在我们就来看看s的线性地址转换到物理地址是多少吧 注意这里是10 - 10 - 12
    先进行分割:0x00156fb0 -> 0x0 | 0x156| 0xfb0
    DirBase = 0x40e1f000
    从一级页表中查找二级页表的索引号(注意: 这里要键入!来显示物理地址!!!):
    在这里插入图片描述
    是0x40fd9867,最后12位是属性,索引号是0x40fd9

    二级页表的地址是0x40fd9000,我们刚才的分割的第二部分0x156就是在二级页表索引的地址

    在这里插入图片描述

    看到普通物理页的号码是0x41042。
    所以我们最终索引到的普通物理页的索引号是0x41042,换算成物理地址就是0x41042000,其物理偏移是我们分隔的第三部分0xfb0
    在这里插入图片描述可以看到我们最终索引到了这个地址,注意这里别==* 4==了,最后的是个偏移,PUCHAR类型。

    线性地址转换物理地址实验成功!!!撒花

    展开全文
  • 逻辑地址(Logical Address) 是指由程序产生的与段相关的偏移地址部分。例如,你在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,...线性地址(Linear Address) 是逻辑地址
  • 逻辑地址、线性地址、物理地址和虚拟地址的分析
  • 逻辑地址、线性地址、物理地址和虚拟地址 逻辑地址、线性地址、物理地址和虚拟地址
  • 这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,...
  • 逻辑地址(Logical Address) 是指由程序产生的与段相关的偏移地址部分;应用程序员仅需与逻辑地址打交道,而分段和分页机制对您来说是完全透明...线性地址(Linear Address) 是逻辑地址到物理地址变换之间的中间层。
  • 段管理机制实现虚拟地址(由段和偏移构成的逻辑地址)到线性地址的转换,分页管理机制实现线性地址到物理地址的转换。如果不启用分页管理机制,那么线性地址就是物理地址。本文将介绍80386的存储器分页管理机制和线性...
  • 这样,当我们给出一个32-bit的线性地址时,首先取出高10-bit作为Page Directory的索引,找到相应的Directory Entry,然后根据此Directory Entry的高20-bit找到相应的Page Table所在的Page,再根据线性地址的中间10-...
  • X86下段地址_偏移地址_虚拟地址_线性地址_物理地址.pptx
  • 通俗理解物理地址、逻辑地址、线性地址、虚拟地址、有效地址的区别  物理地址:物理地址就是内存单元的绝对地址,比如你有一个4G的内存条插在电脑上,物理地址0x0000就表示内存条的第一个存储单元,0x0010就表示...
  • 1.物理地址:是指在 CPU 外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果,是内存储器中的实际有效地址,也叫作绝对地址,从0开始顺序编排,直到其支持的最大存储单元。处理器使用物理地址访问主...
  • 逻辑地址: 包含在机器语言指令中用来指定一个操作数或一条指令的地址,这种寻址方式在80x86著名的分段结构中表现得尤为具体,它促使windows程序员把程序分成若干段。...线性地址/虚拟地址 是一个32位...
  • 逻辑地址、线性地址、物理地址和虚拟地址理解.doc
  • 逻辑地址、线性地址和物理地址的关系

    万次阅读 多人点赞 2016-10-02 10:03:42
    线性地址通常用十六进制数字表示,值得范围从0x00000000到0xfffffff)程序代码会产生逻辑地址,通过逻辑地址变换就可以生成一个线性地址。如果启用了分页机制,那么线性地址可以再经过变换以产生一个物理地址。如果...
  • 本文转自http://blog.csdn.net/erazy0/article/details/6457626一、逻辑地址转线性地址 机器语言指令中出现的内存地址,都是逻辑地址,需要转换成线性地址,再经过MMU(CPU中的内存管理单元)转换成物理地址才能够被...
  • 一、 在保护模式下,有以下三个地址概念: ...在x86环境下,线性地址是从0x00000000到0xFFFFFFFF之间的一个地址,线性地址由段描述符中的段基址+逻辑地址构成,而段描述符由段选择子确定。线性地址到物理地址的...
  • 实模式下的地址变换 16位系统中,数据总线为16位,地址总线为20位,可寻址1M地址空间。由于寄存器只有16位,而地址总线是20位,如果只使用一个寄存器存放地址,则寻址范围小于1M。为了充分利用地址空间,采用:段基址...
  • 在实地址模式(因为实模式没有分段或分页机制,Cpu不进行自动地址转换)下,程序员操作的就是物理地址,所谓的物理地址就是物理内存上的32位地址,即物理地址可以直接定位到物理内存上的位置,无论任何操作,最终都必须要...
  • 逻辑地址、线性地址、物理地址和虚拟地址 本贴涉及的硬件平台是X86,如果是其它平台,嘻嘻,不保证能一一对号入座,但是举一反三,我想是完全可行的。 一、概念 物理地址(physical address) 用于...
  • 逻辑地址(Logical Address) 是指由程式产生的和段相关的偏移地址部分。例如,你在进行C语言指针编程中,能读取指针变量本身值(&操作),实际上这个值就是逻辑地址,他是相对于你当前进程数据段的地址,不和绝对...
  • 《Linux内核完全剖析—基于0.12内核》第5章Linux内核体系结构,本章...本节为大家介绍的是虚拟地址、线性地址和物理地址之间的关系。 5.3.6 虚拟地址、线性地址和物理地址之间的关系 前面我们根
  • 1、 名词解释:逻辑地址、虚拟地址、线性地址、物理地址 2、 80386保护模式下虚拟地址是如何经过分段机制转化为线性地址再经过分页机制转化为物理地址的? 3、 什么是虚拟内存管理?80386为操作系统实现虚拟内存管理...
  • 本博文引自我的知乎回答:Linux 线性地址,逻辑地址和虚拟地址的关系? 为了防止歧义,以下术语都用英文。部分术语不做解释了,不然答案就太长了。 以下讲解都是以代码段为例 在 Intel 平台下,逻辑地址(logical ...
  • P89C669是PHILIPS半导体一款51MX(存储器扩展)内核的微处理器,其指令执行速度2倍于标准的80C51器件,线性地址经扩展后可支持高达8 MB的程序存储器和8 MB的数据存储器,这是他相对于标准51内核的最大优点。...
  • 虚拟地址、线性地址和物理地址的转换
  • P89C669是PHILIPS半导体一款51MX(存储器扩展)内核的微处理器,其指令执行速度2倍于标准的80C51器件,线性地址经扩展后可支持高达8MB的程序存储器和8 MB的数据存储器,这是他相对于标准51内核的最大优点。目前的单片...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 179,396
精华内容 71,758
关键字:

线性地址