Linux内存管理机制——内核空间

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的内存地址,主要用来存储低优先级的代码,组成如下

 

你可能感兴趣的:(内存管理)