首頁 >運維 >linux運維 >linux記憶體管理相關的幾個函數

linux記憶體管理相關的幾個函數

青灯夜游
青灯夜游原創
2023-04-10 16:55:021338瀏覽

linux記憶體管理相關的函數:1、kmalloc(),用於內核態的記憶體分配;2、vmalloc(),一般用在為只存在於軟體中(沒有對應的硬體意義)的較大的順序緩衝區分配記憶體;3、alloc_page()和alloc_pages()函數,可以在核心空間分配;4、__get_free_pages()系列函數,傳回一個或多個頁面的虛擬位址;5、kmem_cache_alloc()等。

linux記憶體管理相關的幾個函數

本教學操作環境:linux7.3系統、Dell G3電腦。

本文敘述了Linux核心中常見的幾種記憶體分配函數及其異同,對理解linux底層記憶體分配機制有個較好理解。

1、kmalloc()

kmalloc()函數類似與我們常見的malloc()函數,前者用於內核態的內存分配,後者用於用戶態。
kmalloc()函數在物理記憶體中分配一塊連續的儲存空間,且和malloc()函數一樣,不會清除裡面的原始數據,如果記憶體充足,它的分配速度很快。其原型如下:

static inline void *kmalloc(size_t size, gfp_t flags);	/*返回的是虚拟地址*/
  • size:待分配的記憶體大小。由於Linux記憶體管理機制的原因,記憶體只能依照頁面大小(一般32位元機為4KB,64位元機為8KB)進行分配,這樣就導致了當我們只需要幾個位元組記憶體時,系統仍會傳回一個頁面的內存,顯然這是極度浪費的。所以,有別於malloc的是,kmalloc的處理方式是:核心先為其分配一系列不同大小(32B、64B、128B、… 、128KB)的記憶體池,當需要分配記憶體時,系統會分配大於等於所需記憶體的最小一個記憶體池給它。即kmalloc分配的內存,最小為32字節,最大為128KB。如果超過128KB,需要採樣其它記憶體分配函數,例如vmalloc()。
  • flag:此參數用於控制函數的行為,最常用的是GFP_KERNEL,表示噹噹前沒有足夠記憶體分配時,進程進入睡眠,待系統將緩衝區中的內容SWAP到硬碟後,獲得足夠記憶體後再喚醒進程,為其分配。更多標誌請見下圖:
    linux記憶體管理相關的幾個函數
  • 使用GFP_ KERNEL 標誌申請記憶體時,若暫時無法滿足,則行程會睡眠等待頁,即會造成阻塞,因此無法在中斷上下文或持有自旋鎖的時候使用GFP_KERNE 申請記憶體。所以,在中斷處理函數、tasklet 和內核定時器等非進程上下文中不能阻塞,此時驅動程式應使用 GFP_ATOMIC 標誌來申請記憶體。當使用 GFP_ATOMIC 標誌申請記憶體時,若不存在空閒頁,則不等待,請直接返回。
  • 除了上述表格所列標誌外,還包括如下
  • _ _GFP_DMA(要求分配在能夠DMA 的記憶體區)
  • _ _GFP_HIGHMEM (指示分配的記憶體可以位於高階記憶體)
  • _ _GFP_COLD(請求一個較長時間不存取的頁)
  • _ _GFP_NOWARN(當一個分配無法滿足時,阻止核心發出警告)
  • _ _GFP_HIGH(高優先級請求,允許獲得被核心保留給緊急狀況使用的最後的記憶體頁面)
  • _ _GFP_REPEAT(分配失敗則盡力重複嘗試)
  • _ _GFP_NOFAIL(標誌只許申請成功,不建議)
  • _ _GFP_NORETRY(若申請不到,則立即放棄)
  • 使用kmalloc()申請的記憶體應使用kfree ()釋放,這個函數的用法和使用者空間的free()類似。

2、vmalloc()

#vmalloc()一般用在為只存在於軟體中(沒有對應的硬體意義)的較大的順序緩衝區分配內存,當內存沒有足夠大的連續物理空間可以分配時,可以用該函數來分配虛擬地址連續但物理地址不連續的內存。由於需要建立新的頁表,所以它的開銷要遠大於kmalloc及後面將要講到的__get_free_pages()函數。且vmalloc()不能用在原子上下文中,因為它的內部實作使用了標誌為 GFP_KERNEL 的kmalloc()。其函數原型如下:

