linux内存管理

1 原理

1.1 伙伴系统算法

在实际应用中,经常需要分配一组连续的页框,而频繁地申请和释放不同大小的连续页框,必然导致在已分配页框的内存块中分散了许多小块的空闲页框。这样,即使这些页框是空闲的,其他需要分配连续页框的应用也很难得到满足。
为了避免出现这种情况,Linux内核中引入了伙伴系统算法(buddy system)。把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块。最大可以申请1024个连续页框,对应4MB大小的连续内存。每个页框块的第一个页框的物理地址是该块大小的整数倍。
假设要申请一个256个页框的块,先从256个页框的链表中查找空闲块,如果没有,就去512个页框的链表中找,找到了则将页框块分为2个256个页框的块,一个分配给应用,另外一个移到256个页框的链表中。如果512个页框的链表中仍没有空闲块,继续向1024个页框的链表查找,如果仍然没有,则返回错误。
页框块在释放时,会主动将两个连续的页框块合并为一个较大的页框块。

1.2 slab分配器

slab分配器源于Solaris 2.4的分配算法,工作于物理内存页框分配器之上,管理特定大小对象的缓存,进行快速而高效的内存分配。


2 常见的内存分配函数

2.1 __get_free_pages

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

__get_free_pages函数是最原始的内存分配方式,直接从伙伴系统中获取原始页框,返回值为第一个页框的起始地址。__get_free_pages在实现上只是封装了alloc_pages函数,从代码分析,alloc_pages函数会分配长度为1 << order的连续页框块。order参数的最大值由include/linux/Mmzone.h文件中的MAX_ORDER宏决定,在默认的2.6.18内核版本中,该宏定义为10。也就是说在理论上__get_free_pages函数一次最多能申请1<<10 * 4KB也就是4MB的连续物理内存。但是在实际应用中,很可能因为不存在这么大量的连续空闲页框而导致分配失败。在测试中,order为10时分配成功,order为11则返回错误。

2.2 kmem_cache_alloc

struct kmem_cache *kmem_cache_create(const char *name, size_t size,
size_t align, unsigned long flags,
void (*ctor)(void*, struct kmem_cache *, unsigned long),
void (*dtor)(void*, struct kmem_cache *, unsigned long))
void *kmem_cache_alloc(struct kmem_cache *c, gfp_t flags)

kmem_cache_create/ kmem_cache_alloc是基于slab分配器的一种内存分配方式,适用于反复分配释放同一大小内存块的场合。首先用kmem_cache_create创建一个高速缓存区域,然后用kmem_cache_alloc从该高速缓存区域中获取新的内存块。 kmem_cache_alloc一次能分配的最大内存由mm/slab.c文件中的MAX_OBJ_ORDER宏定义,在默认的2.6.18内核版本中,该宏定义为5,于是一次最多能申请1<<5 * 4KB也就是128KB的连续物理内存。分析内核源码发现,kmem_cache_create函数的size参数大于128KB时会调用BUG()。测试结果验证了分析结果,用kmem_cache_create分配超过128KB的内存时使内核崩溃。

2.3 kmalloc

void *kmalloc(size_t size, gfp_t flags)

kmalloc是内核中最常用的一种内存分配方式,它通过调用kmem_cache_alloc函数来实现。kmalloc一次最多能申请的内存大小由include/linux/Kmalloc_size.h的内容来决定,在默认的2.6.18内核版本中,kmalloc一次最多能申请大小为131702B也就是128KB字节的连续物理内存。测试结果表明,如果试图用kmalloc函数分配大于128KB的内存,编译不能通过。

2.4 vmalloc

void *vmalloc(unsigned long size)

前面几种内存分配方式都是物理连续的,能保证较低的平均访问时间。但是在某些场合中,对内存区的请求不是很频繁,较高的内存访问时间也可以接受,这是就可以分配一段线性连续,物理不连续的地址,带来的好处是一次可以分配较大块的内存。图3-1表示的是vmalloc分配的内存使用的地址范围。vmalloc对一次能分配的内存大小没有明确限制。出于性能考虑,应谨慎使用vmalloc函数。在测试过程中,最大能一次分配1GB的空间。

2.5 dma_alloc_coherent

void *dma_alloc_coherent(struct device *dev, size_t size, ma_addr_t *dma_handle, gfp_t gfp)

