Linux内存中线性地址为4G,0~3G为用户空间,3~4G为内核空间
一、 内核空间
内核空间是3~4G的内存地址,主要用来存储高优先级的代码
在X86结构中的内核地址存在三种类型的区域:
ZONE_DMA 内存开始的16m
ZONE_NORMAL 16m~896m
ZONE_HIGHMEM 896M~
ZONE_DMA是DMA使用的页(DMA是直接路径访问,不经过cpu缓存而直接访问内存)ZONE_NORMAL是正常可寻址的页。ZONE_HIGHMEM是动态映射的页。Linux把系统的页划分为不同的区,形成不同的内存池,这样就可以根据用途进行分配了。其中还有ZONE_DMA32,他只能被32位设备访问。
其中低端内存实现了对物理内存一对一的线性映射(只有1G),高端内存用来临时存放指向其余物理内存空间的地址。
Linux内核将高端内存划分为三个部分:vmalloc_start~vmalloc_end、kmap_base~fixaddr_start和fixaddr_start~4G,可以通过alloc_page()来获得高端内存对应的页,这个函数获得的只是线性地址。高端内存映射是临时存放非线性内存地址。
物理页在Linux中的表示
Linux通过struct page结构来表示物理页,是系统内存的最小单位
struct page {
unsigned long flags; //flag用来表示这些页的存放状态,比如是否为脏页(总共能够表示32位,在page-flags.h中可以看到)
atomic_t _count; //_count表明这个物理页的计数,若为-1表示内核还没有用到这一页,他是调用page_count()函数来检查这个域(返回0表示空闲,正整数表示正在使用)
void *virtual;//这个用来指向该页在虚拟内存中的地址,常用作高端内存的动态映射
…
}
高端内存的动态映射
0~896M的内存地址称为低端内存,他们跟物理内存是一对一的映射关系。896M~1G的内存地址称为高端内存,实现的是动态映射,目的在于访问内核中一对一映射以外的物理内存。进程A申请了4G开始的内存空间,而内存只能实现0~896M的一对一映射,因此需要将这个地址临时存放到高端内存中,等进程A使用完之后再释放掉。这样就可以访问所有的物理内存地址了。
内存申请
内核中申请内存的方法有:kmalloc()、kzalloc()、vmalloc()、alloc_page()
1、Kmalloc用于申请物理内存,地址上无须连续,因此性能较快。对应的内存释放函数是 kfree()。
2、Kzalloc相对于kmalloc多了一个__GFP_ZERO标志,这个标志位会对申请到的内存内容清零。对应的内存释放函数也是 kfree()。get_zeroed_page()也是同样的效果。
3、Vmalloc用于申请虚拟内存,由于虚拟内存需要保证一定的连续性,因此性能较慢(可能造成比较大的TLB抖动)。由于对象是虚拟内存,所以申请的内存大小没有限制,通常被用来申请大内存空间。对应的内存释放函数是 vfree()。
4、alloc_page()用于申请连续的物理页,可以通过page_address()把指定的页转化成逻辑地址。
Kmalloc()和__get_free_pages()不能分配高端内存,因为这两个函数返回的是物理地址上的逻辑地址,可能还没有映射到虚拟地址上,并非page结构。只有alloc_page()才能分配高端内存
低级页的分配标志位
Linux内存管理的接口都会涉及到一个标志位,如unsigned long get_zeroed_page(unsigned int gfp_mask)。 gfp_mask是一些标志的集合,这些标志可以分为两大类:行为修饰符和区修饰符。行为修饰符表示内核如何分配所需的内存。区修饰符表示从哪里分配内存。
常见的行为修饰符
标志 描述
__GFP_WAIT 分配器可以睡眠
__GFP_IO 分配器可以启动磁盘I/O
__GFP_FS 分配器可以启动文件系统I/O
__GFP_HIGH 分配器可以访问紧急事件缓冲池
常见的区修饰符
标志 描述
__GFP_DMA 只从ZONE_DMA分配
__GFP_HIGHMEM 从ZONE_HIGHMEM或ZONE_NORMAL分配,但会优先考虑高端内存
Slab层
slab分配器专门用来分配小内存。其中,slab分配器将SLAB分为两大类:专用SLAB和普通SLAB。专用SLAB用于特定的场合(比如TCP有自己专用的SLAB,当TCP模块需要小内存时,会从自己的SLAB中分配),而普通SLAB就是用于常规分配的时候。我们可以通过查看/proc/slabinfo看到slab的状态
对于kmalloc-8这些普通SLAB,都有一个对应的dma-kmalloc-8这种类型的普通SLAB,这种类型是专门使用了ZONE-DMA区域的内存,方便用于DMA模式申请内存。
在slab中,可分配的内存块叫做对象。不通的slab所包含对象的大小也不同。如kmalloc-8这个普通SLAB,里面所有的对象都是8B大小,同理,kmalloc-16中的对象都是以16B为大小。申请内存时就会依据这些对象来划分,这样做可以减小内存碎片化。其中申请的对象释放后也会回到他的slab中。
当对象拥有者释放一个对象后,SLAB的处理是仅仅标记对象为空闲,并不做多少处理,而又有申请者申请相应大小的对象时,SLAB会优先分配最近释放的对象,这样这个对象甚至有可能还在硬件高速缓存中,有点类似管理区页框分配器中每CPU高速缓存的做法。
kmem_cache(slab缓存)是slab分配器的最上层。kmem_cache结构用于描述一种SLAB(比如kmalloc-8,kmalloc-16等),并且管理着这种SLAB中所有的对象。所有的kmem_cache结构会保存在以slab_caches作为头的链表中。在内核模块中可以通过kmem_cache_create自行创建一个kmem_cache用于管理属于自己模块的SLAB。Kmalloc()就是基于slab来申请的。
Slab中有三种状态:满、部分满、空。当分配内存时会优先从部分满的slab中划分。分别通过三个链表来表示:
struct list_head slabs_full;
struct list_head slabs_partial;
struct list_head slabs_free;
二、 用户空间
(详情见《linux内存管理机制——用户空间》)
用户空间是0~3G的内存地址,主要用来存储低优先级的代码,组成如下