# 内存管理之三

# Intel CPU与Linux内存管理

# Intel CPU物理结构

x86计算机刚刚加电时,处在实模式(Real Mode)下

# 实模式(Real Mode)

  • 20位地址总线:1M内存空间
  • 地址表示方式:段地址(16位):偏移地址(16位)
  • 段地址低4位对齐(段地址低4位永远是0,所以凑出来20位)
  • 实模式程序直接存取物理内存

# 保护模式(Protect Mode)

每个程序在自己的一个4G的空间内运行,程序间不会互相影响,保护了内核和进程。

  • 32位地址空间:4G内存
  • 支持多任务、任务切换、上下文保护
  • 进程隔离:代码和数据安全
  • 支持分段机制和分页机制
  • 新的寄存器:
    • EAX~EDX:扩充到32位
    • CR0~CR4
    • GDTR
    • LDTR
    • IDTR

# x86 CPU架构下的三种地址

逻辑地址:汇编语言(段:偏移)

图片名称
三种地址转换流程
图片名称

# Intel CPU段机制

# 段和段描述符

段:一段连续内存

段描述符:8个字节,描述段的属性,段基址、段界限、段属性、段类型、是否存在内存等等


图片名称
图片名称

逻辑地址转换为线性地址流程:


图片名称

# Linux页面机制

# Intel分页

  • Intel CPU的页:4KB
  • 通过设置寄存器CR0的PG位开启分页功能
  • 分页:线性地址->物理地址
  • 在MMU(管理分页的硬件)中进行分页

# Linux分页

在Linux中,采用三级页表结构。

  • 普通页表实现时的问题:
    • 32位OS(4G空间),每页4K,页表每个记录占4个字节
    • 进程的页数:4G/4K = 1M个页
    • 页表的记录数有:1M条记录
    • 页表所占的内存:1M*4字节 = 4M
    • 页表占页框数:4M/4K(页框每页4K) = 1K页框

因此难以找到连续的1K个页框来存放页表;页表全部装入会过度消耗内存。

解决思路:

  • 将4M的超大页表存储到离散的1K个页框中
  • 仅将页表的部分内容调入内存
# 二级页表

把超大的页表(4M)以页为单位分成若干个小页表,存入离散的若干个页框中。
例如:将超大页表(4M)划分为1024个小页表,每个页表的大小为4K,此时1个小页表刚好占用1个页框。
为了管理小页表,设置一个叫页目录的表,记录每个小页表的存放位置(即页框号)。

Windows NT采用了二级页表的结构


图片名称

# 二级页表地址映射特点
  • 访问数据需要三次访问:页目录,页表,最终单元
  • 页目录调入内存
  • 页表按需调入主存
  • 页面、页表、页目录大小都刚好4K(占1个页框)
# Linux三级页表结构
图片名称

# Linux对段的支持

Linux将4G的虚拟空间划分为两个部分,用户空间和内核空间。用户空间3G:从0到0xBFFFFFFF,内核空间1G:从0xC0000000到0xFFFFFFFF

  • Linux四个范围一样的段:0~0xFFFFFFFF(4G)
  • 各段属性不同
    • 内核段特权级为0
    • 用户段特权级为3
  • 作用
    • 利用段机制隔离用户数据和系统数据(保留段的等级保护机制)
    • 简化逻辑地址到线性地址的转换(可以直接将虚拟地址当作线性地址,两者完全一致)