Linux内核学习笔记九——内核内存管理方式

一 页

       内核把物理页作为内存管理的基本单位;内存管理单元(MMU)把虚拟地址转换为物理


地址,通常以页为单位进行处理。MMU以页大小为单位来管理系统中的也表。


       32位系统:页大小4KB


       64位系统:页大小8KB


内核用相应的数据结构表示系统中的每个物理页:


  <linux/mm_types.h>


  struct page {}


内核通过这样的数据结构管理系统中所有的页,因此内核判断一个页是否空闲,谁有拥有这个页


,拥有者可能是:用户空间进程、动态分配的内核数据、静态内核代码、页高速缓存……


系统中每一个物理页都要分配这样一个结构体,进行内存管理。


二 区

       Linux内存寻址存在问题:


一些硬件只能用某些特定的内存来执行DMA(直接内存访问)


一些体系结构其内存的物理寻址范围必须你寻址范围大得多。这样导致一些内存不能永久映射到内核空间上。


       通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。当内核模块代码或线程访问内存时,


代码中的内存地址都为逻辑地址,而对应到真正的物理内存地址,需要地址一对一的映射。因此内核空间地址为3~4G,


最多只能映射到1G空间的内存,超出1G大小的内存将如何去问呢!


       由于存在上述条件的限制。Linux将内核空间地址划分为三个区:


ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。



       ZONE_HIGHMEM即为高端内存,这就是内存高端内存概念的由来。


在x86结构中,三种类型的区域如下:


  ZONE_DMA        内存开始的16MB


  ZONE_NORMAL       16MB~896MB


  ZONE_HIGHMEM       896MB ~ 结束


同样每个区包含众多页,形成不同内存池,按照用途进行内存分配。


用相应的数据结构来表示区:


  <linux/mmzone.h>


  struct zone {}


三 获取页/内存

static inline struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)


该函数分配2的order次方个连续的物理页,返回指向第一个页的page结构体指针。


void *page_address(const struct page *page)


返回指向给定物理页当前所在的逻辑地址


extern unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);


extern unsigned long get_zeroed_page(gfp_t gfp_mask);


释放:


extern void __free_pages(struct page *page, unsigned int order);


extern void free_pages(unsigned long addr, unsigned int order);


 


内存的分配可能失败,内存的释放要准确!


1 kmalloc


kmalloc()函数与用户空间malloc一组函数类似,获得以字节为单位的一块内核内存。


void *kmalloc(size_t size, gfp_t flags)


void kfree(const void *objp)


分配内存物理上连续。


gfp_t标志:表明分配内存的方式。如:


GFP_ATOMIC:分配内存优先级高,不会睡眠


GFP_KERNEL:常用的方式,可能会阻塞。



2 vmalloc    


void *vmalloc(unsigned long size)


void vfree(const void *addr)


vmalloc()与kmalloc方式类似,vmalloc分配的 内存虚拟地址是连续的,而物理地址则无需连续,与用户空间分配函数一致。


vmalloc通过分配非连续的物理内存块,在修正页表,把内存映射到逻辑地址空间的连续区域中,虚拟地址是连续的。


       是否必须要连续的物理地址和具体使用场景有关。在不理解虚拟地址的硬件设备中,内存区都必须是连续的。


       通过建立页表转换成虚拟地址空间上连续,肯定存在一些消耗,带来性能上影响。


所以通常内核使用kmalloc来申请内存,在需要大块内存时使用vmalloc来分配。



四 slab层

       内核中经常进行内存的分配和释放。为了便于数据的频繁分配和回收,通常建立一个空
闲链表——内存池。当不使用的已分配的内存时,将其放入内存池中,而不是直接释放掉。


       Linux内核提供了slab层来管理内存的分配和释放。


频繁分配和回收必然导致内存碎片,缓存他们.


slab层得设计实现


       slab层把不同的对象划分为所谓的高速缓存组。每个高速缓存组存放不同类型的对象。高速缓存划分为slab,


