Linux内存管理
内存寻址
内存地址
当使用80x86处理器时,必须区分三个地址
- 逻辑地址:每个逻辑地址包括一个段和偏移,偏移指从段起始地址到实际地址的偏移。
- 线性地址:一个32位无符号整数,可以用来表示高达4GB的地址,通常用十六进制表示,范围从0x00000000到0xffffffff
- 物理地址:用于在内存芯片上寻址内存单元。与从CPU地址引脚发送到内存总线的电信号相对应。
MMU通过一种称为分段单元(segmentation unit)的硬件电路吧逻辑地址转换成线性地址,接着分页单元(paging uni)的硬件电路把线性地址转换成一个物理地址。
Linux中的分段
Linux中的四个段
Linux中只有4个内存段 (全局描述符表中关于内存的段描述符只有这4个。后面的线性空间的划分中,是在数据结构层面人为的划分,没有对应的描述符)
段 | Base | G | Limit | S | Type | DPL | D/B | P |
---|---|---|---|---|---|---|---|---|
用户代码段 | 0x00000000 | 1 | 0xfffff | 1 | 10 | 3 | 1 | 1 |
用户数据段 | 0x00000000 | 1 | 0xfffff | 1 | 2 | 3 | 1 | 1 |
内核代码段 | 0x00000000 | 1 | 0xfffff | 1 | 10 | 0 | 1 | 1 |
内核数据段 | 0x00000000 | 1 | 0xfffff | 1 | 2 | 0 | 1 | 1 |
linux段机制
他们都是从0开始,即意味着用户态和内核态下的所有进程使用相同的线性地址。Linux的分段机制主要是为了权限管理。
GDT和LDT
每个cpu对应一个GDT,Linux中基本不使用LDT。
Linux中的分页
线性地址分成以固定长度为单位的组,称为页。
分页单元把所有的RAM分成固定长度的页框(也叫物理页),每个页框包含一个页,也就是说一个页框的长度与一个页的长度一致。页框是主存的一部分,也是一个存储区域。页只是一个数据块,可以存放在任何页框或磁盘中。分页单元把线性地址转化成物理地址。
高速缓存单元插在分页单元和内存之间。
在linux设置中,对于所有的页框都启用高速缓存,对于写操作总是采用回写(延迟写)策略。
Linux内核地址空间
- 直接映射区:线性空间中从3G开始最大896M的区间,为直接内存映射区,该区域的线性地址和物理地址存在线性转换关系:线性地址=3G+物理地址。
- 动态内存映射区:该区域由内核函数vmalloc来分配,特点是:线性空间连续,但是对应的物理空间不一定连续。vmalloc分配的线性地址所对应的物理页可能处于低端内存,也可能处于高端内存。
- 永久内存映射区:该区域可访问高端内存。访问方法是使用alloc_page(_GFP_HIGHMEM)分配高端内存页或者使用kmap函数将分配到的高端内存映射到该区域。
alloc_page是直接调用buddy算法的接口,返回page结构
4. 固定映射区:该区域和4G的顶端只有4k的隔离带,其每个地址项都服务于特定的用途,如ACPI_BASE等。
关于内核高端内存
Linux把物理内存划分为了三个管理区, 分别为0-16MB的ZONE_DMA, 16-896MB的ZONE_NORMAL和高于896MB的ZONE_HIGHMEM也就是高端内存。
- ZONE_DMA好理解, 因为ISA总线只能对前16MB进行DMA寻址, 这块要分出来不能乱用。
- ZONE_NORMAL对应内核的直接映射区。
- ZONE_HIGHMEM对应动态内存映射区,永久内存映射区,固定映射区
总结
总体图
内核内存管理
物理内存管理机制
内核中物理内存的管理机制主要有伙伴算法,slab高速缓存和vmalloc机制。Linux给内核分配了线性地址空间后,立刻分配物理页。
伙伴系统算法减少外部碎片
不详细展开
slab分配器
Slab分配器的优点:
- 解决伙伴算法引起的内部碎片
- 加快分配速度
内核倾向于反复请求同一类型的内存区;内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间;对内存区的请求可通过根据他们发生的频率来分类
3. 伙伴系统的调用会影响硬件高速缓存,slab分配器的着色有效解决问题
slab分配器着色
A,B对象很可能映射到cache的第0行,此时,如果CPU交替的访问A,B各50次,每一次访问cache第0行都失效,从而需要从内存传送数据。
而slab着色就是为解决该问题产生的,不同的颜色代表了不同的起始对象偏移量,对于B对象,如果将其位置偏移向右偏移32B,则其可能会被映射到cache的第1行上,这样交替的访问A,B各50次,只需要2次内存访问即可。
用户地址空间
线性空间
Linux采用虚拟内存管理技术,每一个进程都有一个3G大小的独立的进程地址空间,这个地址空间就是用户空间。每个进程的用户空间都是完全独立、互不相干的。进程访问内核空间的方式:系统调用和中断。
创建进程等进程相关操作都需要分配内存给进程。这时进程申请和获得的不是物理地址,仅仅是虚拟地址。
实际的物理内存只有当进程真的去访问新获取的虚拟地址时,才会由“请页机制”产生“缺页”异常,从而进入分配实际页框的程序。该异常是虚拟内存机制赖以存在的基本保证,它会告诉内核去为进程分配物理页,并建立对应的页表,这之后虚拟地址才实实在在的映射到了物理地址上。
基本流程
通过brk/do_mmap分配vm_area_struct中的空间,通过缺页中断分配物理页框
基本数据结构
进程所拥有的线性区间是通过一个简单的链表进行链接在一起的,出现在链表中的线性区是按照内存地址的升序进行排列的。
- mmap 指向线性区对象的链表头,具体下一部分介绍。
- mm_rb指向线性区对象的红-黑树的根。mmap 和 mm_rb 这两个不同数据结构体描述的对象是相同的:该地址空间中的所有内存区域。mmap 指向一个 vm_area_struct 结构的链表,利于简单、高效地遍历所有元素。 mm_rb 指向的是一个红-黑树结构节点,适合搜索指定元素。
- pgd 指向第一级页表即页全局目录的基址,当内核运行这个进程时,它就将pgd存放在CR3寄存器内,根据它来进行地址转换工作。
- mmlist 将所有的内存描述符存放在一个双向链表中,第一个元素是init_mm的mmlist字段。
- mm_users 存放共享mm_struct数据结构的轻量级进程的个数。
- mm_count mm_count字段是内存描述符的主使用计数器,在mm_users次使用计数器中的所有用户在mm_count中只作为一个单元。每当mm_count递减时,内核都要检查它是否变为0,如果是,就要解除这个内存描述符,因为不再有用户使用它。
为了提高搜索效率,Linux也使用了红-黑树,这两种数据结构包含指向同一线性区描述符的指针,当插入或删除一个线性区描述符时,内核通过红-黑树搜索前后元素,并用搜索结果快速更新链表而不用扫描链表。一般来说,红-黑树用来确定含有指定地址的线性区,而链表通常在扫描整个线性区集合时来使用。