首页 > 代码库 > 内存管理

内存管理

一、内存管理的相关概念
内核把物理页作为内存管理的基本单位。
内核用struct page结构表示系统中的每个物理页。
内核使用区对具有相似特性的页进行分组。
内核把页划分为不同的区,主要使用了四种区:ZONE_DMA、ZONE_DMA32、ZONE_NORMAL、ZONE_HIGHEM。

二、获得页的接口

struct page * alloc_pages(gfp_t gfp_mask, unsigned int order);
//该函数分配2的order次方即1<<order个连续的物理页;
//并返回一个指针,该指针指向第一个页的page结构体;
//如果出错,就返回NULL。

void * page_address(struct page * page);
//该函数返回一个指针,指向给定物理页当前所在的逻辑地址。

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
//这个函数与alloc_pages()作用相同,不过它直接返回所请求的第一个页的逻辑地址。

struct page * alloc_page(gfp_t gfp_mask);
unsigned long __get_free_page(gfp_mask);
//这两个函数同上面两个函数工作方式相同,只不过传递给order的值为0,即分配一页。

unsigned long get_zeroed_page(unsigned int gfp_mask);
//只分配一页,让其内容填充0,返回指向其逻辑地址的指针。

void __free_pages(struct page * page, unsigned int order);
void __free_page(struct page * page);
void free_pages(unsigned long addr, unsigned int order);
void free_page(unsigned long addr);
//释放分配的页

三、kmalloc()

kmalloc()在<linux/slab.h>中声明。

void * kmalloc(size_t size, gfp_t flags);
//这个函数返回一个指向内存块的指针,其内存块至少要有size大小;
//所分配的内存区在物理上是连续的;
//出错时返回NULL

void kfree(const void * ptr);
//kfree()函数释放由kmalloc()分配出来的内存块。

gfp_mask标志
这些标志可以分为三类:行为修饰符、区修饰符、类型。

行为修饰符表示内核应当如何分配所需的内存。
//例如,中断处理程序就要求内核在分配内存的过程中不能睡眠。
__GFP_WAIT、__GFP_HIGH、__GFP_IO、__GFP_FS、__GFP_COLD、__GFP_NOWARN、
__GFP_REPEAT、__GFP_NOFALL、__GFP_NORETRY、__GFP_NO_GROW、__GFP_COMP

区修饰符表示内存区应当从何处分配。
__GFP_DMA、__GFP_DMA32、__GFP_HIGHMEM

类型标志指定所需的行为和区描述符以完成特殊类型的处理。
内核代码趋向于使用正确的类型标志,而不是一味地指定它可能需要用到的多个描述符。
GFP_ATOMIC、GFP_NOWAIT、GFP_NOIO、GFP_NOFS、GFP_KERNEL、GFP_USER、GFP_HIGHUSER、GFP_DMA

四、vmalloc()
vmalloc()函数声明在<linux/vmalloc.h>中,定义在<mm/vmalloc.c>中。
用法与用户空间的malloc()相同。

注意:vmalloc()函数的工作方式类似于kmalloc(),只不过前者分配的内存虚拟地址是连续的,而物理地址则无需连续。

void * vmalloc(unsigned long size);
//返回一个指针,指向逻辑上连续的一块内存区,其大小至少为size。
//函数可能睡眠

void free(const void *addr);
//释放通过vmalloc()函数分配的内存

五、slab层

slab分配器扮演了通用数据结构缓冲层的角色。
slab层把不同的对象划分为所谓高速缓冲组,其中每个高速缓冲都存放不同类型的对象。
slab由一个或多个物理上连续的页组成。一般情况下仅由一页组成。
每个slab处于三种状态之一:满、部分满、空。

每种对象类型对应一个高速缓存;
每个高速缓存可以由多个slab组成;
每个slab都包含一些对象成员,这里的对象指的是被缓存的数据结构。

技术分享

 

高速缓存使用struct kmem_cache结构表示;
slab描述符struct slab用来描述每个slab。

