内存的申请和释放涉及了Linux的内存管理,内存管理是Linux相当复杂的部分,这里我们只简单介绍在内核中申请/释放内存的接口以及注意事项。
内存申请接口:
void *kmalloc(size_t size, gfp_t);
size要分配内存的大小,以字节为单位。
flags要分配内存的类型。
包括:
GFP_ATOMIC
用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠.
GFP_KERNEL
内核内存的正常分配. 可能睡眠.
GFP_USER
用来为用户空间页来分配内存; 它可能睡眠.
GFP_HIGHUSER
如同 GFP_USER, 但是从高端内存分配, 如果有. 高端内存在下一个子节描述.
GFP_NOIO
GFP_NOFS
这个标志功能如同 GFP_KERNEL, 但是它们增加限制到内核能做的来满足请求. 一个 GFP_NOFS 分配不允许进行任何文件系统调用, 而 GFP_NOIO 根本不允许任何 I/O 初始化. 它们主要地用在文件系统和虚拟内存代码, 那里允许一个分配睡眠, 但是递归的文件系统调用会是一个坏注意.
上面列出的这些分配标志可以是下列标志的相或来作为参数, 这些标志改变这些分配如何进行:
__GFP_DMA
这个标志要求分配在能够 DMA 的内存区. 确切的含义是平台依赖的并且在下面章节来解释.
__GFP_HIGHMEM
这个标志指示分配的内存可以位于高端内存.
__GFP_COLD
正常地, 内存分配器尽力返回"缓冲热"的页 -- 可能在处理器缓冲中找到的页. 相反, 这个标志请求一个"冷"页, 它在一段时间没被使用. 它对分配页作 DMA 读是有用的, 此时在处理器缓冲中出现是无用的. 一个完整的对如何分配 DMA 缓存的讨论看"直接内存存取"一节在第 1 章.
__GFP_NOWARN
这个很少用到的标志阻止内核来发出警告(使用 printk ), 当一个分配无法满足.
__GFP_HIGH
这个标志标识了一个高优先级请求, 它被允许来消耗甚至被内核保留给紧急状况的最后的内存页.
__GFP_REPEAT
__GFP_NOFAIL
__GFP_NORETRY
这些标志修改分配器如何动作, 当它有困难满足一个分配. __GFP_REPEAT 意思是" 更尽力些尝试" 通过重复尝试 -- 但是分配可能仍然失败. __GFP_NOFAIL 标志告诉分配器不要失败; 它尽最大努力来满足要求. 使用 __GFP_NOFAIL 是强烈不推荐的; 可能从不会有有效的理由在一个设备驱动中使用它. 最后, __GFP_NORETRY 告知分配器立即放弃如果得不到请求的内存.
……
kmalloc并不直接从分页机制中获得空闲页面而是从slab页面分配器那儿获得需要的页面,slab的实现代码限制了最大分配的大小,若分配的大小超过slab最大限制,则会调用页分配接口分配内存。
static inline void *kzalloc(size_t size, gfp_t flags)
同该函数调用kmalloc,只不过申请的空间会全部清0,相对浪费时间。
static inline void *kcalloc(size_t n, size_t size, gfp_t flags)
n:申请多少块单位内存空间。
size:每块内存的大小,字节为单位。
flags要分配内存的类型。
void *vmalloc(unsigned long size); //使用vfree释放内存
vmalloc()和kmalloc不同在于前者分配的内存虚拟地址位于vmalloc区的内核动态映射空间,是连续的,而物理地址则无需连续。
内存释放接口:
void kfree(void *ptr);
void vfree(void *ptr);
直接申请页:
struct page * alloc_pages(gft_t gtf_mask,unsigned int order); //申请2^order个页
void * page_address(struct page * page); // 将在非__GFP_HIGHMEM标志申请的页转为线性地址
unsigned long __get_free_pages(gft_t gtf_mask,unsigned int order); //申请2^order个页,并直接返回逻辑地址的长整形数
#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0) //申请1个页
#define __get_free_page(gfp_mask) \
__get_free_pages((gfp_mask),0) //申请1个页,并直接返回线性地址的长整形数
unsigned long get_zeroed_page(unsigned int gtf_mask); //申请1个页填充为0,并直接返回逻辑地址的长整形数
释放页:
void __free_pages(struct page *page,unsigned int order) //page申请的页,order释放的页的个数
void free_pages(unsigned long addr,unsigned int order) //addr为虚拟地址,order释放的页的个数
#define free_page(addr) free_pages((addr),0)
根据定义32位系统上,在高端内存中的页不能永久地映射到内核地址空间上。因此,通过alloc_pages()函数以__GFP_HIGHMEM标志获得的页不可能有逻辑地址。一旦这些页被分配,就必须映射到内核的逻辑地址空间上。要映射一个给定的page结构到内核地址空间,可以使用void *kmap(struct page *page) ,需要#include
当必须创建一个映射而当前的上下文又不能睡眠时,内核提供了临时睡眠(也就是原子睡眠)。只要有一组保留的永久映射,它们就可以临时持有新创建的一个映射。内核可以原子地把高端内存中的一个页映射到某个保留的映射中。因此,临时映射可以用在不能睡眠的地方。建立临时映射:void *kmap_atomic(struct page *page).。这个函数不会阻塞,它也禁止内核抢占
在64位系统上没有高端内存区的概念,地址都在线性映射区,可以直接通过page_address(struct page * page); 将申请的页转为线性地址。
kmalloc()接口建立在slab层上,就是使用了一组Linux内置通用高速缓存,我们也可以自己创建自己的高速缓存使用。
每个高速缓存都使用kmem_cache 结构来表示,这个结构包三个链表:slabs_full、slabs_parti、slabs_empty,均放在kmem_list3结构内,该结构在mm/slab.c中定义。
struct kmem_cache * kmem_cache_create(const char *name, size_t size,size_t align, unsigned long flags,void (*ctor)(void *));
name:高速缓存的名字。
size:高速缓存中每个元素的大小。
align:slab内第一个对象的偏移,用来保证页内进行特定字节的对齐,通常情况下为0,代表标准对齐。
flags:用来控制高速缓存的行为。
#define SLAB_DEBUG_FREE 0x00000100UL /* DEBUG: Perform (expensive) checks on free */
#define SLAB_RED_ZONE 0x00000400UL /* DEBUG: Red zone objs in a cache */
#define SLAB_POISON 0x00000800UL /* DEBUG: Poison objects */
#define SLAB_HWCACHE_ALIGN 0x00002000UL /* Align objs on cache lines */
#define SLAB_CACHE_DMA 0x00004000UL /* Use GFP_DMA memory */
#define SLAB_STORE_USER 0x00010000UL /* DEBUG: Store the last owner for bug hunting */
#define SLAB_PANIC 0x00040000UL /* Panic if kmem_cache_create() fails */
#define SLAB_DESTROY_BY_RCU 0x00080000UL /* Defer freeing slabs to RCU */
#define SLAB_MEM_SPREAD 0x00100000UL /* Spread some memory over cpuset */
#define SLAB_TRACE 0x00200000UL /* Trace allocations and frees */
ctor:为高速缓存的构造函数,只有新的页追加到高速缓存时才被调用。
这个函数成功时会返回一个执行所创建高速缓存的指针,否则,返回空。这个函数由于会睡眠,因此不能在中断上下文中使用。
要销毁一个高速缓存,调用:
int kmem_cache_destroy(kmem_cache *cachep),
同样,也是不能在中断上下文中使用。调用该函数之前必须确保存在以下两个条件
1.高速缓存中的所有slab都必须为空。
2.在调用kmem_cache_destory()期间不再访问这个高速缓存,调用者必须确保这种同步。
创建了高速缓存以后,就可以通过下列函数从中获取对象:
void * kmem_cache_alloc(struct kmem_cache *cachep, int flags)。
该函数从高速缓存cachep中返回一个指向对象的指针。如果高速缓存的所有slab中都没有空闲的对象,那么slab层必须通过kmem_getpages()获取新的页,flags的值传递给__get_free_pages().
最后,释放一个对象,并把它返回给原来的slab,可以使用下面的函数:
void kmem_cache_free(struct kmem_cache *cachep,void *objp)
这样就能把cachep中的对象objp标记为空闲了,关于slab分配器的使用实例,参考资料上有,我就不说了。
用户/内核 |
API名称 |
物理连续? |
大小限制 |
单位 |
场景 |
|
用户空间 |
malloc/calloc/realloc/free |
不保证 |
堆申请 |
字节 |
calloc初始化为0;realloc改变内存大小。 |
|
alloca |
栈申请 |
字节 |
向栈申请内存 |
|||
mmap/munmap |
将文件利用虚拟内存技术映射到内存中去。 |
|||||
brk、sbrk |
虚拟内存到内存的映射。sbrk(0)返回program break地址,sbrk调整对的大小。 |
|||||
内 核 空 间 |
vmalloc/vfree |
虚拟连续 物理不定 |
vmalloc区大小限制 |
页 VMALLOC区域 |
可能睡眠,不能从中断上下文中调用,或其他不允许阻塞情况下调用。 VMALLOC区域vmalloc_start~vmalloc_end之间,vmalloc比kmalloc慢,适用于分配大内存。 |
|
slab |
kmalloc/kcalloc/krealloc/kzmalloc/kfree |
物理连续 |
64B-4MB (随slab而变) |
2^order字节 Normal区域 |
大小有限,不如vmalloc/malloc大。 最大/小值由KMALLOC_MIN_SIZE/KMALLOC_SHIFT_MAX,对应64B/4MB。 从/proc/slabinfo中的kmalloc-xxxx中分配,建立在kmem_cache_create基础之上。 |
|
kmem_cache_create |
物理连续 |
64B-4MB |
字节大小,需对齐 Normal区域 |
便于固定大小数据的频繁分配和释放,分配时从缓存池中获取地址,释放时也不一定真正释放内存。通过slab进行管理。 |
||
伙伴系统 |
__get_free_page/__get_free_pages |
物理连续 |
4MB(1024页) |
页 Normal区域 |
__get_free_pages基于alloc_pages,但是限定不能使用HIGHMEM。 |
|
alloc_page/alloc_pages/free_pages |
物理连续 |
4MB |
页 Normal/Vmalloc都可 |
CONFIG_FORCE_MAX_ZONEORDER定义了最大页面数2^11,一次能分配到的最大页面数是1024。 |