DMA是一种硬件机制,允许外围设备和主存之间直接传输IO数据,而不需要CPU的参与,使用DMA机制能大幅提高与设备通信的吞吐量。DMA操作中,涉及到CPU高速缓存和对应的内存数据一致性的问题,必须保证两者的数据一致,在x86_64体系结构中,硬件已经很好的解决了这个问题, dma_alloc_coherent和__get_free_pages函数实现差别不大,前者实际是调用__alloc_pages函数来分配内存,因此一次分配内存的大小限制和后者一样。__get_free_pages分配的内存同样可以用于DMA操作。测试结果证明,dma_alloc_coherent函数一次能分配的最大内存也为4M。

2.6 ioremap

void * ioremap (unsigned long offset, unsigned long size)

ioremap是一种更直接的内存“分配”方式,使用时直接指定物理起始地址和需要分配内存的大小,然后将该段物理地址映射到内核地址空间。ioremap用到的物理地址空间都是事先确定的,和上面的几种内存分配方式并不太一样,并不是分配一段新的物理内存。ioremap多用于设备驱动,可以让CPU直接访问外部设备的IO空间。ioremap能映射的内存由原有的物理内存空间决定,所以没有进行测试。

2.7 boot memory

如果要分配大量的连续物理内存,上述的分配函数都不能满足,就只能用比较特殊的方式,在Linux内核引导阶段来预留部分内存。
2.7.1 在内核引导时分配内存
void* alloc_bootmem(unsigned long size)
可以在Linux内核引导过程中绕过伙伴系统来分配大块内存。使用方法是在Linux内核引导时,调用mem_init函数之前用alloc_bootmem函数申请指定大小的内存。如果需要在其他地方调用这块内存,可以将alloc_bootmem返回的内存首地址通过EXPORT_SYMBOL导出,然后就可以使用这块内存了。这种内存分配方式的缺点是,申请内存的代码必须在链接到内核中的代码里才能使用,因此必须重新编译内核,而且内存管理系统看不到这部分内存,需要用户自行管理。测试结果表明,重新编译内核后重启,能够访问引导时分配的内存块。

2.7.2 在内核引导参数预留顶部内存
在Linux内核引导时,传入参数“mem=size”保留顶部的内存区间。比如系统有256MB内存,参数“mem=248M”会预留顶部的8MB内存,进入系统后可以调用ioremap(0xF800000,0x800000)来申请这段内存。


3 几种分配函数的比较

这里写图片描述

以上部分摘自:http://my.oschina.net/zhangjie830621/blog/371932


4 linux内存管理框图

这里写图片描述
说明:
用户空间的malloc请求分配内存,返回的是虚拟地址,但此时还没有真正的分配物理页,只有当真正访问时才产生缺页异常,然后分配物理页。


5 内核虚拟地址与物理内存地址

进程地址空间为0~4G,内核虚拟地址空间是3~4G
内核3G~4G虚拟地址空间又分为:
ZONE_DMA 0~16MB
ZONE_NORMAL 16MB~896MB
ZONE_HIGHMEM 896MB~1G
其实上面是针对x86结构的pc,而对于一般的嵌入式系统而言,当很多接口需要使用DMA的时候,不必专门从ZONE_DMA区域分配内存,而只需从ZONE_NORMAL区域通过kmalloc分一段物理内存连续的区域就行了,所以嵌入式系统一般没有ZONE_DMA区域。
另外为什么需要ZONE_HIGHMEM呢,因为如果1G内核完全用来线性映射的话,显然只能访问到1G的物理内存,HIGHMEM就是为了解决这个问题,提供永久映射kmap和一段保留的映射用于临时映射kmap_atomic,如此这样不必线性映射可灵活定制的映射以便能访问1G以上的物理内存。

这里写图片描述
直接映射区 :
内核线性地址=物理地址+offset,offset是一个固定的偏移3G(0xc0000000)
kmalloc和get_free_pages分配的是这里的页面
永久映射区:
先通过alloc_pages(…, HIGHMEM)从ZONE_HIGHMEM分配struct page,然后将struct page通过kmap映射到内核虚拟地址的永久映射区,或者通过kmap_atomic映射到内核虚拟地址的临时映射区。
临时映射区:
当必须创建一个映射而当前的上下文又不能睡眠时,内核提供了临时映射。有一组保留的映射,他可以存放新创建的临时映射。内核可以原子的把高端内存中的一个页映射到某个保留的映射中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luckywang1103

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值