2010年(16)
分类: LINUX
2010-04-13 12:38:49
MMU(内存管理单元)以页为单位进行处理,页是最小单位。(32位体系结构支持4KB页64位体系结构支持8KB页)。 结构体Struct page表示每个物理页,此结构位于
struct page {
page_flags_t flags;页状态(是否是脏的/被锁定,定义在
atomic_t _count; 页引用计数(用page_count()函数检查,返回0表示页空闲,返回正整数表示页在使用)。
atomic_t _mapcount;
unsigned long private;当页作为私有数据时,被使用。
struct address_space *mapping;当页被页缓存使用时,它指向和这个页关联的address_space对象。
pgoff_t index;
struct list_head lru;
#if defined(WANT_PAGE_VIRTUAL)
void *virtual;页的虚拟地址,当页处于高端内存时,它为NULL,在需要的时候再动态映射。
#endif /* WANT_PAGE_VIRTUAL */
};
内核用这一结构来管理系统中所有的页,因为内核需要知道一个页是否空闲。如果页已经被分配,内核需要知道谁拥有这个页,是用户空间进程、动态分配的内核结果、静态内核代码、或页高速缓存等等。
产生原因:1.一些硬件只能用某些特定的内存地址来执行DMA
2.一些体系结构其内存的物理寻址范围比虚拟寻址范围大得多。这样就有一些内存不能永久地映射到内核空间上。
分区:
ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM,定义在
区的实际使用和分布是与体系结构相关的,区的划分没有任何物理意义;它知识以内核为管理页而采取的一种逻辑上的分组。X86上的区划分看P146-表11-1。这些区的划分也不是绝对的,使用也不是绝对的。如果可供分配的资源不够用了,那么,内核就会去占用其他可用区的内存。
区的结构:struct zone 定义在
struct zone {
unsigned long free_pages;这个区中空闲页的个数。内核要保证(通过交换达到目的)有pages_min个空闲页可用。
unsigned long pages_min, pages_low, pages_high;
unsigned long protection[MAX_NR_ZONES];
struct per_cpu_pageset pageset[NR_CPUS];
spinlock_t lock;是一个自旋锁,防止struct zone被并发访问。但它不保护驻留在这个区的所有页。
struct free_area free_area[MAX_ORDER];
ZONE_PADDING(_pad1_)
spinlock_t lru_lock;
struct list_head active_list;
struct list_head inactive_list;
unsigned long nr_scan_active;
unsigned long nr_scan_inactive;
unsigned long nr_active;
unsigned long nr_inactive;
unsigned long pages_scanned;
int all_unreclaimable;
int temp_priority;
int prev_priority;
ZONE_PADDING(_pad2_)
wait_queue_head_t * wait_table;
unsigned long wait_table_size;
unsigned long wait_table_bits;
struct pglist_data *zone_pgdat;
struct page *zone_mem_map;
unsigned long zone_start_pfn;
unsigned long spanned_pages;
unsigned long present_pages;
char *name;区的名字,内核启动期间初始化这个值,代码位于mm/page_alloc.c中,三个区的名字分别为“DMA”“Normal”“HighMem”。
}
这种结构体很大,但是,系统中只有三个区,因此也只有三个这样的结构。
内核提供了一种请求内存的底层机制,并提供了对它进行访问的几个接口。而我们正是通过这些接口在内存内分配和释放内存的。它们定义在
释放页时要谨慎,只能释放属于你的页。传递错误的struct page或地址,用了错误的order值,这些都可能导致系统崩溃。所以页分配和页释放最好成对使用。
内核分配可能失败,因此代码中必须进行检查并做相应的处理。这意味着之前的工作可能前功尽弃,甚至需要回滚到原来的状态。所以在程序开始的地方就先进行内存分配的检查是很重要的。
Kmalloc()函数是以字节为单位分配内存的函数,获得以自己为单位的一块内核内存。它和用户空间的malloc()函数非常相似,但它多了一个flag参数(是一个gfp_mask标志)。和以页分配的函数比起来,kmalloc函数用的更多。
Void *kmalloc(size_t size , int flags) 函数返回一个指向内存块的指针。它在
Kfree()函数和kmalloc相对,它和kmalloc()相对使用、以避免内存泄漏和其他bug。Kfree(NULL)是安全的。
还有一个内存分配函数:vmalloc(),它在
Void* vmalloc(unsigned long size) 对应的释放函数void vfree(void *addr)
Vmalloc()函数工作方式类似于kmalloc()函数,但是也有很大的区别:
Vmalloc()分配的内存虚拟地址是连续的,而物理地址则无需连续。和malloc的是一样的。它通过分配非连续的物理内存块,再“修正”页表,把内存映射到逻辑地址空间的连续区域中,就能做到这点。然而这样做却很花费时间,volloc()函数为了把物理上不连续的转换为虚拟地址空间上连续的页,需要专门建立页表项。然后对获得的页一个一个进行映射,这样会产生很大的TLB抖动,性能不高,所以许多内核代码都用Kmalloc()来获得内存。
Kmalloc()函数分配的内存物理地址是连续的,虚拟地址当然也是连续的。
说明:一般情况下,只有以硬件设备需要得到物理地址连续的内存,因为硬件设备存在于MMU之外,它根本不理解什么是虚拟地址。所以硬件设备得到的任何内存区都必须是物理上连续的块,而不仅仅是虚拟地址连续的块。而供软件使用的内存块则只要虚拟地址连续即可,但程序员去察觉不到。
分为三类:行为修饰符、区修饰符及类型。所有这些都在
用来表示内核应当如何分配所需的内存。在某些特定情况下,只能使用某些特定的方法分配内存。如中断处理程序在分配内存过程中不能睡眠。具体见P150-表11-3。
用来表示内存区应当从何处分配。通常是从ZONE_NORMAL中分配,如指定了表中的一个标志就可以改变内核试图进行分配的区。
主要不能给那些返回逻辑地址的分配函数指定_GFP_HIGHMEM(如_get_free_pages()或kmalloc()),因为这些函数分配的内存当前可能还没有映射到内核的虚拟地址空间,因为根本就没有逻辑地址。具体见P150-表11-4。
类型标志组合了行为修饰符和区修饰符来完成特殊类型的处理,所以内核代码一般使用类型标志,这样既简单又不容易出错。具体见P151-表11-5和表11-6。
什么时候该用什么标志,看具体见P151-表11-7。
目的:为了便于数据的频繁分配和回收。
产生背景:内核中空闲链表不能全局控制,所以当内存紧张时,内核无法通知内核链表去删除一些节点来释放内存。为了弥补这一缺陷,使代码更稳固,LINUX内核提供了slab层(slab分配器)。
SLAB所处的位置(与一些结构间的关系):
图片传不上来