用户与内核空间
内核空间与用户空间的关系
____在一个32位系统中,一个程序的虚拟空间最大可以是4GB,那么最直接的做法就是,把内核也看作是一个程序,使它和其他程序一样也具有4GB空间。但是这种做法会使系统不断的切换用户程序的页表和内核页表,以致影响计算机的效率。解决这个问题的最好做法就是把4GB空间分成两个部分:一部分为用户空间,另一部分为内核空间,这样就可以保证内核空间固定不变,而当程序切换时,改变的仅是程序的页表。这种做法的唯一缺点便是内核空间和用户空间均变小了。
例如:在i386这种32位的硬件平台上,Linux在文件page.h中定义了一个常量PAGE_OFFSET:1
2
3
4
5
6
Linux以PAGE_OFFSET为界将4GB的虚拟内存空间分成了两部分:地址0GB-3GB这段低地址空间为用户空间,大小为3GB;地址3GB-4GB这段高地址空间为内核空间,大小为1GB。
当系统中运行多个程序时,多个用户空间与内核空间的关系可以表示如下图:
如图中所示,程序1、2……n共享内核空间。当然,这里的共享指得是分时共享,因为在任何时刻,对于单核处理器系统来说,只能有一个程序在运行。
内核空间的总体布局
Linux在发展过程中,随着硬件设备的更新和技术水平的提高,其内核空间布局的发展也是一种不断打补丁的方式。这样的后果就是使得内核空间被分成不同的几个区域,而且在不同的区域具有不同的映射方式。通常,人们认为Linux内核空间有三个区域,即DMA区(ZONE_DMA)、普通区(ZONE_NORMAL)和高端内存区(ZONE_HIGHMEM)。
实际物理内存较小时内核空间的直接映射
早期计算机实际配置的物理内存通常只有几MB,所以为了提高内核通过虚拟地址访问物理地址内存的速度,内核空间的虚拟地址与物理内存地址采用了一种从低地址向高地址依次一一对应的固定映射方式,如下图所示:
可以看到,这种固定映射方式使得虚拟地址与物理地址的关系变得很简单,即内核虚拟地址与实际物理地址只在数值上相差一个固定的偏移量PAGE_OFFSET,所以当内核使用虚拟地址访问物理页框时,只需在虚拟地址上减去PAGE_OFFSET即可得到实际物理地址,比使用页表的方式要快得多!
由于这种做法几乎就是直接使用物理地址,所以这种按固定映射方式的内核空间也就叫做“物理内存空间”,简称物理内存。另外,由于固定映射方式是一种线性映射,所以这个区域也叫做线性映射区。
当然,这种情况下(计算机实际物理内存较小时),内核固定映射空间仅占整个1GB内核空间的一部分。例如:在配置32MB实际物理内存的x86计算机系统时,内核的固定映射区便是PAGE_OFFSET-(PAGE_OFFSET+0x02000000)这个32MB空间。那么内核空间剩余的内核虚拟空间怎么办呢?
当然还是按照普通虚拟空间的管理方式,以页表的非线性映射方式使用物理内存。具体来说,在整个1GB内核空间中去除固定映射区,然后在剩余部分中再去除其开头部分的一个8MB隔离区,余下的就是映射方式与用户空间相同的普通虚拟内存映射区。在这个区,虚拟地址和物理地址不仅不存在固定映射关系,而且通过调用内核函数vmalloc()获得动态内存,故这个区就被称为vmalloc分配区,如下图所示:
对于配置32MB实际物理内存的x86计算机系统来说,vmalloc分配区的起始位置为PAGE_OFFSET+0x02000000+0x00800000。
这里说明一下:这里说的内核空间与物理页框的固定映射,实质上是内核页对物理页框的一种“预定”,并不是说这些页就“霸占”了这些物理页框。即只有当虚拟页真正需要访问物理页框时,虚拟页才与物理页框绑定。而平时,当某个物理页框不被与它对应的虚拟页所使用时,该页框完全可以被用户空间以及后面所介绍的内核kmalloc分配区使用。
总之,在实际物理内存较小的系统中,实际内存的大小就是内核空间的物理内存区与vmalloc分配区的边界。
ZONE_DMA区与ZONE_NORMAL区
对于整个1GB的内核空间,人们还把该空间头部的16MB叫做DMA区,即ZONE_DMA区,因为以往硬件将DMA空间固定在了物理内存的低16MB空间;其余区则叫做普通区,即ZONE_NORMAL。
内核空间的高端内存
随着计算机技术的发展,计算机的实际物理内存越来越大,从而使得内核固定映射区(线性区)也越来越大。显然,如果不加以限制,当实际物理内存达到1GB时,vmalloc分配区(非线性区)将不复存在。于是以前开发的、调用了vmalloc()的内核代码也就不再可用,显然为了兼容早期的内核代码,这是不能允许的。
下图就表示了这种内核空间所面临的局面:
显然,出现上述问题的原因就是没有预料到实际物理内存可以超过1GB,因而没有为内核固定映射区的边界设定限制,而任由其随着实际物理内存的增大而增大。
解决上述问题的方法就是:对内核空间固定映射区的上限加以限制,使之不能随着物理内存的增加而任意增加。Linux规定,内核映射区的上边界的值最大不能大于一个小于1G的常数high_menory,当实际物理内存较大时,以3G+high_memory为边界来确定物理内存区。
例如:对于x86系统,high_memory的值为896M,于是1GB内核空间余下的128MB为非线性映射区。这样就确保在任何情况下,内核都有足够的非线性映射区以兼容早期代码并可以按普通虚存方式访问实际物理内存的1GB以上的空间。
也就是说,高端内存的最基本思想:借一段地址空间,建立临时地址映射,用完后释放,达到这段地址空间可以循环使用,访问所有物理内存。当计算机是物理内存较大时,内核空间的示意图如下
习惯上,Linux把内核空间3G+high_memory~4G-1的这个部分叫做高端内存区(ZONE_HIGHMEM)。
总结一下:在x86结构的内核空间,三种类型的区域(从3G开始计算)如下:
- ZONE_DMA:内核空间开始的16MB
- ZONE_NORMAL:内核空间16MB~896MB(固定映射)
- ZONE_HIGHMEM :内核空间896MB ~ 结束(1G)
根据应用目标不同,高端内存区分vmalloc区、可持久映射区和临时映射区。内核空间中高端内存的布局如下图所示:
内核将高端内存划分为3部分:VMALLOC_START~VMALLOC_END、KMAP_BASE~FIXADDR_START和FIXADDR_START~4G。
vmalloc映射区
vmalloc映射区时高端内存的主要部分,该区间的头部与内核线性映射空间之间有一个8MB的隔离区,尾部与后续的可持久映射区有一个4KB的隔离区。
vmalloc映射区的映射方式与用户空间完全相同,内核可以通过调用函数vmalloc()在这个区域获得内存。这个函数的功能相当于用户空间的malloc(),所提供的内存空间在虚拟地址上连续(注意,不保证物理地址连续)。
可持久内核映射区
如果是通过 alloc_page() 获得了高端内存对应的 page,如何给它找个线性空间?
内核专门为此留出一块线性空间,从PKMAP_BASE开始,用于映射高端内存,就是可持久内核映射区。
在可持久内核映射区,可通过调用函数kmap()在物理页框与内核虚拟页之间建立长期映射。这个空间通常为4MB,最多能映射1024个页框,数量较为稀少,所以为了加强页框的周转,应及时调用函数kunmap()将不再使用的物理页框释放。
临时映射区
临时映射区也叫固定映射区和保留区。该区主要应用在多处理器系统中,因为在这个区域所获得的内存空间没有所保护,故所获得的内存必须及时使用;否则一旦有新的请求,该页框上的内容就会被覆盖,所以这个区域叫做临时映射区。