这部分主要研究 Linux 内存管理的基础知识, 重点在于对设备驱动有用的技术. 因为许多驱动编程需要一些对于虚拟内存(VM)子系统原理的理解。
而这些知识主要分为三个部分:
1、 mmap系统调用的实现原理:它允许设备内存直接映射到一个用户进程地址空间. 这样做对一些设备来说可显著地提高性能.
2、与mmap的功能相反的应用原理:内核态代码如何跨过边界直接存取用户空间的内存页. 虽然较少驱动需要这个能力. 但是了解如何映射用户空间内存到内核(使用 get_user_pages)会有用.
3、直接内存存取( DMA ) I/O 操作, 它提供给外设对系统内存的直接存取.
但所有这些技术需要理解 Linux 内存管理的基本原理, 因此我将先学习VM子系统的基本原理.
一、Linux的内存管理
这里重点是 Linux 内存管理实现的主要特点,而不是描述操作系统的内存管理理论。Linux虚拟内存管理非常的复杂,要写可以写一本书:。学习驱动无须如此深入, 但是对它的工作原理的基本了解是必要的.
解了必要的背景知识后,才可以学习内核管理内存的数据结构.
Linux
是一个虚拟内存系统(但是在没有MMU的CPU中跑的ucLinux除外), 意味着在内核启动了MMU
之后所有使用的地址不直接对应于硬件使用的物理地址,这些地址(称之为虚拟地址)都经过了MMU转换为物理地址之后再从CPU的内存总线中发出,读取/写
入数据.
这样 VM 就引入了一个间接层, 它是许多操作成为可能: 1、
系统中运行的程序可以分配远多于物理内存的内存空间,即便单个进程都可拥有一个大于系统的物理内存的虚拟地址空间.
2、虚拟内存也允许程序对进程的地址空间运用多种技巧, 包括映射程序的内存到设备内存.等等~~~
1、地址类型
Linux 系统处理几种类型的地址, 每个有它自己的含义:
用户虚拟地址:User virtual addresses,用户程序见到的常规地址. 用户地址在长度上是 32 位或者 64 位, 依赖底层的硬件结构, 并且每个进程有它自己的虚拟地址空间.
总线地址:Bus addresses,在外设和内存之间使用的地址,但是这不是必要. 一些体系可提供一个
I/O 内存管理单元(IOMMU), 它在总线和主内存之间重映射地址.
物理地址:Physical addresses,在处理器和系统内存之间使用的地址. 有32bit或64bit等。
以下的概念很重要,一定要理解了再往下看:
内核逻辑地址:Kernel
logical
addresses,他们是虚拟地址(需要经过MMU转换的地址),这些组成了常规的内核地址空间.这些地址映射了部分(也许全部)主存并且常常被当作物
理内存. 在大部分的体系上, 逻辑地址和它们的相关物理地址只差一个常量偏移. 逻辑地址常常存储于 unsigned long 或者 void *
类型的变量中. 从 kmalloc 返回的内存就是内核逻辑地址.
内核虚拟地址:Kernel virtual addresses,它们都是从内核地址空间到物理地址的映射,但是内核虚拟地址并不必像逻辑地址空间一样具备线性的、一对一到物理地址的映射。所有的逻辑地址是内核虚拟地址, 但是许多内核虚拟地址不是逻辑地址. (也就是说在启动MMU后所有的地址都是内核虚拟地址,但是有一部分可以称为内核逻辑地址,因为他们具有上面介绍的特性:线性且连续、与对应的物理地址只差一个常量偏移)
对于内核虚拟地址,vmalloc 分配的内存是虚拟地址. kmap 函数也返回虚拟地址. 虚拟地址常常存储于指针变量.
如果有内核逻辑地址, 可通过宏 __pa() ( 在 中定义,但是也可能在它包含的头文件中)返回它关联的物理地址. 同时物理地址也可被映射回逻辑地址使用 __va(), 但是只适用于低端内存(后面会讲到). 不同的内核函数需要不同类型地址.
2、物理地址和页
物理内存被划分为离散的单元称为页. 系统内部的许多内存处理都基于单个页. 页大小依赖体系结构, 大部分系统使用 4K字节为一页(ARM就是4KB/page). 在不同的体系上,常量 PAGE_SIZE (定义在 ) 给出了页大小的具体定义.
-
物理内存会划分为固定大小的页来处理的原因,个人认为主要是由于MMU的分页机制决定的(X86正常情况下MMU的页大小为4KB,ARM的MMU的小页
的页大小也是4KB)。有的CPU的MMU分页的页大小是可调的,这可能使得内核配置的PAGE_SIZE也随之改变,比如MIPS构架的页大小可能是
4KB、8KB、16KB、32KB和64KB。
一个内存地址(虚拟或物理),它可分为一个页帧号和一个页内的偏移. 以使用 4KB页为例, 低12 位有效位是偏移, 而剩下的高位为页帧号.
若忽略偏移并向右移动地址 offset 位, 结果即为页帧号 (PFN). 移位来在页帧号和地址之间转换是一个相当普通的操作. 宏
PAGE_SHIFT 告诉必须移动多少位来进行这个转换.
3、高端与低端内存
在拥有大量内存的 32bit系统中,内核逻辑地址和虚拟地址之间的差异就会突显出来。 32 bit系统可寻址4G内存. 但是因为建立虚拟地址空间的限制(不能把所有虚拟地址空间都用完,必须留下一些作为临时的映射和IO空间映射之用),早期的在 32bit Linux系统被限制使用少于4G的内存,比如我的DELL vostro 120笔记本,我升级到4G内存,原来的ubuntu 8.10 32bit只认到3G,且64bit根本装不上。后来似乎是升级到了ubuntu 10.10 32bit才认到了4G内存。
内核(在x86体系的缺省配置里)划分4GB虚拟地址空间为用户空间和内核空间;在2个上下文中使用同一套映射.一个典型的划分:3GB用户空间-1GB内核空间。内核的代码和数据结构必须要匹配这个空间,
占用内核地址空间最大部分的是物理内存的虚拟映射(包括逻辑地址映射和虚拟地址映射(如果有高端内存)). 内核不能直接操作没有映射到内核地址空间的内存. 换句话说, 内核对任何内存的访问需使用它自己的虚拟地址。因此, 多年来, 能够被内核处理的最大物理内存量是能够映射到虚拟地址的内核部分的大小再减去内核代码自身的空间.因此,基于x86的Linux系统可以使用的最大内存量会比1GB稍小。
为了使用更多内存, 在不破坏 32bit应用和系统的兼容性的情况下,处理器制造商在产品中增加了"地址扩展"特性. 这样,即便32bit处理器也能够寻址多于4GB物理内存. 但可被直接用逻辑地址映射的内存大小限制还存在. 这样内存的最低部分(根据硬件和内核配置一般是 1到2 GB, )有逻辑地址; 剩下的(高端内存)没有. 在访问一个特定高地址页前, 内核必须建立一个明确的虚拟映射来使此页可在内核地址空间中被访问. 因此, 许多内核数据结构必须放在低端内存; 高端内存主要为用户进程页所保留.
所以定义如下:
Low memory:低端内存
在内核空间中拥有逻辑地址的内存. 在大部分系统中(ARM构架几乎都是),几乎所有的内存都是低端内存.
High memory:高端内存
没有逻辑地址映射的内存,它位于内核逻辑地址范围之外,使用前必须使用vmalloc等内核函数做好映射.
在 i386 系统上, 低和高内存之间的分界在内核配置时可被改变,但常常设置在1GB以下。 这个边界与硬件无关.它是由内核自身设置的。
其实我认为这个分界线和两个内核配置因素有关:
(1)内核对于内核空间和用户空间比例的配置。1GB :3GB、2GB :2GB、3GB :1GB等(例如比例是2GB :2GB,内核又有3G内存,则低端内存是可以超过1G的~~)
(2)分配给内核虚拟地址空间的大小vmalloc,这个是可以在启动参数中设置的。
-
vmalloc=nn[KMG]
-
强制指定vmalloc区域的大小。可用于增加vmalloc区域的最小尺寸(x86默认128MB),也可以用于减少vmalloc的大小,增加更多的空间用于直接映射内核RAM。
当然硬件上的限制就是RAM的大小,低端内存和高端内存的和总不能超过RAM总大小吧~~~
关于X86这方面的更详细的资料,我觉得可以参考以下链接:
那么对于ARM构架来说,情况是一样的。虽然说ARM构架中你很少能找到存在高端内存的设备,但是如果内存足够大(例如1GB、2GB的板子也不是没有,比如TI芯片DM8168的16路D1解决方案就用了1GB或2GB内存,当然其中一部分要分给视频协处理器和DSP),然后将vmalloc设置大些,也可以在系统中出现高端内存。
最后对于LDD3的图,我感觉意思表达得不太好,自己画了一个。我的理解是这样的,如果您觉得我错了,请指出来,先谢谢了!