linux 内核内存分配和释放

内存的申请和释放涉及了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 这个函数在高端内存或低端内存上都能用。如果page结构对应的是低端内存中的一页,函数只会单纯地返回该页的虚拟地址,如果页位于高端内存,则会建立一个永久映射,再返回地址。这个函数可以睡眠,所以kmap()只能用在进程上下文中。当不再需要内存映射的时候,就用下列函数进行解除映射:void kunmap(struct page* page)。
当必须创建一个映射而当前的上下文又不能睡眠时,内核提供了临时睡眠(也就是原子睡眠)。只要有一组保留的永久映射,它们就可以临时持有新创建的一个映射。内核可以原子地把高端内存中的一个页映射到某个保留的映射中。因此,临时映射可以用在不能睡眠的地方。建立临时映射: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分配器的使用实例,参考资料上有,我就不说了。

Linux中常用内存分配函数的异同点

用户/内核

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。

你可能感兴趣的:(linux,linux,c语言)