void *vmalloc(unsigned long size);
void vfree(const void *addr);
  • 使用 vmalloc 函数的一个例子函数是 create_module()系统调用,它利用 vmalloc()函数来获取被创建模块需要的内存空间。
  • 内存分配是一项要求严格的任务,无论什么时候,都应该对返回值进行检测。
  • 在驱动编程中可以使用copy_from_user()对内存进行使用。下面举一个使用vmalloc函数的示例:
static int xxx(...)
{
	...
	cpuid_entries = vmalloc(sizeof(struct kvm_cpuid_entry) * cpuid->nent);
	if(!cpuid_entries)
	goto out;
	if(copy_from_user(cpuid_entries, entries, cpuid->nent * sizeof(struct kvm_cpuid_entry)))
		goto out_free;
	for(i=0; i<cpuid->nent; i++){
		vcpuid->arch.cpuid_entries[i].eax = cpuid_entries[i].eax;
		...
		vcpuid->arch.cpuid_entries[i].index = 0;
	}
	...
out_free:
	vfree(cpuid_entries);
out:
	return r;
}

3、页分配函数

在linux中,内存分配是以页为单位的,32位机中一页为4KB,64位机中,一页为8KB,但具体还有根据平台而定。
根据返回值类型的不同,页分配函数分为两类,一是返回物理页地址,二是返回虚拟地址。虚拟地址和物理地址起始相差一个固定的偏移量。

#define __pa(x) ((x) - PAGE_OFFSET)
static inline unsigned long virt_to_phys(volatile void *address)
{
	return __pa((void *)address);
}

#define __va(x) ((x) + PAGE_OFFSET)
static inline  void* phys_to_virt(unsigned long address)
{
	return __va(address);
}

linux記憶體管理相關的幾個函數

根据返回页面数目分类,分为仅返回单页面的函数和返回多页面的函数。

3.1 alloc_page()和alloc_pages()函数

该函数定义在头文件/include/linux/gfp.h中,它既可以在内核空间分配,也可以在用户空间分配,它返回分配的第一个页的描述符而非首地址,其原型为:

#define alloc_page(gfp_mask)  alloc_pages(gfp_mask, 0)
#define alloc_pages(gfp_mask, order) alloc_pages_node(numa_node_id(), gfp_mask, order)  //分配连续2^order个页面
static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask, unsigned int order) 
{
	if(unlikely(order >= MAX_ORDER))
		return NULL;
	if(nid < 0)
		nid = numa_node_id();
	return __alloc_pages(gfp_mask, order, noed_zonelist(nid, gfp_mask));
}

3.2 __get_free_pages()系列函数

它是kmalloc函数实现的基础,返回一个或多个页面的虚拟地址。该系列函数/宏包括 get_zeroed_page()_ _get_free_page()_ _get_free_pages()。在使用时,其申请标志的值及含义与 kmalloc()完全一样,最常用的是 GFP_KERNEL 和 GFP_ATOMIC。

/*分配多个页并返回分配内存的首地址,分配的页数为2^order,分配的页不清零。
order 允许的最大值是 10(即 1024 页)或者 11(即 2048 页),依赖于具体
的硬件平台。*/
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
{
	struct page *page;
	page = alloc_pages(gfp_mask, order);
	if(!page)
		return 0;
	return (unsigned long)page_address(page);
}

#define __get_free_page(gfp_mask)  __get_free_pages(gfp_mask, 0)

/*该函数返回一个指向新页的指针并且将该页清零*/
unsigned long get_zeroed_page(unsigned int flags);
  • 使用_ _get_free_pages()系列函数/宏申请的内存应使用free_page(addr)free_pages(addr, order)函数释放:
#define __free_page(page) __free_pages((page), 0)
#define free_page(addr) free_pages((addr), 0)

void free_pages(unsigned long addr, unsigned int order)
{
	if(addr != 0){
		VM_BUG_ON(!virt_addr_valid((void*)addr));
		__free_pages(virt_to_page((void *)addr), order);
	}
}

void __free_pages(struct page *page, unsigned int order)
{
	if(put_page_testzero(page)){
		if(order == 0)
			free_hot_page(page);
		else
			__free_pages_ok(page, order);
	}
}

