Linux下逻辑地址-线性地址-物理地址详解

一、逻辑地址转线性地址
机器语言指令中出现的内存地址,都是逻辑地址,需要转换成线性地址,再经过MMU(CPU中的内存管理单元)转换成物理地址才能够被访问到 。
我们写个最简单的hello world程序,用gccs编译,再反编译后会看到以下指令:
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_DATA 用户数据段描述符 。
从上面可以看到,Linux在x86的分段机制上运行,却通过一个巧妙的方式绕开了分段 。
Linux主要以分页的方式实现内存管理 。

Linux下逻辑地址-线性地址-物理地址详解

文章插图
【Linux下逻辑地址-线性地址-物理地址详解】 
需要C/C++ Linux服务器架构师学习资料私信“资料”(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
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字节,内容为最终物理页的物理内存起始地址 。
每个活动的任务,必须要先分配给它一个页目录表,并把页目录表的物理地址存入cr3寄存器 。页表可以提前分配好,也可以在用到的时候再分配 。


推荐阅读