Electronic Joint Business

Solution for E-Business

逻辑地址,线性地址及物理地址的区别

逻辑地址转线性地址 机器语言指令中出现的内存地址,都是逻辑地址,需要转换成线性地址,再经过 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主要以分页的方式实现内存管理。 二、线性地址转物理地址 前面说了Linux中逻辑地址等于线性地址,那么线性地址怎么对应到物理地址呢?这个大家都知道,那就是通过分页机制,具体的说,就是通过页表查找来对应物理地址。 准确的说分页是CPU提供的一种机制,Linux只是根据这种机制的规则,利用它实现了内存管理。 在保护模式下,控制寄存器CR0的最高位PG位控制着分页管理机制是否生效,如果PG=1,分页机制生效,需通过页表查找才能把线性地址转换物理地址。如果PG=0,则分页机制无效,线性地址就直接做为物理地址。 分页的基本原理是把内存划分成大小固定的若干单元,每个单元称为一页(page),每页包含4k字节的地址空间(为简化分析,我们不考虑扩展分页的情况)。这样每一页的起始地址都是4k字节对齐的。为了能转换成物理地址,我们需要给CPU提供当前任务的线性地址转物理地址的查找表,即页表(page table)。注意,为了实现每个任务的平坦的虚拟内存,每个任务都有自己的页目录表和页表。 为了节约页表占用的内存空间,x86将线性地址通过页目录表和页表两级查找转换成物理地址。 32位的线性地址被分成3个部分: 最高10位Directory页目录表偏移量,中间10位Table是页表偏移量,最低12位Offset是物理页内的字节偏移量。 页目录表的大小为4k(刚好是一个页的大小),包含1024项,每个项4字节(32位),项目里存储的内容就是页表的物理地址。如果页目录表中的页表尚未分配,则物理地址填0。 页表的大小也是4k,同样包含1024项,每个项4字节,内容为最终物理页的物理内存起始地址。 […]

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.