free_pages()函数是调用__free_pages()函数完成内存释放的。

4、slab缓存

  • 当在驱动程序中,遇到反复分配、释放同一大小的内存块时(例如,inode、task_struct等),建议使用内存池技术(对象在前后两次被使用时均分配在同一块内存或同一类内存空间,且保留了基本的数据结构,这大大提高了效率)。在linux中,有一个叫做slab分配器的内存池管理技术,内存池使用的内存区叫做后备高速缓存。
  • salb相关头文件在linux/slab.h中,在使用后备高速缓存前,需要创建一个kmem_cache的结构体。

4.1 创建slab缓存区

该函数创建一个slab缓存(后备高速缓冲区),它是一个可以驻留任意数目全部同样大小的后备缓存。其原型如下:

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 ong)));

其中:
name:创建的缓存名;
size:可容纳的缓存块个数;
align:后备高速缓冲区中第一个内存块的偏移量(一般置为0);
flags:控制如何进行分配的位掩码,包括 SLAB_NO_REAP(即使内存紧缺也不自动收缩这块缓存)、SLAB_HWCACHE_ALIGN ( 每 个 数 据 对 象 被 对 齐 到 一 个 缓 存 行 )、SLAB_CACHE_DMA(要求数据对象在 DMA 内存区分配)等);
ctor:是可选的内存块对象构造函数(初始化函数);
dtor:是可选的内存对象块析构函数(释放函数)。

4.2 分配slab缓存函数

一旦创建完后备高速缓冲区后,就可以调用kmem_cache_alloc()在缓存区分配一个内存块对象了,其原型如下:

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

cachep指向开始分配的后备高速缓存,flags与传给kmalloc函数的参数相同,一般为GFP_KERNEL。

4.3 释放slab缓存

该函数释放一个内存块对象:

void *kmem_cache_free(struct kmem_cache *cachep, void *objp);

4.4 销毁slab缓存

kmem_cache_create对应的是销毁函数,释放一个后备高速缓存:

int kmem_cache_destroy(struct kmem_cache *cachep);

它必须等待所有已经分配的内存块对象被释放后才能释放后备高速缓存区。

4.5 slab缓存使用举例

创建一个存放线程结构体(struct thread_info)的后备高速缓存,因为在linux中涉及频繁的线程创建与释放,如果使用__get_free_page()函数会造成内存的大量浪费,效率也不高。所以在linux内核的初始化阶段就创建了一个名为thread_info的后备高速缓存,代码如下:

/* 创建slab缓存 */
static struct kmem_cache *thread_info_cache;
thread_info_cache = kmem_cache_create("thread_info", sizeof(struct thread_info), \
										SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);

/* 分配slab缓存 */
struct thread_info *ti;
ti = kmem_cache_alloc(thread_info_cache, GFP_KERNEL);

/* 使用slab缓存 */
...
/* 释放slab缓存 */
kmem_cache_free(thread_info_cache, ti);
kmem_cache_destroy(thread_info_cache);

5、内存池

在 Linux 内核中还包含对内存池的支持,内存池技术也是一种非常经典的用于分配大量小对象的后备缓存技术。

5.1 创建内存池

mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn, \
 							mempool_free_t *free_fn, void *pool_data);

mempool_create()函数用于创建一个内存池,min_nr 参数是需要预分配对象的数目,alloc_fn 和 free_fn 是指向内存池机制提供的标准对象分配和回收函数的指针,其原型分别为:

typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data); 

typedef void (mempool_free_t)(void *element, void *pool_data);

pool_data 是分配和回收函数用到的指针,gfp_mask 是分配标记。只有当_ _GFP_WAIT 标记被指定时,分配函数才会休眠。

5.2 分配和回收对象

在内存池中分配和回收对象需由以下函数来完成:

void *mempool_alloc(mempool_t *pool, int gfp_mask); 
void mempool_free(void *element, mempool_t *pool);

mempool_alloc()用来分配对象,如果内存池分配器无法提供内存,那么就可以用预分配的池。

5.3 销毁内存池

void mempool_destroy(mempool_t *pool);

mempool_create()函数创建的内存池需由 mempool_destroy()来回收。

相关推荐:《Linux视频教程

以上是linux記憶體管理相關的幾個函數的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn