这里主要介绍UMA机制。
contig_page_data被定义如下:
mm/page_alloc.c
structpglist_data__refdatacontig_page_data={.bdata=&bootmem_node_data[0]};
EXPORT_SYMBOL(contig_page_data);
structpglist_data即是pg_data_t的原型。
了解pg_data_t中的结构成员对于了解内存管理是必经之路:
enumzone_type{
ZONE_DMA,
ZONE_NORMAL,
ZONE_MOVABLE,
......
__MAX_NR_ZONES
};
typedefstructpglist_data{
structzonenode_zones[MAX_NR_ZONES];
structzonelistnode_zonelists[MAX_ZONELISTS];
intnr_zones;
#ifdefCONFIG_FLAT_NODE_MEM_MAP/*means!
SPARSEMEM*/
structpage*node_mem_map;
#ifdefCONFIG_CGROUP_MEM_RES_CTLR
structpage_cgroup*node_page_cgroup;
#endif
#endif
structbootmem_data*bdata;
....../*forCONFIG_MEMORY_HOTPLUG*/
unsignedlongnode_start_pfn;
unsignedlongnode_present_pages;/*totalnumberofphysicalpages*/
unsignedlongnode_spanned_pages;/*totalsizeofphysicalpage
range,includingholes*/
intnode_id;
wait_queue_head_tkswapd_wait;
structtask_struct*kswapd;
intkswapd_max_order;
}pg_data_t;
∙node_zones:
当前节点中包含的最大管理区数。
MAX_NR_ZONES在include/linux/bounds.h定义,该文件是在编译过程中根据管理区类型定义中的__MAX_NR_ZONES变量自动生成的。
∙node_zonelists:
内存分配器所使用的管理区链表数组,MAX_ZONELISTS的值在配置CONFIG_NUMA时为2,否则为1。
索引为0的链表表示后援(Fallback)链表,也即当该链表中的第一个不满足分配内存时,依次尝试链表的其他管理区。
索引为1,的链表则用来针对GFP_THISNODE的内存申请,此时只能申请指定的该链表中的管理区。
∙nr_zones:
指定当前节点中的管理区数,也即node_zones中实际用到的管理区数。
它的取值范围为[1,MAX_NR_ZONES]。
对于UMA来说,它的值为1。
∙node_mem_map:
节点中页描述符数组首地址。
∙node_page_cgroup:
∙bdata:
系统引导时用的Bootmem分配器。
∙node_start_pfn:
节点中第一个页框的下标。
∙node_present_pages:
节点中的页面数,不包含孔洞。
∙node_spanned_pages:
节点中的页面总数,包含孔洞。
∙node_id:
节点标识符,在节点数组中唯一存在。
∙kswapd_wait:
kswapd页换出守护进程使用的等待队列。
∙kswapd:
指针指向kswaps内核线程的进程描述符。
∙kswapd_max_order:
kswapd将要创建的空闲块大小取对数的值。
注意到zonelist中的_zonerefs元素,它用来实现分配器分配内存时候的管理区后援功能。
MAX_ZONES_PER_ZONELIST被定义为所有节点中包含的最多管理区的和并加上1,加1的目的是在后援链表中,可以检测是否遍历到最后一个节点了,如果是说明申请失败。
/*Maximumnumberofzonesonazonelist*/
#defineMAX_ZONES_PER_ZONELIST(MAX_NUMNODES*MAX_NR_ZONES)
structzonelist{
structzonelist_cache*zlcache_ptr;//NULLor&zlcache
structzoneref_zonerefs[MAX_ZONES_PER_ZONELIST+1];
#ifdefCONFIG_NUMA
structzonelist_cachezlcache;//optional...
#endif
};
节点中的管理区都在free_area_init_core函数中初始化。
调用关系如下所示:
start_kernel->setup_arch->paging_init->bootmem_init->bootmem_free_node->free_area_init_node->free_area_init_core
在理想的计算机体系结构中,一个物理页框就是一个内存存储单元,可用于任何事情:
存放内核数据和用户数据,磁盘缓冲数据等。
热河中磊的数据页都可以存放在任何页框中,没有什么限制。
但是,实际的计算机体系结构有硬件的制约,这制约页框可以使用的方式。
尤其是Linux内核必须处理80x86体系结构的两种硬件约束:
∙ISA总线的直接内存存取DMA访问控制器只能对RAM的低16MB寻址。
∙在具有大容量RAM的现代32位计算机中,由于线性地址空间的限制,CPU不能直接访问所有的物理内存。
最后一种限制不仅存在于80x86,而存在于所有的体系结构中。
为了应对这两种限制,Linux把每个内存节点的物理内存划分为多个(通常为3个)管理区(zone)。
在80x86UMA体系结构中的管理区为:
∙ZONE_DMA,包含低于16MB的内存页框。
∙ZONE_NORMAL,包含高于16MB且低于896MB的内存页框。
∙ZONE_HIGHMEM,包含从896MB开始的内存页框。
对于ARM来说,ZONE_HIGHMEM被名为ZONE_MOVABLE的宏取代,而ZONE_DMA也不会仅限于最低的16MB,而可能对应所有的内存区域,此时只有内存节点ZONE_DMA有效,所以ZONE_DMA并不一定名副其实的用来作为DMA访问之用。
ZONE_DMA和ZONE_NORMAL区包含内存的"常规"页框,通过把它们线性的映射到线性地址的第4个GB(0xc0000000-0xcfffffff),内核就可以直接访问。
相反ZONE_HIGHMEM或者ZONE_MOVABLE区包含的内存页不能由内核直接访问,尽管它们也线性地映射到了线性地址空间的第4个GB。
每个内存管理区都有自己的描述符structzone。
它用来保存管理区的跟踪信息:
内存使用统计,空闲区,锁定区等。
include/linux/mmzone.h
structzone{
/*Fieldscommonlyaccessedbythepageallocator*/
unsignedlongpages_min,pages_low,pages_high;
unsignedlonglowmem_reserve[MAX_NR_ZONES];
structper_cpu_pagesetpageset[NR_CPUS];
structfree_areafree_area[MAX_ORDER];
ZONE_PADDING(_pad1_)
/*Fieldscommonlyaccessedbythepagereclaimscanner*/
spinlock_tlru_lock;
struct{
structlist_headlist;
unsignedlongnr_scan;
}lru[NR_LRU_LISTS];
unsignedlongrecent_rotated[2];
unsignedlongrecent_scanned[2];
unsignedlongpages_scanned;/*sincelastreclaim*/
unsignedlongflags;/*zoneflags,seebelow*/
/*Zonestatistics*/
atomic_long_tvm_stat[NR_VM_ZONE_STAT_ITEMS];
};
∙pages_min,记录管理区中空闲页的数目。
∙pages_low,回收页框使用的下届,同时也被管理区分配器作为阈值使用。
∙pages_high,回收页框使用的上届,同时也被管理区分配器作为阈值使用。
∙lowmem_reserve,指明在处理内存不足的临界情况下每个管理区必须保留的页框数目。
∙pageset,单一页框的特殊告诉缓存。
∙
在申请内存时,会遇到两种情况:
如果有足够的空闲页可用,请求就会被立刻满足;否则,必须回收一些内存,并且将发出请求的内核控制路径阻塞,直到有内存被释放。
不过有些内存请求不能被阻塞。
这种情况发生在处理中断或在执行临界区内的代码时。
在这些情况下,一条内核控制路径应使用原子内存分配请求(GFP_ATOMIC)。
原子请求从不被阻塞;如果没有足够的空闲页,则仅仅是分配失败而已。
内核为了尽可能保证一个原子内存分配请求成功,它为原子内存分配请求保留了一个页框池,只有在内存不足时才使用。
保留内存的数量存放在min_free_kbytes变量中,单位为KB。
mm/page_alloc.c
intmin_free_kbytes=1024;
.....
/*min_free_kbytes=sqrt(lowmem_kbytes*16);*/
lowmem_kbytes=nr_free_buffer_pages()*(PAGE_SIZE>>10);
min_free_kbytes=int_sqrt(lowmem_kbytes*16);
min_free_kbytes由当前直接映射区的物理内存数量决定。
也即ZONE_DMA和ZONE_NORMAL内存管理区的可用页框数决定,这可以通过nr_free_buffer_pages获取。
尽管可以通过/proc/sys/vm/min_free_kbytes来修改该它的大小,但是min_free_kbytes的初始值范围必须是[128K,64M]。
管理区描述符中的pages_min成员存储了管理区内保留页框的数目。
这个字段与pages_low和pages_high字段一起被用在内存分配和回收算法中。
pages_low字段总是被设为pages_min的值的5/4,而pages_high则总是被设为pages_min的值的3/2。
这些值在模块快初始化module_init调用的init_per_zone_pages_min中被设置。
表 27. 页面分配控制
名称
大小
pages_min
min_free_kbytes>>(PAGE_SHIFT-10)
pages_low
pages_min*5/4
pages_high
pages_min*3/2
free_area_init_core中对管理区初始化的代码部分如下,后续章节将对该函数进一步分析。
zone->spanned_pages=size;
zone->present_pages=realsize;
zone->name=zone_names[j];
spin_lock_init(&zone->lock);
spin_lock_init(&zone->lru_lock);
zone_seqlock_init(zone);
zone->zone_pgdat=pgdat;
zone->prev_priority=DEF_PRIORITY;
zone_pcp_init(zone);
for_each_lru(l){
INIT_LIST_HEAD(&zone->lru[l].list);
zone->lru[l].nr_scan=0;
}
zone->recent_rotated[0]=0;
zone->recent_rotated[1]=0;
zone->recent_scanned[0]=0;
zone->recent_scanned[1]=0;
zap_zone_vm_stats(zone);
zone->flags=0;
13.2. page管理项
structpage{
unsignedlongflags;/*Atomicflags,somepossibly
*updatedasynchronously*/
atomic_t_count;/*Usagecount,seebelow.*/
union{
atomic_t_mapcount;/*Countofptesmappedinmms,
*toshowwhenpageismapped
*&limitreversemapsearches.
*/
struct{/*SLUB*/
u16inuse;
u16objects;
};
};
每一个物理页框都需要一个对应的page结构来进行管理:
记录分配状态,分配和回收,互斥以及同步操作。
对该结构成员的解释如下:
∙flag域存放当前页框的页标志,它存储了体系结构无关的状态,专门供Linux内核自身使用。
该标志可能的值定义在include/linux/page-flags.h中。
∙原子计数成员_count则指明了当前页框的引用计数,当该值为0时,就说明它没有被使用,此时在新分配内存时它就可以被使用。
内核代码应该通过page_count来访问它,而非直接访问。
∙原子计数成员_mapcount表示在页表中有多少页指向该页框。
在SLUB中它被inuse和objects代替。
include/linux/page-flags.h
enumpageflags{
PG_locked,/*Pageislocked.Don'ttouch.*/
PG_error,
PG_referenced,
PG_uptodate,
PG_dirty,
PG_lru,
PG_active,
......
__NR_PAGEFLAGS,
......
}
以上是页标志位的可能取值,通常不应该直接使用这些标志位,而应该内核预定义好的宏,它们在相同的头文件中被定义,但是它们是被间接定义的,也即通过##连字符来统一对它们进行定义。
#defineTESTPAGEFLAG(uname,lname)\
staticinlineintPage##uname(structpage*page)\
{returntest_bit(PG_##lname,&page->flags);}
......
TESTPAGEFLAG(Locked,locked)
PAGEFLAG(Error,error)
PAGEFLAG(Referenced,referenced)TESTCLEARFLAG(Referenced,referenced)
表 28. 页标志宏函数
宏
扩展函数/宏
用途
TESTPAGEFLAG(uname,lname)
Page##uname
测试PG_##lname位
SETPAGEFLAG(uname,lname)
SetPage##uname
设置PG_##lname位
CLEARPAGEFLAG(uname,lname)[a]
ClearPage##uname
清除PG_##lname位
TESTSETFLAG(uname,lname)
TestSetPage##uname
测试并设置PG_##lname
TESTCLEARFLAG(uname,lname)
TestClearPage##uname
测试并清除PG_##lname
PAGEFLAG(uname,lname)[b]
TESTPAGEFLAG
SETPAGEFLAG
CLEARPAGEFLAG
当于同时扩展了三个宏,也即三个函数
PAGEFLAG_FALSE(uname)
Page##uname
永远返回0
TESTSCFLAG(uname,lname)
TESTSETFLAG
TESTCLEARFLAG
当于同时扩展了两个宏,也即两个函数
SETPAGEFLAG_NOOP(uname)
SetPage##uname
空操作
CLEARPAGEFLAG_NOOP(uname)
ClearPage##unam
空操作
__CLEARPAGEFLAG_NOOP(uname)
__ClearPage##uname
空操作
TESTCLEARFLAG_FALSE(uname)
TestClearPage##uname
永远返回0
[a]以上三个宏分别对应test_bit,set_bit和clear_bit,是原子操作,与它们对应的是有三个开头
为下划线的同名函数__SETPAGEFLAG等与它们相对应,但不是原子操作,这里不再列出。
[b]与此对应也有__PAGEFLAG的宏存在。
flags实际上为两部分:
标志区(FlagsArea)从最低处向上扩展到第__NR_PAGEFLAGS位;字段区(FieldsArea)则从最高位向低位扩展。
字段区用来实现管理区,内存节点和稀疏内存的映射。
|FIELD|...|FLAGS|
N-1^0
(__NR_PAGEFLAGS)
_count引用计数不应被直接引用,内核提供了一系列的内联函数来操作它,通常它们被定义在include/linux/mm.h中。
表 29. 页引用计数函数
函数名
用途
page_count
读取引用计数
get_page
引用计数加1
init_page_count
初始化引用计数为1
_mapcount与_count引用计数类似,不应被直接引用,内核提供了一系列的内联函数来操作它,它们也被定义在include/linux/mm.h中。
表 30. 页引用计数函数
函数名
用途
reset_page_mapcount
初始化引用计数为-1[a]
page_mapcount
读取引用计数并加1的值
page_mapped
该函数根据引用计数值是否大于等于0,判断该页框是否被映射。
[a]没有初始化为0是因为atomic_inc_and_test和atomic_add_negative的操作,对该引用计数的加减是由这两个函数完成的。
union{
struct{
unsignedlongprivate;/*Mapping-privateopaquedata:
*usuallyusedforbuffer_heads
*ifPagePrivateset;usedfor
*swp_entry_tifPageSwapCache;
*indicatesorderinthebuddy
*systemifPG_buddyisset.
*/
structaddress_space*mapping;/*Iflowbitclear,pointsto
*inodeaddress_space,orNULL.
*Ifpagemappedasanonymous
*memory,lowbitisset,and
*itpointstoanon_vmaobject:
*seePAGE_MAPPING_ANONbelow.
*/
};
#ifUSE_SPLIT_PTLOCKS
spinlock_tptl;
#endif
structkmem_cache*slab;/*SLUB:
Pointertoslab*/
structpage*first_page;/*Compoundtailpages*/
};
union{
pgoff_tindex;/*Ouroffsetwithinmapping.*/
void*freelist;