概述
这篇文章我们探究下Linux内存管理领域的相关基础知识, 覆盖编写linux driver与linux 应用程序需要深入理解的的相关知识。
地址空间
物理地址空间
系统硬件使用的地址(比如 RAM, DMA, peripherals)位于物理地址空间。
一个具有32-bit data bus(单次处理速度)与32-bit address bus的处理器(可访问的物理空间),理论上可寻址的物理空间是2^32=4G。现代的处理器通常具有36-bit,48-bit或是64-bit的地址总线,最大可寻址物理空间可通过类似的方法算得。
本文既定讨论范围是针对具有32-bit address bus处理器,片上内置MMU的SOC。这4G的物理寻址空间,一部分用来寻址主存-DDR,另一部分用来寻址外设存储,看下面示例图就相对明了了。
我们把DDR与外设存储(PCIe设备,显卡,网卡等)统称为physical memory。在linux os下通过cat /proc/iomem 可查看系统的physical memory,我们归纳为一个简单的示意图如下:
虚拟地址空间
Linux os中软件使用的地址位于虚拟地址空间。
针对具备MMU的现代计算机系统,linux os中的kernel space与user space中各进程能直接访问的是虚拟地址空间,用一个概括性示意图总结下:
地址类型
地址类型包括如下:
要更好理解物理地址、总线地址与虚拟地址,我们首先要明确讨论视角:
针对物理地址,站在系统主存DDR的角度,也就是直接访问主存的地址
总线地址,站在各类连接到总线上外设的角度,也就是直接访问外设的地址
虚拟地址,kernel space进程与user space进程直接使用的地址
物理地址
针对系统主存而言的访问地址
总线地址
这对各类总线外设而言的地址
通常处理器访问DDR与访问外设使用相同的机制,但也不完全,一些架构可以提供I/O内存管理单元(IOMMU),在总线和主存储器之间重新映射地址如:
一个IOMMU可以在许多方面优化系统性能(比如,使内存中分散的缓存变成对设备而言当作连续的内存使用),在设置DMA时,须事先配置好IOMMU。总线地址高度依赖于体系结构。
虚拟地址
High and Low Memory概念
High memory与Low memory是针对物理地址而言的。
Low memory
对应的存在kernel逻辑地址的物理地址,物理上是连续的
High memory
仅针对32-bit系统挂接大容量RAM的情况,对应的不存在kernel逻辑地址的物理地址,在kernel中使用时是非物理连续的
比如32-bit系统,挂接一个比较小的RAM(小于1G),这种情况下针对物理RAM就不必要划分low memory与high memory:
再比如针对32-bit系统,挂接大容量RAM(大于1G),这种情况下针对物理RAM有必要划分low memory与high memory
为什么会有高低内存的限制呢,看下面这段来自LDD3的分析,可以明确原因:
内核(x86架构默认配置中)在用户空间和内核之间分割4G的虚拟地址空间,在这两个上下文中使用相同的映射集。典型的分割是将3G用于用户空间,1G用于内核空间。内核的代码和数据结构必须限定在1G内核空间,但是内核地址空间最大的消费者是物理内存的虚拟映射,内核不能直接操作没有映射到内核地址空间的内存。换句话说,内核对于它必须直接接触的任何内存都需要自己的虚拟地址。因此,内核能够处理的最大物理内存就是可以映射到内核的虚拟地址空间部分的物理内存量,减去内核代码本身所需的空间。因此,基于x86的Linux系统 的kernel 最多只能使用略低于1 GB的物理内存:
kernel逻辑地址
隶属kernel常规地址空间,在大多数体系结构上,逻辑地址和它们相关联的物理地址之间的差异只是一个常量偏移量。这也意味着逻辑上连续一定对应着物理上的连续,适用于DMA。
针对小于1G RAM的系统,kernel 逻辑范围是PAGE_OFFSET设定值~physical memory end。
针对32-bit有大容量memory的系统,不是所有的物理RAM可以被映射到kernel 逻辑地址空间。默认的这对32-bit系统,kernel地址空间是1G,这种情况下大容量RAM的系统的low memory部分可以被映射到kernel逻辑地址。对于64-bit系统,不存在这个限制,所有的物理RAM可以被映射到kernel逻辑地址空间。
kernel 虚拟地址
针对32-bit 大容量物理RAM系统存在的概念,位于kernel逻辑地址之上的部分:
有一点需要注意,kernel space连续的虚拟地址,对应的物理地址不一定是连续的,因此不适用于DMA。
用户虚拟地址
是用户空间程序使用的常规地址,对应PAGE_OFFSET设置值以下的地址部分。每个用户进程有其独立的虚拟地址空间,进程中的线程共享这部分地址空间。
详细结构可参见内核数据结构struct mm(位于task_struct中的指针),对应的在linux os中可以通过cat /proc/pid/maps 查看用户进程地址情况。
内存映射
虚拟内存
内存映射着重讨论虚拟地址空间与物理地址空间的映射,包括
映射虚拟地址到physical ram
映射虚拟地址到hardware devices(PCI/PCIe devices,GPU RAM,on-SOC IP blocks)
虚拟内存的优点
kernel space与user space的内存是分开的
每个进程可以拥有其对应的独立虚拟内存
硬件设备的内存可以被映射到进程地址空间-kernel执行相应的map
系统物理RAM可以一次被映射到多个进程-shared memory
内存区域可以进行权限限制-读,写,执行
虚拟内存特点
访问已经映射的内存区域没有性能损失
无惩罚权限处理
使用相同的CPU指令访问RAM和映射的硬件
kernel space与user space 进程在正常运行时,只使用虚拟地址
内存映射的执行者-MMU
MMU是什么
位于CPU核心和内存之间,通常是物理CPU本身的一部分。-在ARM上,它是授权核心的一部分。与RAM控制器分离,DDR控制器为单独的IP模块
MMU做什么
透明地处理来自Load/Store指令的所有内存访问
处理虚拟地址到system ram的映射访问
处理虚拟地址到系统具备内存映射功能的外设的映射访问
权限处理
无效访问时产生page fault 异常
MMU的关键
TLB-Translation Lookaside Buffer
TLB是MMU system的关键部分,负责虚拟地址到物理地址的映射
内存映射接口
kernel space
kernel 逻辑地址
kmalloc()
通过宏_pa(x)-将逻辑地址映射到物理地址
通过宏_va(x)-将物理地址映射到逻辑地址
kernel 虚拟地址
vamlloc()
ioremap()
kmap()
user space
用户虚拟地址
与架构无关的*alloc()家族
架构相关的系统级接口
mmap()
brk()/sbrk()