Linux内存管理和寻址详细介绍
1.概念
内存管理模式
段式:内存分为了多段,每段都是连续的内存,不同的段对应不用的用途。每个段的大小都不是统一的,会导致内存碎片和内存交换效率低的问题。
页式:内存划分为多个内存页进行管理,如在 linux 系统中,每一页的大小为 4kb
。由于分了页后,就不会产生细小的内存碎片。但是仍然也存在内存碎片问题。
段页式:段式和页式结合。
地址类型划分
逻辑地址:程序所使用的地址,通常是没被段式内存管理映射的地址,称为逻辑地址
线性地址:通过段式内存管理映射的地址,称为线性地址,也叫虚拟地址
虚拟地址:通过段式内存管理映射的地址,称为线性地址,也叫虚拟地址
物理地址:物理内存地址
说明:
inetel处理器中,逻辑地址是「段式内存管理」转换前的地址,线性地址则是「页式内存管理」转换前的地址。
段式内存管理映射而成的地址不再是“物理地址”了,intel 就称之为“线性地址”(也称虚拟地址)。于是,段式内存管理先将逻辑地址映射成线性地址,然后再由页式内存管理将线性地址映射成物理地址。
linux内存主要是页式内存管理,同时也有涉及段式机制。当前linux内核所采取的办法是使段式映射的过程实际上不起什么作用。
intel最早处理器80286是纯段式管理,80386段式和页式均存在。
2.页式管理
x86架构32位cpu
二级页表选址方式,一个内存页4kb大小,一级页目录表1024项,二级页表1024项,一个页表项4字节。一级页目录表项全部分配,二级页表在需要的时候创建。(局部性原理)。
虚拟地址32位
10+10+12,分别索引1级页表号,2级页表项,记录物理基地址的偏移地址。使用pae机制之后32bit系统支持最大的内存是64gb(地址是32+4=36位)。
线性地址寻址物理地址步骤
先根据10位寻址1级页表号,1级页表号中记录了2级页表的地址
找到2级页表地址后,接着根据虚拟地址的另10位寻找2级页表中表项的位置
找到2级页表的表项之后,表项中记录了该虚拟地址映射物理地址的起始地址,表项的大小是4字节32bit
根据找到的物理地址的起始地址结合虚拟地址的后12位作为偏移计算出最终的物理地址
x86架构 64位cpu
存在更多级页表
全局页目录项 pgd(page global directory上层页目录项 pud(page upper directory)中间页目录项 pmd(page middle directory)页表项 pte(page table entry)
线性地址寻址物理地址步骤
线性地址为48bit,最大物理地址为52bit,实际物理内存地址总线宽度是40bit,也就是支持1tb物理内存x86_64有四级页表,原理同x86系统,也是一层层的寻址cr3寄存器保存最高层一级表的起始物理地址,因此寻址首先就是要获取到cr3寄存器中的值每个pte表项的大小是8个字节也就是64bit
tlb
在 cpu 芯片中,加入了一个专门存放程序最常访问的页表项的 cache,这个 cache 就是 tl(translation lookaside buffer) 。通常称为页表缓存、转址旁路缓存、快表等。那么在cpu的内存管理单元mmu寻址时,会先查 tlb,如果没找到,才会继续查常规的页表。
专有名词
pdt:页目录表,多级页表一级页表,32bit系统有1024个页目录
ptt:页表项表,多级页表二级页表,32bit系统有每个页目录下有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位系统:
内核128t: 0xff ff 80 00 00 00 00 00 - 0xff ff ff ff ff ff ff ff (高位)
0xff ff 7f ff ff ff ff ff - 0xff ff ff ff ff ff ff ff(自己计算)
用户128t: 0x00 00 00 00 00 00 00 00 - 0x00 00 7f ff ff ff ff ff (低位)
0x00 00 80 00 00 00 00 00 - 0x00 00 80 00 00 00 00 00 (自己计算)
0x00 00 7f ff ff ff ff ff == 127t
疑问:64位系统128t是分界线是127t?
访问权限
进程在用户态时,只能访问用户空间内存
只有进入内核态后,才可以访问内核空间的内存
pae机制
cpu位宽指的是一个时钟周期内cpu能处理的二进制位数,普通场景中32位系统cpu的地址总线可以是32位,但是引入了pae机制之后,16位cpu的地址总线位宽可以是20位(物理内存1m),32位cpu的地址总线可以是36位(物理内存64gb),64位cpu的地址总线位宽可以是40位(物理内存1tb)。因此我们不能简单的说32位系统只支持最大4gb的内存条。
4. 调试
程序寄存器
cs:是代码段寄存器
ds:是数据段寄存器
ss:是堆栈段寄存器
es:是扩展段寄存器
fs:是标志段寄存器 32位之后才有
gs:是全局段寄存器 32位之后才有
示例一个内核宕机的日志:
rip: 0010:[
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位系统下分段机制不被待见,但是操作系统仍然会保持先分段再分页的寻址方式。
结语
到此这篇关于linux内存管理和寻址详细介绍的文章就介绍到这了,更多相关linux内存管理和寻址内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!