一、页
1、内核把物理页作为内存管理的基本单位。
1)、内存管理单元通常以页为单位进行处理。正因为如此,MMU以页(page)大小为单位来管理系统中的页表。
2)、从虚拟内存的角度上看,页就是最小单位。
3)、大多数32位体系结构支持4KB的页,而64位体系结构一般会支持8KB的页。
2、内核用struct page结构表示系统中的每个物理页,该结构位于<linux/mm_types.h>中:
struct page {
unsigned long flags;
atomic_t _count;
atomic_t _mapcount;
unsigned long private;
struct address_space *mapping;
pgoff_t index;
struct list_head lru;
void *virtual;
};
1)、page结构与物理页相关,因为内核需要知道一个页是否空闲。如果页已经被分配,内核还需要知道谁拥有这个页。
二、区
1、内核把页页划分为不同的区(zone),内核使用区对具有相似特性的页进行分组。
2、Linux主要使用四种区:
1)、ZONE_DMA——这个区包含的页能用来执行DMA操作。
2)、ZINE_DMA32——和ZONE_DMA类似,该区包含的页面可用来执行DMA操作;而和ZONE_DMA不同之处在于,这些页面只能被32位设备访问。在某些体系结构中,该
区将比ZONE_DMA更大。
3)、ZONE_NORMAL——这个区包含的都是能正常映射的页。
4)、ZONE_HIGHEM——这个区包含“高端内存”,其中的页并不能永久地映射到内核地址空间。
3、区的实际使用和分布是与体系结构有关的。
1)、X86-32上的区(P188 表12-1)
4、Linux把系统的页划分为区,形成不同的内存池,这样就可以根据用途进行分配。注意,区的划分没有任何物理意义,这只不过是内核为了管理页而采取的一种逻辑上的分
组。
5、某些分配可能需要从特定的区中获取页,而另外一些分配则可以从多个区中获取页。不是所有的体系结构都定义了全部区。
6、每个区都用struct zone表示,在<linux/mmzone>中定义:
struct zone {
unsigned long watermark[NR_WMARK];
unsigned long lowmem_reserve[MAX_NR_ZONES];
struct per_cpu_pageset pageset[NR_CPUS];
spinlock_t lock;
struct free_area free_area[MAX_ORDER];
spinlock_t lru_lock;
struct zone_lru {
struct list_head list;
unsigned long nr_saved_scan;
} lru[NR_LRU_LISTS];
struct zone_reclaim_stat reclaim_stat;
unsigned long pages_scanned;
unsigned long flags;
atomic_long_t am_stat[NR_VM_ZONE_STAT_ITEMS];
int prev_priority;
unsigned int inaction_ratio;
wait_queue_head_t *wait_table;
unsigned long wait_table_hash_nr_entries;
unsigned long wait_table_bits;
struct pglist_data *zone_pgdat;
unsigned long zone_start_pfn;
unsigned long spanned_pages;
unsigned long present_pages;
const char *name;
};
1)、lock域是一个自旋锁,它防止该结构被并发访问。
2)、watermark数组持有该区的最小值、最低和最高水位值。
3)、name域是一个以NULL结束的字符串表示这个区的名字。
三、获得页
一)、相关简介
1、内恶化提供了一种请求内存的底层机制,并提供了对它进行访问的几个接口。所有这些接口都以页为单位分配内存,定义在<linux/gfp.h>中。最核心函数是:
struct page * alloc_pages(gfp_t gfp_mask, unsigned int order);
2、把指定的页转换成它的逻辑地址:
void * page_address(struct page *page);
3、无需用到struct page结构的函数
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
4、其他函数
struct page * alloc_page(gfp_t gfp_mask);
unsigned long __get_free_page(gfp_t gfp_mask);
二)、获得填充为0的页
1、需要返回的页的内容全为0:
unsigned long get_zeroed_page(unsigned int gfp_mask);
2、低级页分配方法(P190 表12-2)
三)、释放页
1、当不需要页时可以用下面的函数释放它们:
void __free_pages(struct page *page, unsigned int order);
void free_pages(unsigned long addr, unsigned int order);
void free_page(unsigned long addr);
四、kmalloc()
一)、相关简介
1、kmalloc()函数在<linux/slab.h>中声明:
void * malloc(size_t size, gfp_t flags);
二)、gfp_mask标志
1、gfp_mask标志可分为三类
1)、行为修饰符:表示内核应当如何分配所需要的内存。在某些特定情况下,只能使用某些特定的方法分配内存。
2)、区修饰符:表示从哪儿分配内存。
3)、类型标志组合了行为修饰符和区修饰符,将各种可能用到的组合归纳为不同的类型,简化了修饰符的使用;如此,只需指定一个类型标志就行。
2、行为修饰符(P192-P193 表12-3)
3、区修饰符(P193 表12-4)
4、不能给__get_free_pages()或kalloc()指定ZONE_HIGHEM,因为这两个函数返回的都是逻辑地址,而不是page结构,这两个函数分配的内存当前可能还没有映射到内核的
虚拟地址空间,因此,肯恩根本没有逻辑地址。
5、只有alloc_pages()才能分配高端内存。
6、类型标志
1)、类型标志(P194 表12-5)
2)、在每种类型标志后隐含的修饰符列表(P194 表12-6)
3)、什么时候用那种标志(P195 表12-7)
三)、kfree
五、vmalloc()
1、vmalloc(0函数的工作方式类似于kmalloc()。只不过前者分配的内存虚拟地址是连续的,而物理地址则无须连续。大多数内核代码用kmalloc()来获取内存,而不是
vmalloc()。
2、vmalloc()函数声明在<linux/vmalloc.h>中,定义在<mm/vmalloc.c>中:
void * vmalloc(unsigned long size);
3、释放通uovmalloc分配的内存:
void vfree(const void *addr);
六、slab层
一)、相关简介
1、slab分配器扮演了通用数据结构缓存层的角色。
2、Linux的slab在设计和实现是考虑的规则。
二)、slab层的设计
1、slab层把不同的对象划分为所谓高速缓存组,其中每个高速缓存组都存放不同类型的对象,每种对象类型对应一个高速缓存。
2、高速缓存又被划分为slab。slab由一个或多个物理上的连续的页组成,一般情况下,slab也就仅仅由一页组成。没和高速缓存可以由多个slab组成。
3、没个slab都包含一些对象的成员,这里的对象指的是被缓存的数据结构。每个slab处于三种状态之一:满、部分满或空。
4、高速缓存、slab以及对象之间的关系(P198 图12-1)。
5、每个高速缓存都使用kmem_cache接口来表示。这个结构包含三个链表:slabs_full、slabs_partial和slabs_empty,均存放在描述符kmem_list3结构内,该结构在
mm/slab.c中定义。这些链表包含告诉缓存中的所有slab。slab描述符struct slab用来描述每个slab:
struct slab {
struct list_head list;
unsigned long colouroff;
void *s_mem;
unsigned int inuse;
kmem_bufctl_t free;
};
三)、slab分配器的接口
1、一个新的高速缓存通过以下的函数创建:
struct kmem_cache_create(const char *name, sezi_t size,
size_t align, unsigned long flags,
void (*ctor) (noid *));
1)、第一个参数是一个字符串,存放着高速缓存的名字。
2)、第二个参数是高速缓存中每个元素的大小。
3)、第三个参数是slab内第一个对象,它用来确保在页内进行特定的对齐。
4)、flags参数是可选的设置项,用来控制高速缓存的行为。
5)、最后一个参数ctor是高速缓存的构造函数。
2、要撤销一个高速缓存:
int kmem_cache_destroy(struct kmem_cache *cachep);
1)、调用该函数之前应该满足以下条件:
I、高速缓存中的所有的slab都必须为空。
II、在调用kmem_eache_destroy()过程中不再访问这个高速缓存。
3、从缓存中分配
1)、创建一个高速缓存之后,就可以通过下列函数获取对象:
void * kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);
2)、最后释放一个对象,并把它返回给原先的slab,可以用下列函数:
void kmem_cache_free(struct kmem_cache *cachep, void *objp);
4、slab分配器实例
七、在栈上的静态分配
1、单页内核栈
1)、当激活这个选项时,每个进程的内核只有一页那么大,根据体系结构的不同,或为4KB,或为8KB。
2、中断栈
1)、中断栈为每个进程提供了一个用于中断处理程序的栈。有了这个选项,中断处理程序不用再和被中断进程共享一个内存栈,它们可以使用自己的栈。
3、进行动态分配内存是一个明智的选择。
八、高端内存的映射
1、永久映射
1)、要映射一个给定的page结构到内呢地址空间,可以使用下列函数:
void *kmap(struct page *page);
2)、当不再需要高端内存时,应该解除映射:
void kunmap(struct page *page);
2、临时映射
1)、可以通过以下函数建立一个临时映射:
void *kmap_atomic(struct page *page, enum km_type type);
2)、通过下列函数取消映射:
void kunmap_atomic(void *kxaddr, enum km_type type);
九、每个CPU的分配
1、一般来说,每个CPU的数据存放在一个数组中。数组中的每一项都对应着系统上一个存在的处理器。
unsigned long my_percpu[NR_CPUS];
2、按如下方式访问:
int cpu;
cpu = get_cpu();
my_percpu[cpu]++;
printk("my_percpu on cpu=%d is %lu\n", cpu, my_percpu[cpu]);
十、新的每个CPU接口
一)、编译时的每个CPU数据
1、在编译时定义每个CPU变量易如反掌:
DEFINE_PER_CPU(type, name);
1)、在别处声明变量,以防止编译时警告:
DECLARE_PER_CPU(type, name);
2、可以利用get_cpu_var()和put_cpu_var()例程操作变量。调用get_cpu_var()返回当前处理器上的指定变量,同时它将禁止抢占;另一方面put_cpu_var()将相应的重新激活抢
占。
3、也可以用per_cpu()获得别的处理器上的每个CPU数据。
二)、运行时的每个CPU数据
1、内核实现每个CPU数据的动态分配方法类似于kmalloc()。该例程为系统上的每个处理器创建所需的内存实例
void *alloc_percpu(type);
void *__alloc_percpu(size_t size, size_t align);
void free_percpu(const void *);
十一、使用每个CPU数据的原因
1、减少了数据的锁定。
2、使用每个CPU数据可以大大减少缓存失效。
十二、分配函数的选择
1、如果需要连续的物理页,就可以使用某个低级页分配器或kmalloc()。
2、如果想从高端内存进行分配,就是用alloc_pages()。
3、如果不需要物理上连续的页,二仅仅需要虚拟地址上的页,就可以使用vmalloc()。
4、如果要创建和撤销很多大的数据结构,那么考虑建立slab高速缓存。