接口函数:
struct kmem_cache * kmem_cache_create(const char * name, size_t size, size_t align,
				unsigned long flags, void (*ctor) (void *));
				//第一个参数是一个字符串,存放着高速缓存的名字;
				//第二个参数是高速缓存中每个元素的大小;
				//第三个参数是slab内第一个对象的偏移,它用来确保在页内进行特定的对齐。通常0就可以满足。
				//第四个参数是可选的设置项,用来控制高速缓存的行为。可以为0,表示没有特殊的行为。
								//或者以下标志中的一个或多个进行“或”运算:
								//SLAB_HWCACHE_ALIGN			把一个slab内的所有对象按高速缓存行对齐。
								//SLAB_POISON				用已知的值(a5a5a5a5)填充slab。
								//SLAB_RED_ZONE				在已分配的内存周围插入“红色警戒区”以探测缓冲越界。
								//SLAB_PANIC				当分配失败时提醒slab层。
								//SLAB_CACHE_DMA			使用可以执行DMA的内存给每个slab分配空间。
				//第五个参数是高速缓存的构造函数。由于内核代码并不需要它,因此已经被抛弃了。
				注意:该函数可以引起睡眠。
				
int kmem_cache_destroy(struct kmem_cache * cachep);
				//撤销给定的高速缓存。
				注意:该函数也可能睡眠。
				
void * kmem_cache_alloc(struct kmem_cache * cachep, gfp_t flags);
				//从给定的高速缓存cachep中返回一个指向对象的指针。
void * kmem_cache_free(struct kmem_cache * cachep, void * objp);
				//释放一个对象,并把它返回给原先的slab。
				

六、高端内存的映射

1.高端内存的映射
void * kmap(struct page * page);
				//映射一个给定的page结构到内核地址空间;这个函数在高端内存或低端内存上都能用。
				//这个函数可以睡眠
				
void kunmap(struct page * page);
				//接触映射。允许永久映射的数量是有限的,当不再需要高端内存时,应该解除映射。

2.临时映射
void * kmap_atomic(struct page * page, enum km_type type);
void kunmap_atomic(void * kvaddr, enum km_type type);
				//建立和取消一个临时映射,这两个函数不会导致睡眠。

enum km_type{
		KM_BOUNCE_READ,
		KM_SKB_SUNRPC_DATA,
		KM_SKB_DATA_SOFTIRQ,
		KM_USER0,
		KM_USER1,
		KM_BIO_SRC_IRQ,
		KM_BIO_DST_IRQ,
		KM_PTE0,
		KM_PTE1,
		KM_PTE2,
		KM_IRQ0,
		KM_IRQ1,
		KM_SOFTIRQ0,
		KM_SOFTIRQ1,
		KM_SYNC_ICACHE,
		KM_SYNC_DCACHE,
		KM_UML_USERCOPY,
		KM_IRQ_PTE,
		KM_NMI,
		KM_NMI_PTE,
		KM_TYPE_NR
	};

七、每个CPU的分配

1.自己创建的方法
	unsigned long my_percpu[NR_CPUS];
	int cpu;
	cpu = get_cpu();		//获得当前处理器,并禁止内核抢占
	my_percpu[cpu]++;		//对每个CPU的数据进行处理
	put_cpu();			//激活内核抢占
2.新的每个CPU接口
编译时的每个CPU数据
DEFINE_PER_CPU(type, name);			//定义每个CPU变量
DECLARE_PER_CPU(type, name);			//在别处声明变量,以供其他地方使用。

get_cpu_var(name)++;		//返回当前处理器上的指定变量,同时禁止抢占;并对其执行++ 操作
put_cpu_var(name);		//重新激活抢占

per_cpu(name, cpu) ++;	//增加指定处理器上的name变量的值

运行时的每个CPU数据
void * alloc_percpu(type);
void * __alloc_percpu(size_t size, size_t align);
void free_percpu(const void * );

例如:
struct rabid_cheetah ptr = alloc_percpu(struct rabid_cheetah);
等价于
struct rabid_cheetah ptr = __alloc_percpu(sizeof(struct rabid_cheetah),
				__alignof__(struct rabid_cheetah));
																			
__alignof__是gcc的一个功能,它会返回指定类型所需的对齐字节数。它的语义和sizeof一样。

get_cpu_var(ptr);		返回了一个指向当前处理器数据的特殊实例,同时禁止内核抢占。
put_cpu_var(ptr);		重新激活内核抢占。

使用例子:
void *percpu_ptr;
unsigned long * foo;

percpu_ptr = alloc_percpu(unsigned long);
if(!percpu_ptr)
		/*内存分配错误*/
foo = get_cpu_var(percpu_ptr);
/*操作foo*/
put_cpu_var(percpu_ptr);
free_precpu(percpu_ptr);

  

内存管理