内存管理区struct page.docx
《内存管理区struct page.docx》由会员分享,可在线阅读,更多相关《内存管理区struct page.docx(24页珍藏版)》请在冰豆网上搜索。
内存管理区structpage
内存管理区(structpage)
前面已经提到,物理内存被划分为三个区来管理,它们是ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。
每个区都用structzone_struct结构来表示,定义于include/linux/mmzone.h:
typedefstructzone_struct{
/*
*Commonly
accessedfields:
*/
spinlock_t
lock;
unsigned
longfree_pages;
unsigned
longpages_min,
pages_low,pages_high;
int
need_balance;
/*
*freeareasofdifferentsizes
*/
free_area_tfree_area[MAX_ORDER];
/*
*Discontig
memorysupportfields.
*/
struct
pglist_data*zone_pgdat;
struct
page*zone_mem_map;
unsigned
long
zone_start_paddr;
unsigned
long
zone_start_mapnr;
/*
*rarelyusedfields:
*/
char
*name;
unsigned
long
size;
}zone_t;
#define
ZONE_DMA
0
#define
ZONE_NORMAL
1
#define
ZONE_HIGHMEM
2
#define
MAX_NR_ZONES
3
对structzone_struct结构中每个域的描述如下:
lock:
用来保证对该结构中其它域的串行访问
free_pages:
在这个区中现有空闲页的个数
pages_min、pages_low及pages_high是对这个区最少、此少及最多页面个数的描述
need_balance:
与kswapd合在一起使用
free_area:
在伙伴分配系统中的位图数组和页面链表
zone_pgdat:
本管理区所在的存储节点
zone_mem_map:
该管理区的内存映射表
zone_start_paddr:
该管理区的起始物理地址
zone_start_mapnr:
在mem_map中的索引(或下标)
name:
该管理区的名字
size:
该管理区物理内存总的大小
其中,free_area_t定义为:
#difine
MAX_ORDER
10
type
structfree_area_struct{
structlist_headfree_list
unsignedint*map
}free_area_t
因此,zone---_struct结构中的free_area[MAX_ORDER]是一组“空闲区间”链表。
为什么要定义一组而不是一个空闲队列呢?
这是因为常常需要成块地在物理空间分配连续的多个页面,所以要按块的大小分别加以管理。
因此,在管理区数据结构中既要有一个队列来保持一些离散(连续长度为1)的物理页面,还要有一个队列来保持一些连续长度为2的页面块以及连续长度为4、8、16、…、直至2MAX_ORDER(即4M字节)的队列。
如前所述,内存中每个物理页面都有一个structpage结构,位于include/linux/mm.h,该结构包含了对物理页面进行管理的所有信息,下面给出具体描述:
typedefstructpage{
struct
list_headlist;
struct
address_space*mapping;
unsigned
longindex;
struct
page*next_hash;
atomic_t
count;
unsigned
longflags;
struct
list_headlru;
wait_queue_head_t
wait;
struct
page**pprev_hash;
struct
buffer_head*buffers;
void
*virtual;
struct
zone_struct*zone;
}
mem_map_t;
对每个域的描述如下:
list:
指向链表中的下一页
mapping:
用来指定我们正在映射的索引节点(inode)
index:
在映射表中的偏移
next_hash:
指向页高速缓存哈希表中下一个共享的页
count:
引用这个页的个数
flags:
页面各种不同的属性
lru:
用在active_list中
wait:
等待这一页的页队列
pprev_hash:
与next_hash相对应
buffers:
把缓冲区映射到一个磁盘块
zone:
页所在的内存管理区
与内存管理区相关的三个主要函数为:
·
free_area_init()函数
·
build_zonelists()函数
·
mem_init()函数
1.free_area_init()函数
这个函数用来初始化内存管理区并创建内存映射表,定义于mm/page_alloc.c中。
函数原型为:
voidfree_area_init(unsignedlong*zones_size);
voidfree_area_init_core(intnid,pg_data_t*pgdat,
struct
page**gmap,
unsignedlong*zones_size,
unsignedlongzone_start_paddr,
unsignedlong*zholes_size,
structpage*lmem_map);
free_area_init()为封装函数,而free_area_init_core()为真正实现的函数,对该函数详细描述如下:
structpage*p;
unsignedlongi,j;
unsignedlongmap_size;
unsignedlongtotalpages,offset,realtotalpages;
constunsignedlongzone_required_alignment=1UL<<
(MAX_ORDER-1);
if(zone_start_paddr&~PAGE_MASK)
BUG();
检查该管理区的起始地址是否是一个页的边界。
totalpages=0;
for(i=0;i<MAX_NR_ZONES;i++){
totalpages+=size;
}
计算本存储节点中页面的个数。
realtotalpages=totalpages;
if(zholes_size)
for(i=0;i<MAX_NR_ZONES;i++)
realtotalpages-=zholes_size[i];
printk("Onnode%dtotalpages:
%lu\n",nid,
realtotalpages);
打印除空洞以外的实际页面数。
INIT_LIST_HEAD(&active_list);
INIT_LIST_HEAD(&inactive_list);
初始化循环链表。
/*
*Somearchitectures(withlotsofmemanddiscontinousmemory
*maps)
havetosearchforagoodmem_maparea:
*For
discontigmem,theconceptualmemmaparraystartsfrom
*
PAGE_OFFSET,weneedtoaligntheactualarrayontoamemmap
*boundary,sothatMAP_NRworks.
*/
map_size=
(totalpages+1)*sizeof(structpage);
if(lmem_map==(structpage*)0){
lmem_map
=(structpage*)(PAGE_OFFSET+
MAP_ALIGN((unsignedlong)lmem_map-PAGE_OFFSET));
}
给局部内存(即本节点中的内存)映射分配空间,并在sizeof(mem_map_t)边界上对齐它。
*gmap=
pgdat->node_mem_map=lmem_map;
pgdat->node_size=totalpages;
pgdat->node_start_paddr=zone_start_paddr;
pgdat->node_start_mapnr=(lmem_map-mem_map);
pgdat->nr_zones=0;
初始化本节点中的域。
/*
*Initially
allpagesarereserved-freeonesarefreed
*upby
free_all_bootmem()oncetheearlybootprocessis
*done.
*/
for(p=lmem_map;p<lmem_map+totalpages;p++){
SetPageReserved(p);
init_waitqueue_head(&p->wait);
memlist_init(&p->list);
}
仔细检查所有的页,并进行如下操作:
·
把页的使用计数(count域)置为0。
·
把页标记为保留。
·
初始化该页的等待队列。
·
初始化链表指针。
offset=lmem_map-mem_map;
变量mem_map是类型为structpages的全局稀疏矩阵。
mem_map下标的起始值取决于第一个节点的第一个管理区。
如果第一个管理区的起始地址为0,则下标就从0开始,并且与物理页面号相对应,也就是说,页面号就是mem_map的下标。
每一个管理区都有自己的映射表,存放在zone_mem_map中,每个管理区又被映射到它所在的节点node_mem_map中,而每个节点又被映射到管理全局内存的mem_map中。
在上面的这行代码中,offset表示该节点放的内存映射表在全局mem_map中的入口点(下标)。
在这里,offset为0,因为在i386上,只有一个节点。
for(j=0;j<MAX_NR_ZONES;j++){
这个循环对zone的域进行初始化。
zone_t*zone=
pgdat->node_zones+j;
unsigned
longmask;
unsigned
longsize,realsize;
realsize
=size=zones_size[j];
管理区的实际数据是存放在节点中的,因此,让指针指向正确的管理区,并获得该管理区的大小。
if
(zholes_size)
realsize-=zholes_size[j];
printk("zone(%lu):
%lupages.\n",j,size);
计算各个区的实际大小,并进行打印。
例如,在具有256MB的内存上,上面的输出为:
zone(0):
4096pages.
zone
(1):
61440pages.
zone
(2):
0pages.
这里,管理区2为0,因为只有256MB的RAM.
zone->size
=size;
zone->name
=zone_names[j];
zone->lock
=SPIN_LOCK_UNLOCKED;
zone->zone_pgdat
=pgdat;
zone->free_pages
=0;
zone->need_balance
=0;
初始化管理区中的各个域。
if
(!
size)
continue;
如果一个管理区的大小为0
pgdat->nr_zones
=j+1;
mask
=(realsize/zone_balance_ratio[j]);
if
(mask<zone_balance_min[j])
mask=zone_balance_min[j];
else
if(mask>zone_balance_max[j])
mask=zone_balance_max[j];
计算合适的平衡比率。
zone->pages_min
=mask;
zone->pages_low
=mask*2;
zone->pages_high
=mask*3;
zone->zone_mem_map
=mem_map+offset;
zone->zone_start_mapnr
=offset;
zone->zone_start_paddr
=zone_start_paddr;
设置该管理区中页面数量的几个界限,并把在全局变量mem_map中的入口点作为zone_mem_map的初值。
用全局变量mem_map的下标初始化变量zone_start_mapnr。
if
((zone_start_paddr>>PAGE_SHIFT)&
(zone_required_alignment-1))
printk("BUG:
wrongzonealignment,itwill
crash\n");
for
(i=0;i<size;i++){
structpage*page=mem_map+offset+i;
page->zone=zone;
if(j!
=ZONE_HIGHMEM)
page->virtual=__va(zone_start_paddr);
zone_start_paddr+=PAGE_SIZE;
}
对该管理区中的每一页进行处理。
首先,把structpage结构中的zone域初始化为指向该管理区(zone),如果这个管理区不是ZONE_HIGHMEM,则设置这一页的虚地址(即物理地址+PAGE_OFFSET)。
也就是说,建立起每一页物理地址到虚地址的映射。
offset
+=size;
把offset增加size,使它指向mem_map中下一个管理区的起始位置。
for
(i=0;;i++){
unsignedlongbitmap_size;
memlist_init(&zone->free_area[i].free_list);
if(i==MAX_ORDER-1){
zone->free_area[i].map=NULL;
break;
}
初始化free_area[]链表,把free_area[]中最后一个序号的位图置为NULL。
/*
*Page
buddysystemuses"index>>(i+1)",
*where"index"isatmost"size-1".
*
*Theextra
"+3"istorounddowntobyte
*size(8
bitsperbyteassumption).Thus
*weget"(size-1)>>(i+4)"asthelastbyte
*wecanaccess.
*
*The
"+1"isbecausewewanttoroundthe
*byteallocationupratherthandown.So
*weshouldhavehada"+7"beforeweshifted
*downbythree.Also,wehavetoaddoneas
*weactually_use_thelastbit(it's[0,n]
*inclusive,not[0,n[).
*
*Sowe
actuallyhad+7+1beforeweshift
*downby3.But(n+8)>>3==(n>>3)+1
*(modulooverflows,whichwedonothave).
*
*Finally,
weLONG_ALIGNbecauseallbitmap
*operationsareonlongs.
*/
bitmap_size=(size-1)
>>(i+4);
bitmap_size
=LONG_ALIGN(bitmap_size+1);
zone->free_area[i].map=(unsignedlong*)
alloc_bootmem_node(pgdat,bitmap_size);
}
计算位图的大小,然后调用alloc_bootmem_node给位图分配空间。
}
build_zonelists(pgdat);
在节点中为不同的管理区创建链表。
2.build_zonelists()函数
函数原型:
staticinlinevoidbuild_zonelists(pg_data_t
*pgdat)
代码如下:
inti,j,
k;
for(i=
0;i<=GFP_ZONEMASK;i++){
zonelist_t*zonelist;
zone_t*zone;
zonelist
=pgdat->node_zonelists+i;
memset(zonelist,
0,sizeof(*zonelist));
获得节点中指向管理区链表的域,并把它初始化为空。
j=0;
k=ZONE_NORMAL;
if
(i&__GFP_HIGHMEM)
k=
ZONE_HIGHMEM;
if
(i&__GFP_DMA)
k=
ZONE_DMA;
把当前管理区掩码与三个可用管理区掩码相“与”,获得一个管理区标识,把它用在下面的switch语句中。
switch
(k){
default:
BUG();
/*
*fallthrough:
*/
caseZONE_HIGHMEM:
zone=pgdat->node_zones+ZONE_HIGHMEM;
if(zone->size){
#ifndefCONFIG_HIGHMEM
BUG();
#endif
zonelist->zones[j++]=zone;
}
caseZONE_NORMAL:
zone=pgdat->node_zones+ZONE_NORMAL;
if(zone->size)
zonelist->zones[j++]=zone;
caseZONE_DMA:
zone=pgdat->node_zones+ZONE_DMA;
if(zone->size)
zonelist->zones[j++]=zone;
}
给定的管理区掩码指定了优先顺序,我们可以用它找到在switch语句中的入口点。
如果掩码为__GFP_DMA,管理区链表zonelist将仅仅包含DMA管理区,如果为__GFP_HIGHMEM,则管理区链表中就会依次有ZONE_HIGHMEM、ZONE_NORMAL和ZONE_DMA。
zonelist->zones[j++]
=NULL;
}
用Null结束链表。
3.mem_init()函数
这个函数由start_kernel()调用,以对管理区的分配算法进行进一步的初始化,定义于arch/i386/mm/init.c中,具体解释如下:
intcodesize,reservedpages,datasize,initsize;
inttmp;
intbad_ppro;
if(!
mem_map)
BUG();
#ifdef
CONFIG_HIGHMEM
highmem_start_page
=mem_map+highstart_pfn;
max_mapnr=
num_physpages=highend_pfn;
如果HIGHMEM被激活,就要获得HIGHMEM的起始地址和总的页面数。
#else
max_mapnr=
num_physpages=max_low_pfn;
#endif
否则,页面数就是常规内存的页面数。
high_memory
=(void*)__va(max_low_pfn*PAGE_SIZE);
获得低区内存中最后一个页面的虚地址。
/*clear
thezero-page*/
memset(empty_zero_page,0,PAGE_SIZE);
/*this
willputalllowmemoryontothefreelists*/
totalram_pages
+=free_all_bootmem();
reservedpages=0;
free_a