1 .概念
内存管理模式
段:内存分为多个段,每个段都是连续内存,适用于不使用不同段的用途。 由于每个段的大小不一致,因此存在内存碎片和内存交换效率降低的问题。
页面表达式:内存分为多个内存页面进行管理。 例如,在Linux系统上,每个页面的大小为4KB。 分页后,不会出现细小内存碎片。 但是,还存在存储器碎片化的问题。
分栏:分栏和页面组合。
地址类型分类
逻辑地址:程序使用的地址。 通常是未通过段内存管理进行映射的地址,称为逻辑地址
线性地址:管理段存储器中映射的地址,称为线性地址,也称为虚拟地址
虚拟地址:管理段内存中映射的地址,也称为线性地址,也称为虚拟地址
物理地址:物理内存地址
说明:
在Inetel处理器中,逻辑地址是“段存储器管理”转换前的地址,线性地址是“页面存储器管理”转换前的地址。
段内存管理映射的地址不再是“物理地址”,Intel被称为“线性地址”(也称为虚拟地址)。 因此,段存储器管理器首先将逻辑地址映射到线性地址,然后页存储器管理器将线性地址映射到物理地址。
linux内存主要是基于页面的内存管理,也有基于段的机制。 目前,Linux内核采取的方法是使段映射过程实质上不起作用。
英特尔的第一个处理器80286是纯市场细分管理,同时包含80386市场细分和页面。
2 .页面管理
x86架构32位cpu
2级页面表的选择方法、1个内存页面4KB的大小、1级页面目录表1024个项目、2级页面表1024个项目、1个页面表项目4个字节。 “第1级”页上的所有目录项都将被分配,并且“第2级”页上的表将根据需要创建。 (局部原理)。
虚拟地址32位
10分别索引10 10 12、第1级页表编号、第2级页表条目,记录物理基址的偏移地址。 使用PAE机制后,32位系统支持的最大内存为64GB。 地址为32 4=36位。
线性寻址物理地址步骤
首先,用10位地址指定级别1的页表编号,在级别1的页表编号中记录级别2的页表的地址
找到2级页面表地址后,然后从虚拟地址的另10位开始查找2级页面表条目的位置
在找到二级页面表条目后,该条目记录该虚拟地址映射的物理地址的起始地址,条目大小为4字节32位
从找到物理地址的开头地址起将虚拟地址的低位12位作为偏移量进行结合,计算最终的物理地址
x86体系结构64位cpu
存在更多级别的页面表
全局页面目录条目分页全局目录(PGD )
首页目录项目pud(pageupperdirectory ) )。
中间页面的目录项目PMD(pagemiddledirectory ) )。
页面项目pte (页面宽度) )。
线性寻址物理地址步骤
支持48位线性地址,52位最大物理地址,40位实际物理内存地址总线宽度,即1TB物理内存
x86_64有四个阶段的页面表,原理与x86系统相同,也是一个阶段的寻址
由于CR3寄存器保持最高等级表的开头物理地址,所以地址指定首先取得CR3寄存器的值
PTE表项的大小为8字节,即64位
TLB
CPU芯片具有内置的Cache,用于存储程序最常访问的页面表条目。 此缓存是翻译语言定位缓冲区(TL )。 通常称为页面表缓存、目标旁路缓存、快速表等。 中,CPU的存储器管理单元MMU进行寻址时,首先检查TLB。 如果找不到,就继续查普通的页面表。
专有名词
PDT :页面目录表、多级页面表一级页面表,32位系统有1024页目录
PTT :页表条目表、多级页表子页表,32位系统在每个页目录下有1024个页表条目,每个表条目4字节
PDE :页面表的基址是PDT之一
PTE :是页面的基址,是PTT之一
GDT )用于全局描述符表,逻辑地址到线性地址的转换
LDT )用于本地描述符表,逻辑地址到线性地址的转换
3 .地址分类
32系统
内核1G:0xC0 00 00 01 -0xFF FF FF FF
用户3G:0x00 00 00 00 -0xC0 00 00 00
0xC0 00 00 00==3G
64位系统:
内核128 t :0 xffff 800000000-0 xfffffff (高位) () ) ) ) ) ) ) 65
0 xffff7ffffffffff-0 xffffffffffffffffffffffff (自己计算) ) ) ) ) ) ) ) )。
用户128 t 33600 x 000000000000000-0x 00007 fffffff (低位) () ) ) ) ) ) ) ) ) )。
0x 0008000000000000-0x 0000800000000000 (自己计算) ) ) ) )。
0x00 00 7F FF FF FF FF FF==127T
疑问: 64位系统128T的边界线是127T吗?
访问权限
r> 进程在用户态时,只能访问用户空间内存只有进入内核态后,才可以访问内核空间的内存
PAE机制
CPU位宽指的是一个时钟周期内CPU能处理的二进制位数,普通场景中32位系统CPU的地址总线可以是32位,但是引入了PAE机制之后,16位CPU的地址总线位宽可以是20位(物理内存1M),32位CPU的地址总线可以是36位(物理内存64GB),64位CPU的地址总线位宽可以是40位(物理内存1TB)。因此我们不能简单的说32位系统只支持最大4GB的内存条。
调试程序寄存器
cs:是代码段寄存器
ds:是数据段寄存器
ss:是堆栈段寄存器
es:是扩展段寄存器
fs:是标志段寄存器 32位之后才有
gs:是全局段寄存器 32位之后才有
示例一个内核宕机的日志:
RIP: 0010:[] [] xxxxxxxxxx+0x69/0x70
RSP: 0018:ffff886241737d98 EFLAGS: 00010246
RAX: ffff880034814d40 RBX: ffff881fc6248740 RCX: 0000000000000200
RDX: 0000000000000000 RSI: 0000000000000286 RDI: ffff881fc6381858
RBP: ffff886241737d98 R08: ffff886241734000 R09: 0000000000000000
R10: ffff880034814d40 R11: 0000000000000200 R12: ffff881fc62487a0
R13: 0000000000000000 R14: 00007fff86cb6260 R15: ffff881fc6381858
FS: 00007f78b59b8720(0000) GS:ffff885ffe3c0000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f690a057180 CR3: 0000006208985000 CR4: 00000000003627e0
DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
查看程序寄存器
使用GDB随意调试一个linux 32位上的ELF32的可执行文件,使用info r命令查看一下寄存器情况:
段寄存器有0x23和0x2b两种情况:
十六进制:0023
二进制:0000000000100 0 11 - 畅快的缘分:4 - 表类型:GDT - 特权级:Ring3
十六进制:002B
二进制:0000000000101 0 11 - 畅快的缘分:5 - 表类型:GDT - 特权级:Ring3
畅快的缘分:从第四位开始 表类型:第三位 特权级:第1、2位
Linux下没有找到可以直接用什么命令或者工具查看GDT的方式,于是去源代码中寻找答案:
看到了吗,这两项所描述的段和Windows一样,基地址为0,大小为4GB。
Windows和Linux都选择了通过这种方式架空了CPU的分段内存管理机制。
但需要说明一下的时,虽然两个操作系统都是这种情况,但并不意味着段机制彻底没用到,CPU的任务管理TSS还是需要用到,这一点大家知道就行了,在linux64位系统下分段机制不被待见,但是操作系统仍然会保持先分段再分页的寻址方式。
5.参考资料:
图形化解释内存:https://segmentfault.com/a/1190000023055534#item-2-6
图形代码结合:https://www.cnblogs.com/alantu2018/p/9177356.html
当代操作系统内存管理:https://www.cnblogs.com/xuanyuan/p/15266447.html
GDT:https://en.wikipedia.org/wiki/Global_Descriptor_Table
线性地址转为物理地址实践:https://www.cnblogs.com/onetrainee/p/11721946.html
64位系统40位物理地址解释:https://zhuanlan.zhihu.com/p/69334474