分类:
2012-04-29 21:19:13
原文地址:《深入理解LINUX内存管理》学习笔记(二)
mem_map是一个struct page的数组,管理着系统中所有的物理内存页面。在系统启动的过程中,创建和分配mem_map的内存区域。UMA体系结构中,free_area_init()函数在系统唯一的struct node对象contig_page_data中node_mem_map成员赋值给全局的mem_map变量。调用的关系图:
主要的核心函数free_area_init_core(),为node的初始化过程分配本地的lmem_map(node->node_mem_map)。数组的内存在boot memory 分配的alloc_bootmem_node()函数分配.在UMA体系结构中,这个新分配的lmem_map成为全局的mem_map. 对于NUMA体系,lmem_map赋值给每一个node的node_mem_map成员,而这个情况下mem_map就被简单的赋值为PAGE_OFFSET(有兴趣理解NUMA体系结构的可以阅读英文原版,了解更多信息)。UMA体系中,node中的各个zone的zone_mem_map就指向mem_map中的某些元素作为zone所管理的第一个page的地址。
系统中的每个物理页面用struct page数据结构对象来表示,并且跟踪page使用的状态:(省略了一些特定平台用到的成员)
struct page {
unsigned long flags;
atomic_t _count;
union {
atomic_t _mapcount;
unsigned int inuse;
};
union {
struct {
unsigned long private;
struct address_space *mapping;
};
struct kmem_cache *slab; /* SLUB: Pointer to slab */
struct page *first_page; /* Compound tail pages */
};
union {
pgoff_t index; /* Our offset within mapping. */
void *freelist; /* SLUB: freelist req. slab lock */
};
struct list_head lru;
#if ()
void *virtual;
#endif
};
union {
atomic_t _mapcount;
unsigned int inuse;
}: 和页表转换有关的PTE链,下面章节将描述。
index:这个成员根据page的使用的目的有2种可能的含义。第一种情况:如果page是file mapping的一部分,它指明在文件中的偏移。如果page是交换缓存,则它指明在address_space所声明的对象:swapper_space(交换地址空间)中的偏移。第二种情况:如果这个page是一个特殊的进程将要释放的一个page块,则这是一个将要释放的page块的序列值,这个值在__free_page_ok()函数中设置。
mapping: 当文件或设备需要内存映射,文件或设备的inode对象有一个address_space类型的成员。如果page属于这个文件或设备,mapping将指向inode中这个成员。如果page不属于任何文件或设备,但是 mapping被设置了,则mapping指向了一个address_space类型的swapper_space对象,则page用于管理交换地址空间(swap address space)了。
lru: page交换调度策略使用。page可能被调度到active_list或者inactive_list队列里。就是使用lru这个list_head。
private:这个保存了一些和mapping(文件mapping到内存)有关的一些特定的信息。如果page是一个buffer page,则它就保存了一个指向buffer_head的指针。
virtual: 不再用于将high memory的映射到ZONE_NORMAL区域的作用了,除了一些其他的体系结构会用到外。
count: page的访问计数,当为0是,说明page是空闲的,当大于0的时候,说明page被一个或多个进程真正使用或者kernel用于在等待I/O。
flags: page状态的标志信息。kernel代码里定义了大量的宏用于设置,清楚,检测flag成员中的各个位所表示的page状态信息。特别提示一下,SetPageUptodate(),它需要调用一个和体系结构有关的函数:arch_set_page_uptodate().
在2.4.18内核之前,struct page数据结构中有一个zone的成员,后来证明这样做会无谓的浪费大量的内存空间,因为系统中会有大量的page对象,所以以后版本的page中不在有这样的成员了,而是有一个索引表示,这个索引保存在flag成员中的某些位段中,这个索引占用8个位。2.6.19版本的kernel系统中建立了一个全局的zone数组:
struct *[1 << ] ;
();
EXPORT_SYMBOL宏的作用,是让zone_table能够被其他载入的模块使用。free_area_init_core()函数对node里的所有page做初始化。
zone_table[nid * MAX_NR_ZONES + j] = zone; //对zone_table做初始化。
nid是node ID。 j是zone的索引。
对每个page调用set_page_zone()初始化page中的zone的索引值(在page->flag中)。
set_page_zone(page, nid * MAX_NR_ZONES + j);
但是2.6.20后就不用这一套了,mm/sparse.c文件中做了一套管理系统。新的方法将多个page组成section来管理。
这里略微描述一下,有兴趣的,可以详细阅读sparse.c的源代码。kernel将所有的page分成多个section管理,对于x86平台,有64个section,每个section管理着(1<<26)个或(1<<30)个(对于支持PAE的情况下)内存区域。
以下是几个主要的define:
include/asm-x86/sparsemem_32.h:
#ifdef CONFIG_X86_PAE
#define SECTION_SIZE_BITS 30
#define MAX_PHYSADDR_BITS 36
#define MAX_PHYSMEM_BITS 36
#else
#define SECTION_SIZE_BITS 26
#define MAX_PHYSADDR_BITS 32
#define MAX_PHYSMEM_BITS 32
#endifinclude/linux/mmzone.h:#define SECTIONS_SHIFT (MAX_PHYSMEM_BITS - SECTION_SIZE_BITS)#define NR_MEM_SECTIONS (1UL << SECTIONS_SHIFT)#ifdef CONFIG_SPARSEMEM_EXTREME
#define SECTIONS_PER_ROOT (PAGE_SIZE / sizeof (struct mem_section))
#else
#define SECTIONS_PER_ROOT 1
#endif#define SECTION_NR_TO_ROOT(sec) ((sec) / SECTIONS_PER_ROOT)
#define NR_SECTION_ROOTS (NR_MEM_SECTIONS / SECTIONS_PER_ROOT)
#define SECTION_ROOT_MASK (SECTIONS_PER_ROOT - 1)
首先声明了一个全局的mem_section的全局数组。
struct mem_section *mem_section[NR_SECTION_ROOTS];
调用sparse_add_one_section()函数,分配mem_section,并且初始化。