ホームページ  >  記事  >  運用・保守  >  Linuxのメモリ管理に関連するいくつかの関数

Linuxのメモリ管理に関連するいくつかの関数

青灯夜游
青灯夜游オリジナル
2023-04-10 16:55:021284ブラウズ

Linux メモリ管理関連関数: 1. kmalloc()、カーネル モードでのメモリ割り当てに使用; 2. vmalloc()、一般にソフトウェア内にのみ存在する関数に使用 (対応するハードウェアの意味はない) より大きなシーケンシャル バッファメモリの割り当て; 3. alloc_page() および alloc_pages() 関数はカーネル空間に割り当てることができます; 4. __get_free_pages() 一連の関数は 1 つ以上のページの仮想アドレスを返します; 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)。その結果、数バイトだけが必要な場合でもシステムが返されることになります。メモリ 1 ページのメモリは明らかに非常に無駄です。したがって、malloc とは異なり、kmalloc の処理方法は次のとおりです: カーネルはまず、異なるサイズ (32B、64B、128B、...、128KB) の一連のメモリ プールを割り当てます。メモリを割り当てる必要がある場合、システムはより大きなメモリ プールを割り当てます。以上 メモリを必要とする最小のメモリ プールを指定します。つまり、kmalloc によって割り当てられるメモリの最小値は 32 バイト、最大値は 128KB です。 128KB を超える場合は、vmalloc() などの他のメモリ割り当て関数をサンプリングする必要があります。
  • flag: このパラメータは、関数の動作を制御するために使用されます。最も一般的に使用されるパラメータは GFP_KERNEL です。これは、現在十分なメモリが割り当てられていない場合、プロセスが次の処理に進むことを意味します。スリープし、システムがバッファの内容を保存するのを待ちます。ハード ディスクに SWAP した後、十分なメモリを取得し、プロセスを起動してメモリを割り当てます。その他のフラグについては、以下の図を参照してください。
    Linuxのメモリ管理に関連するいくつかの関数
  • GFP_ KERNEL フラグを使用してメモリを適用する場合、一時的に満たせない場合、プロセスはスリープしてページを待ちます。ブロッキングの原因となるため、割り込みコンテキストで使用することはできません。または、スピン ロックを保持するときに GFP_KERNE を使用してメモリを適用することもできません。したがって、割り込みハンドラー、タスクレット、カーネル タイマーなどの非プロセス コンテキストではブロックできません。この場合、ドライバーは GFP_ATOMIC フラグを使用してメモリを適用する必要があります。 GFP_ATOMICフラグを使用してメモリを申請した場合、空きページがない場合は待たずに直接リターンします。
  • 上記の表にリストされているフラグに加えて、次の も含まれます:
  • _ _GFP_DMA (DMA 可能なメモリ領域に割り当てられる必要があります)
  • _ _GFP_HIGHMEM (割り当てられたメモリがハイメモリに配置できることを示します)
  • _ _GFP_COLD (長期間アクセスされていないページを要求します)
  • _ _GFP_NOWARN (割り当てが満たされない場合にカーネルが警告を発行しないようにします)
  • _ _GFP_HIGH (優先度の高い要求。緊急用にカーネルによって予約された最後のメモリ ページを取得できます)
  • _ _GFP_REPEAT (割り当てが失敗した場合はベストエフォートで再試行)
  • _ _GFP_NOFAIL (マークはアプリケーションの成功のみを許可します。推奨されません)
  • _ _GFP_NORETRY (アプリケーションを取得できない場合は、すぐに諦めます)
  • kmalloc() を使用するために適用されるメモリは、kfree ()Release を使用する必要があります。この関数の使用法は、ユーザー空間での 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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。