slab由一个或多个物理上连续的页组成。每个slab处于三种状态之一:满,部分满,空。


高速缓存,slab,对象之间的关系:


     


 


 


       与传统的内存管理模式相比, slab 缓存分配器提供了很多优点。首先,内核通常依赖于对小对象的分配,


它们会在系统生命周期内进行无数次分配。slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能,


从而避免了常见的碎片问题。slab 分配器还支持通用对象的初始化,从而避免了为同一目而对一个对象重复


进行初始化。最后,slab 分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,


从而提高缓存的利用率并获得更好的性能。


 


slab数据结构和接口:


每个高速缓存用kmem_cache结构来表示:


       struct kmem_cache {


              struct kmem_list3 **nodelists;


              ……


       }


缓存区包含三种slab:满,未满,空闲


struct kmem_list3 {


       struct list_head slabs_partial; /* partial list first, better asm code */


       struct list_head slabs_full;


       struct list_head slabs_free;


       ……


};


每一个slab包含多个对象:


struct slab {


              struct list_head list;


              unsigned long colouroff;


              void *s_mem;            /* including colour offset */


              unsigned int inuse;     /* num of objs active in slab */


              kmem_bufctl_t free;


              unsigned short nodeid;


};


 


相关接口:mm/slab.c


              内核函数 kmem_cache_create 用来创建一个新缓存。这通常是在内核初始化时执行的,或者在 首次加载内核模块时执行。


struct kmem_cache *kmem_cache_create (


  const char *name,


  size_t size,


  size_t align,


  unsigned long flags,


  void (*ctor)(void *))


      


name 参数定义了缓存名称,proc 文件系统(在 /proc/slabinfo 中)使用它标识这个缓存。


size 参数指定了为这个缓存创建的对象的大小,


align 参数定义了每个对象必需的对齐。


flags 参数指定了为缓存启用的选项:


  kmem_cache_create 的部分选项(在 flags 参数中指定)


  SLAB_RED_ZONE    在对象头、尾插入标志,用来支持对缓冲区溢出的检查。


  SLAB_POISON  使用一种己知模式填充 slab,允许对缓存中的对象进行监视(对象属对象所有,不过可以在外部进行修改)。


  SLAB_HWCACHE_ALIGN      指定缓存对象必须与硬件缓存行对齐。


ctor 和 dtor 参数定义了一个可选的对象构造器和析构器。构造器和析构器是用户提供的回调函数。当从缓存中分配新对象时,可以通过构造器进行初始化。


    要从一个命名的缓存中分配一个对象,可以使用 kmem_cache_alloc 函数。


 


void kmem_cache_alloc( struct kmem_cache *cachep, gfp_t flags );


这个函数从缓存中返回一个对象。注意如果缓存目前为空,那么这个函数就会调用 cache_alloc_refill 向缓存中增加内存。


kmem_cache_alloc 的 flags 选项与 kmalloc 的


cachep:所建立的缓存区


flags参数:


  GFP_USER 为用户分配内存(这个调用可能会睡眠)。


  GFP_KERNEL    从内核 RAM 中分配内存(这个调用可能会睡眠)。


  GFP_ATOMIC   使该调用强制处于非睡眠状态(对中断处理程序非常有用)。


  GFP_HIGHUSER      从高端内存中分配内存。


 


五 高端内存的映射

永久映射:可能会阻塞


  映射一个给定的page结构到内核地址空间:


  void *kmap(struct page *page)


  解除映射:


  void kunmap(struct page *page)


 


临时映射:不会阻塞     


void *kmap_atomic(struct page *page)


 


六 分配函数的选择

  l  连续的物理页:kmalloc或者低级页分配器


  l  高端内存分配:alloc_pages 指向page结构指针,不是逻辑地址指针。再通过kmap()把高端地址内存映射到内核的逻辑地址空间。


  l  无需连续物理地址:vmalloc 虚拟地址连续物理地址可能不连续,相对存在性能损失


  l  频繁创建和销毁很多较大数据结构:建立slab缓存区,提高对象分配和回收性能。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值