linux内存管理分析二 linux 内存管理
为建立内存管理系统,在内核初始化过程中调用了下面几个函数:
init/main.c
asmlinkagevoid__initstart_kernel(void)
{
......
初始化持久映射与临时映射的一些信息,后面持久映射和临时映射一节将详细讲解
page_address_init();
setup_arch是特定于体系架构的函数,负责初始化自举分配器和内核页表等。
setup_arch(&command_line);
......
初始化per_cpu机制的一些结构,将.data.percpu段中的数据拷贝到每个CPU的数据段中
setup_per_cpu_areas();
......
建立结点和内存域之间的关系
build_all_zonelists(NULL);
......
停用自举分配器bootmem,迁移到实际的内存管理中,初始化slab分配器,初始化进程虚拟地址空间管理结构
mm_init();
......
为每一个内存区域分配per_cpu_pageset结构并初始化其成员
setup_per_cpu_pageset();
......
}
【start_kernel--->setup_arch】
arch/arm/kernel/setup.c
void__initsetup_arch(char**cmdline_p)
{
structmachine_desc*mdesc;
内核参数可以通过平坦设备树或者tags由bootloader传递给内核。
每一个机器平台都由一个structmachine_desc结构来描述,内核所支持的所有平台对应的machine_desc结构都包含在段.init.arch.info的__arch_info_begin到__tagtable_end之间。但每一个平台都有其唯一的机器码machine_arch_type,可通过机器码在段.init.arch.info中找到对应的平台描述结构。函数setup_machine_tags就是根据机器码找到对应的平台描述结构,并且分析内核参数中内存相关的信息,用以初始化内存块管理结构membank。
mdesc=setup_machine_fdt(__atags_pointer);
if(!mdesc)
mdesc=setup_machine_tags(machine_arch_type);
machine_desc=mdesc;
machine_name=mdesc->name;
根据mdesc->dma_zone_size设置DMA区域的大小arm_dma_zone_size,和DMA区域的结束地址arm_dma_limit
setup_dma_zone(mdesc);
结构structmm_struct管理进程的虚拟地址空间,所有内核线程都使用共同的地址空间,因为他们都是用相同的地址映射,这个地址空间由init_mm来描述。_text和_etext表示内核镜像代码段的其实和结束位置,_etext和_edata之间是已初始化数据段,_edata到_end是未初始化数据段等,_end之后便是堆区。
init_mm.start_code=(unsignedlong)_text;
init_mm.end_code=(unsignedlong)_etext;
init_mm.end_data=(unsignedlong)_edata;
init_mm.brk=(unsignedlong)_end;
内核命令行参数在函数setup_machine_tags获取并保存在了boot_command_line中
strlcpy(cmd_line,boot_command_line,COMMAND_LINE_SIZE);
*cmdline_p=cmd_line;
分析命令行参数,主要关注一些与内存相关的东西
parse_early_param();
将内存块按从小到大排序
sort(&meminfo.bank,meminfo.nr_banks,sizeof(meminfo.bank[0]),meminfo_cmp,NULL);
扫描各个内存块,检测低端内存的最大值arm_lowmem_limit,设置高端内存起始值的虚拟地址high_memory
sanity_check_meminfo();
将所有内存块添加到结构memblock的memory区中,将已使用的内存添加到reserved区中去。
arm_memblock_init(&meminfo,mdesc);
创建内核页表,初始化自举分配器
paging_init(mdesc);
内核中将许多物理资源用structresource结构来管理,下面函数就是将IO内存作为resource注册到内核
request_standard_resources(mdesc);
......
如果内核命令行中有预留用于内核crash是的转存空间,就将这些存储空间标记为已分配reserve_crashkernel();
......
}
【start_kernel--->setup_arch--->setup_machine_tags】
arch/arm/kernel/setup.c
staticstructmachine_desc*__initsetup_machine_tags(unsignedintnr)
{
structtag*tags=(structtag*)&init_tags;
structmachine_desc*mdesc=NULL,*p;
char*from=default_command_line;
init_tags.mem.start=PHYS_OFFSET;
下面循环根据机器号在段.init.arch.info中寻找对应的machine_desc结构
for_each_machine_desc(p)
if(nr==p->nr){
printk("Machine:%sn",p->name);
mdesc=p;
break;
}
......
Bootloader传入的参数地址存放在__atags_pointer中
if(__atags_pointer)
tags=phys_to_virt(__atags_pointer);
elseif(mdesc->atag_offset)
tags=(void*)(PAGE_OFFSET+mdesc->atag_offset);
......
内核参数是由structtag来管理,其中第一个tag类型必然是ATAG_CORE
if(tags->hdr.tag!=ATAG_CORE){
......
tags=(structtag*)&init_tags;内核提供的一个默认参数列表
}
函数mdesc->fixup中一般会获取内存块的信息
if(mdesc->fixup)
mdesc->fixup(tags,&from,&meminfo);
if(tags->hdr.tag==ATAG_CORE){
如果内存块已经初始化,就将参数列表中关于内存的参数标记为ATAG_NONE
if(meminfo.nr_banks!=0)
squash_mem_tags(tags);
将参数列表拷贝到一个静态数组atags_copy中
save_atags(tags);
分析内核参数,后面细讲
parse_tags(tags);
}
将解析出来的内核命令行信息拷贝到静态数组boot_command_line中。在内核启动期间用了很多静态存储空间,它们前面缀有__initdata,像这样的空间在内核启动起来后将被释放
strlcpy(boot_command_line,from,COMMAND_LINE_SIZE);
returnmdesc;
}
【start_kernel--->setup_arch--->setup_machine_tags--->parse_tags】
arch/arm/kernel/setup.c
staticvoid__initparse_tags(conststructtag*t)
{
遍历参数列表中每一个参数结构
for(;t->hdr.size;t=tag_next(t))
if(!parse_tag(t))
......
}
【start_kernel--->setup_arch--->setup_machine_tags--->parse_tags--->parse_tag】
arch/arm/kernel/setup.c
staticint__initparse_tag(conststructtag*tag)
{
externstructtagtable__tagtable_begin,__tagtable_end;
structtagtable*t;
参数类型多种多样解析方式也各不相同,所有针对每一种参数类型都有一个对应的解析函数,这些解析函数和其参数类型由结构structtagtable来管理。这些结构都存放在段.init.tagtable的__tagtable_begin和__tagtable_end之间。
for(t=&__tagtable_begin;t<&__tagtable_end;t++)
if(tag->hdr.tag==t->tag){
t->parse(tag);
break;
}
returnt<&__tagtable_end;
}
参数ATAG_MEM的解析函数定义如下:
arch/arm/kernel/setup.c
staticint__initparse_tag_mem32(conststructtag*tag)
{
returnarm_add_memory(tag->u.mem.start,tag->u.mem.size);
}
__tagtable(ATAG_MEM,parse_tag_mem32);
【parse_tag_mem32--->arm_add_memory】
从ATAG_MEM参数中获取内存信息,初始化内存块管理结构
int__initarm_add_memory(phys_addr_tstart,unsignedlongsize)
{
structmembank*bank=&meminfo.bank[meminfo.nr_banks];
if(meminfo.nr_banks>=NR_BANKS){
printk(KERN_CRIT"NR_BANKStoolow,"
"ignoringmemoryat0x%08llxn",(longlong)start);
return-EINVAL;
}
size-=start&~PAGE_MASK;
bank->start=PAGE_ALIGN(start);
#ifndefCONFIG_LPAE
if(bank->start+size<bank->start){
size=ULONG_MAX-bank->start;
}
#endif
bank->size=size&PAGE_MASK;
if(bank->size==0)
return-EINVAL;
meminfo.nr_banks++;
return0;
}
【start_kernel--->setup_arch--->sanity_check_meminfo】
arch/arm/mm/mmu.c
void__initsanity_check_meminfo(void)
{
inti,j,highmem=0;
遍历每一个内存块
for(i=0,j=0;i<meminfo.nr_banks;i++){
structmembank*bank=&meminfo.bank[j];
*bank=meminfo.bank[i];
if(bank->start>ULONG_MAX)
highmem=1;
#ifdefCONFIG_HIGHMEM
vmalloc_min在文件arch/arm/mm/mmu.c中定义,它定义了高端内存的起始位置。PAGE_OFFSET是物理位置的起始处。如果内存块起始位置大于vmalloc_min,表示存在高端内存。如果内存扩展超过32位,它就有可能小于PAGE_OFFSET。
if(__va(bank->start)>=vmalloc_min||
__va(bank->start)<(void*)PAGE_OFFSET)
highmem=1;
标志该内存块是否处于高端内存中
bank->highmem=highmem;
如果该内存块部分处于高端内存中,部分处于低端内存中就将其分为两个内存块。
if(!highmem&&__va(bank->start)<vmalloc_min&&
bank->size>vmalloc_min-__va(bank->start)){
if(meminfo.nr_banks>=NR_BANKS){
......
}else{
memmove(bank+1,bank,
(meminfo.nr_banks-i)*sizeof(*bank));
meminfo.nr_banks++;
i++;
bank[1].size-=vmalloc_min-__va(bank->start);
bank[1].start=__pa(vmalloc_min-1)+1;
bank[1].highmem=highmem=1;
j++;
}
bank->size=vmalloc_min-__va(bank->start);
}
#else如果不支持高端内存做如下处理
bank->highmem=highmem;
if(highmem){
......
continue;
}
if(__va(bank->start)>=vmalloc_min||
__va(bank->start)<(void*)PAGE_OFFSET){
......
continue;
}
if(__va(bank->start+bank->size)>vmalloc_min||
__va(bank->start+bank->size)<__va(bank->start)){
unsignedlongnewsize=vmalloc_min-__va(bank->start);
......
bank->size=newsize;
}
#endif
求出低端内存的最大地址值
if(!bank->highmem&&bank->start+bank->size>arm_lowmem_limit)
arm_lowmem_limit=bank->start+bank->size;
j++;
}
......
meminfo.nr_banks=j;记录内存块数
计算高端内存起始地址,该值不一定等于vmalloc_min,因为可能没有高端内存
high_memory=__va(arm_lowmem_limit-1)+1;
memblock_set_current_limit(arm_lowmem_limit);
}
【start_kernel--->setup_arch--->arm_memblock_init】
arch/arm/mm/init.c
void__initarm_memblock_init(structmeminfo*mi,structmachine_desc*mdesc)
{
inti;
将所有内存模块添加到memblock.memory中。结构体memblock在文件mm/memblock.c中定义,如下:
structmemblockmemblock__initdata_memblock={
.memory.regions=memblock_memory_init_regions,
......
.reserved.regions=memblock_reserved_init_regions,
......
};
for(i=0;i<mi->nr_banks;i++)
memblock_add(mi->bank[i].start,mi->bank[i].size);
如果内核在rom中运行就只将它的数据段开始的空间添加到memblock.reserved中,否则将内核代码段开始的空间添加到memblock.reserved中。
#ifdefCONFIG_XIP_KERNEL
memblock_reserve(__pa(_sdata),_end-_sdata);
#else
memblock_reserve(__pa(_stext),_end-_stext);
#endif
#ifdefCONFIG_BLK_DEV_INITRD
如果支持initrd启动,此时它还不在内存中
if(phys_initrd_size&&
!memblock_is_region_memory(phys_initrd_start,phys_initrd_size)){
pr_err("INITRD:0x%08lx+0x%08lxisnotamemoryregion-disablinginitrdn",
phys_initrd_start,phys_initrd_size);
phys_initrd_start=phys_initrd_size=0;
}
if(phys_initrd_size&&
memblock_is_region_reserved(phys_initrd_start,phys_initrd_size)){
pr_err("INITRD:0x%08lx+0x%08lxoverlapsin-usememoryregion-disablinginitrdn",
phys_initrd_start,phys_initrd_size);
phys_initrd_start=phys_initrd_size=0;
}
为inird镜像预留一块存储区
if(phys_initrd_size){
memblock_reserve(phys_initrd_start,phys_initrd_size);
initrd_start=__phys_to_virt(phys_initrd_start);
initrd_end=initrd_start+phys_initrd_size;
}
#endif
为内核页表分配存储空间
arm_mm_memblock_reserve();
......
}
【start_kernel--->setup_arch--->paging_init】
arch/arm/mm/mmu.c
void__initpaging_init(structmachine_desc*mdesc)
{
void*zero_page;
memblock_set_current_limit(arm_lowmem_limit);
根据不同的arm版本初始化不同的mem_types,该结构存放着页表的一些属性相关信息
build_mem_type_table();
将除了内核镜像、主内存所在虚拟地址之外全部内存的页表清除掉
prepare_page_table();
为低端内存的所有区域创建内核页表
map_lowmem();
对DMA区域重新创建页表
dma_contiguous_remap();
为设备IO空间和中断向量表创建页表,并刷新TLB和缓存
devicemaps_init(mdesc);
获取持久映射区页表的位置,存储在pkmap_page_table中
kmap_init();
高64K是用于存放中断向量表的
top_pmd=pmd_off_k(0xffff0000);
分配一个0页,该页用于写时复制机制。
zero_page=early_alloc(PAGE_SIZE);
初始化自举内存分配,后面有专门章节讲解
bootmem_init();
empty_zero_page=virt_to_page(zero_page);
刷新数据缓存
__flush_dcache_page(NULL,empty_zero_page);
}
【start_kernel--->setup_arch--->paging_init--->prepare_page_table】
arch/arm/mm/mmu.c
staticinlinevoidprepare_page_table(void)
{
unsignedlongaddr;
phys_addr_tend;
模块加载的范围应该是在MODULES_VADDR到MODULES_END之间,MODULES_VADDR在文件arch/arm/include/asm/memory.h中定义,如下:
#defineMODULES_VADDR(PAGE_OFFSET-8*1024*1024)
对于arm处理器,该区域在正常内核虚拟地址之下。清除存储空间在MODULES_VADDR之下的页表项。
for(addr=0;addr<MODULES_VADDR;addr+=PMD_SIZE)
pmd_clear(pmd_off_k(addr));
#ifdefCONFIG_XIP_KERNEL
addr=((unsignedlong)_etext+PMD_SIZE-1)&PMD_MASK;
#endif
for(;addr<PAGE_OFFSET;addr+=PMD_SIZE)
pmd_clear(pmd_off_k(addr));
第一个存储区存放的是内核镜像,跳过该区域,即不清除这个区域的页表
end=memblock.memory.regions[0].base+memblock.memory.regions[0].size;
if(end>=arm_lowmem_limit)
end=arm_lowmem_limit;
for(addr=__phys_to_virt(end);
addr<VMALLOC_START;addr+=PMD_SIZE)
pmd_clear(pmd_off_k(addr));
}
【start_kernel--->setup_per_cpu_areas】
每CPU变量(per-cpu-variable)是一种内核的同步机制。每CPU变量分为静态变量和动态变量。静态变量用DEFINE_PER_CPU(type,name)来定义(CPU变量name,类型为type)。这些静态变量包含在段.data.percpu中。下面函数就是为每个CPU分配一部分空间用于动态分配per_cpu变量。并为每个CPU拷贝一份.data.percup段中的内容。
mm/percpu.c
void__initsetup_per_cpu_areas(void)
{
unsignedlongdelta;
unsignedintcpu;
intrc;
rc=pcpu_embed_first_chunk(PERCPU_MODULE_RESERVE,
PERCPU_DYNAMIC_RESERVE,PAGE_SIZE,NULL,
pcpu_dfl_fc_alloc,pcpu_dfl_fc_free);
if(rc<0)
panic("Failedtoinitializepercpuareas.");
数组__per_cpu_offset中存储了每个CPU,per_cpu变量区域的偏移,在以后访问per_cpu变量时将用到。
delta=(unsignedlong)pcpu_base_addr-(unsignedlong)__per_cpu_start;
for_each_possible_cpu(cpu)
__per_cpu_offset[cpu]=delta+pcpu_unit_offsets[cpu];
}
【start_kernel--->build_all_zonelists】
mm/page_alloc.c
void__refbuild_all_zonelists(void*data)
{
设置current_zonelist_order,它决定备用内存域在pglist_data->node_zonelists中的排列顺序
set_zonelist_order();
if(system_state==SYSTEM_BOOTING){
初始化备用结点内存域列表pglist_data->node_zonelists。
__build_all_zonelists(NULL);
mminit_verify_zonelist();打印一些调试信息
当前进程的进程描述结构task_struct中有一个成员mems_allowed,该成员是nodemask_t类型的结构体,这个结构体在文件include/linux/nodemask.h中定义如下:
typedefstruct{DECLARE_BITMAP(bits,MAX_NUMNODES);}nodemask_t;
这个结构其实就是定义了一个位域,每个位对应一个内存结点,如果置1表示该节点内存可用。在下面函数中将这个位域中每个位置1。
cpuset_init_current_mems_allowed();
}else{
#ifdefCONFIG_MEMORY_HOTPLUG
if(data)
setup_zone_pageset((structzone*)data);
#endif
如果内核不是出于启动过程中,就停止CPU的运行来初始化备用结点内存域列表
stop_machine(__build_all_zonelists,NULL,NULL);
}
计算总的空闲内存数
vm_total_pages=nr_free_pagecache_pages();
内核通过标记页的可移动类型来避免产生过多碎片,如果可用内存太少就标记page_group_by_mobility_disabled以禁用这种反碎片机制。
if(vm_total_pages<(pageblock_nr_pages*MIGRATE_TYPES))
page_group_by_mobility_disabled=1;
else
page_group_by_mobility_disabled=0;
......
}
【start_kernel--->build_all_zonelists--->__build_all_zonelists】
static__init_refokint__build_all_zonelists(void*data)
{
intnid;
intcpu;
......
遍历每一个内存结点,初始化他们的备用结点内存域列表
for_each_online_node(nid){
pg_data_t*pgdat=NODE_DATA(nid);
build_zonelists(pgdat);
build_zonelist_cache(pgdat);
}
遍历每一个CPU,初始化他们的per_cpu缓存
for_each_possible_cpu(cpu){
setup_pageset(&per_cpu(boot_pageset,cpu),0);
......
}
return0;
}
【start_kernel--->build_all_zonelists--->__build_all_zonelists--->build_zonelists
】
structzonelist是备用结点内存域列表管理结构,该结构在文件include/linux/mmzone.h中定义,如下:
structzonelist{
指针zlcache_ptr通常指向本结构中的zlcache成员
structzonelist_cache*zlcache_ptr;
MAX_ZONES_PER_ZONELIST表示所有节点内存域总和
structzoneref_zonerefs[MAX_ZONES_PER_ZONELIST+1];
#ifdefCONFIG_NUMA
structzonelist_cachezlcache;
#endif
};
structzoneref{
structzone*zone;指向内存域管理结构
intzone_idx;内存域所在结点id
};
structzonelist_cache{
存储所有内存域对应结点号
unsignedshortz_to_n[MAX_ZONES_PER_ZONELIST];
fullzones是所有内存域的一个位图,如果内存域对应位置1,表示这个内存域已没有可用内存
DECLARE_BITMAP(fullzones,MAX_ZONES_PER_ZONELIST);
unsignedlonglast_full_zap;
};
备用结点内存域列表中内存域排列原则是:按分配代价由小到大排列。在节点间应当先排列本地结点的内存域后排其他结点内存域,在节点内先排列高端内存、然后是普通内存、再然后是DMA内存。
在结点描述结构中,节点的备用结点内存域列表定义如下:
typedefstructpglist_data{
......
structzonelistnode_zonelists[MAX_ZONELISTS];
......
}
在多处理器系统中MAX_ZONELISTS定义为2,node_zonelists[0]中排列本结点内存域,node_zonelists[1]中排列其他备用结点内存域。如果在找node_zonelists[0]中不到可用内存就到node_zonelists[1]中去分配。
mm/page_alloc.c
staticvoidbuild_zonelists(pg_data_t*pgdat)
{
intj,node,load;
enumzone_typei;
nodemask_tused_mask;
intlocal_node,prev_node;
structzonelist*zonelist;
intorder=current_zonelist_order;
初始化备用结点内存域
for(i=0;i<MAX_ZONELISTS;i++){
zonelist=pgdat->node_zonelists+i;
zonelist->_zonerefs[0].zone=NULL;
zonelist->_zonerefs[0].zone_idx=0;
}
local_node=pgdat->node_id;
load=nr_online_nodes;
prev_node=local_node;
nodes_clear(used_mask);
memset(node_order,0,sizeof(node_order));
j=0;
找一个与结点pgdat距离最近的结点
while((node=find_next_best_node(local_node,&used_mask))>=0){
intdistance=node_distance(local_node,node);
if(distance>RECLAIM_DISTANCE)
zone_reclaim_mode=1;
if(distance!=node_distance(local_node,prev_node))
node_load[node]=load;
prev_node=node;
load--;
if(order==ZONELIST_ORDER_NODE)
将找到的最佳结点内存域排列到pgdat的备用内存域列表node_zonelists[1]中
build_zonelists_in_node_order(pgdat,node);
else
node_order[j++]=node;/*rememberorder*/
}
if(order==ZONELIST_ORDER_ZONE){
/*calculatenodeorder--i.e.,DMAlast!*/
build_zonelists_in_zone_order(pgdat,j);
}
将结点自己的内存域排列到自己的备用结点内存域node_zonelists[0]中
build_thisnode_zonelists(pgdat);
}
【start_kernel--->build_all_zonelists--->__build_all_zonelists--->build_zonelists
--->build_zonelists_in_node_order】
mm/page_alloc.c
staticvoidbuild_zonelists_in_node_order(pg_data_t*pgdat,intnode)
{
intj;
structzonelist*zonelist;
函数build_thisnode_zonelists和本函数最大的区别就在于这里取的是node_zonelists[0],而在函数build_thisnode_zonelists中取的是node_zonelists[1]。
zonelist=&pgdat->node_zonelists[0];
找到第一个空的位置
for(j=0;zonelist->_zonerefs[j].zone!=NULL;j++)
;
将结点node的所有内存区域排列在,j开始的备用列表中
j=build_zonelists_node(NODE_DATA(node),zonelist,j,
MAX_NR_ZONES-1);
zonelist->_zonerefs[j].zone=NULL;
zonelist->_zonerefs[j].zone_idx=0;
}
【start_kernel--->build_all_zonelists--->__build_all_zonelists--->build_zonelists
--->build_zonelists_in_node_order--->build_zonelists_node】
mm/page_alloc.c
staticintbuild_zonelists_node(pg_data_t*pgdat,structzonelist*zonelist,
intnr_zones,enumzone_typezone_type)
{
structzone*zone;
BUG_ON(zone_type>=MAX_NR_ZONES);
zone_type++;
do{
这里的zone_type,上层函数传入的是MAX_NR_ZONES,循环中做的就是将内存域按ZONE_HIGHMEM--->ZONE_NORMAL--->ZONE_DMA的顺序排列在备用内存列表中
zone_type--;
zone=pgdat->node_zones+zone_type;
if(populated_zone(zone)){
函数zoneref_set_zone要做的就是用zone和zone所在结点id来初始化_zonerefs。
zoneref_set_zone(zone,
&zonelist->_zonerefs[nr_zones++]);
找出整个备用列表中内存区域类型值最大的的内存区域
check_highest_zone(zone_type);
}
}while(zone_type);
returnnr_zones;
}
【start_kernel--->build_all_zonelists--->__build_all_zonelists--->build_zonelist_cache】
mm/page_alloc.c
staticvoidbuild_zonelist_cache(pg_data_t*pgdat)
{
structzonelist*zonelist;
structzonelist_cache*zlc;
structzoneref*z;
zonelist=&pgdat->node_zonelists[0];
让结构structzonelist的zlcache_ptr指针成员指向它自己的zlcache成员
zonelist->zlcache_ptr=zlc=&zonelist->zlcache;
bitmap_zero(zlc->fullzones,MAX_ZONES_PER_ZONELIST);
for(z=zonelist->_zonerefs;z->zone;z++)
zlc->z_to_n[z-zonelist->_zonerefs]=zonelist_node_idx(z);
}
【start_kernel--->mm_init】
init/main.c
staticvoid__initmm_init(void)
{
为page_cgroup相关结构分配存储空间
page_cgroup_init_flatmem();
将自举分配器bootmem中的空闲空间释放到伙伴系统中,并停用自举分配器切换到伙伴系统中
mem_init();
启用slab分配器,后面另起章节专门讲解
kmem_cache_init();
一些per_cpu结构在系统启动期间做了临时初始化,这里将进行一些更完善的处理
percpu_init_late();
pgtable_cache_init();为空
初始化非连续内存分配。关于非线性内存区域的分配,后面专门有章节来讲解
vmalloc_init();
}
【start_kernel--->mm_init--->mem_init】
arch/arm/mm/init.c
void__initmem_init(void)
{
unsignedlongreserved_pages,free_pages;
structmemblock_region*reg;
inti;
......
系统中可能有多个存储块,每个存储块之间可能有没用的区域,将这些区域在bootmem中标记为空闲
free_unused_memmap(&meminfo);
将bootmem中空闲区域释放到伙伴系统中去
totalram_pages+=free_all_bootmem();
#ifdefCONFIG_SA1111
PHYS_PFN_OFFSET是物理存储位置对应的页帧号。将PHYS_PFN_OFFSET到内核页表swapper_pg_dir之间的区域释放到伙伴系统中去
totalram_pages+=free_area(PHYS_PFN_OFFSET,
__phys_to_pfn(__pa(swapper_pg_dir)),NULL);
#endif
将空闲的高端内存区域释放到伙伴系统中去
free_highpages();
reserved_pages=free_pages=0;
统计所有内存块中的空闲页和非空闲页。
for_each_bank(i,&meminfo){
structmembank*bank=&meminfo.bank[i];
unsignedintpfn1,pfn2;
structpage*page,*end;
pfn1=bank_pfn_start(bank);
pfn2=bank_pfn_end(bank);
page=pfn_to_page(pfn1);
end=pfn_to_page(pfn2-1)+1;
do{
if(PageReserved(page))
reserved_pages++;
elseif(!page_count(page))
free_pages++;
page++;
}while(page<end);
}
......
}
【start_kernel--->
更多阅读
二次指数平滑法实例分析 二次指数平滑法spss
表中第③栏是我国1978-2002年全社会客运量的资料,据期绘制散点图,见下图,可以看出,各年的客运量资料基本呈线性趋势,但在几个不同的时期直线有不同的斜率,因此考虑用变参数线性趋势模型进行预测。具体步骤如下: 表 我国1978-2002年全社
在夜场泡美女并带回家的技术分析二 _灵 柬埔寨金边夜场美女
4、时间安排。其实从认识到带姑娘回家,也就一晚上的时间,充其量几个小时,所以这几个小时一定要充分的安排好,不能有一点浪费。我建议10点到达D厅。占据一个比较好的位置,开始观察。对于那些很早就来到D厅的姑娘还是就不要惦记了,因为我觉
中国与日本——两种经济发展模式的比较分析二 经济发展模式
三、两种发展模式的比较分析从资本形成的角度讲,日本完全依靠内生的资本,而中国兼采用内生和外来的手段。外资在中国的资本形成方面在2004年大约为17%;而在日本,这一比例连1%都不到。这一现象的出现是因为日本的金融体制比较健全,能够把
二年级下册语文期末试卷质量分析 二年级下册语文试卷
二年级下册语文期末试卷质量分析城北小学 徐丽丽一、试卷评价二年级语文试卷分基础知识、阅读和写话三大部分。基础知识分分字、词、句、篇四大块。语文是一门综合性很强的学科,涉及的层面和内容很多,知识点也分布到方方面面。本次
小学二年级上册数学期中试卷分析 二年级数学特色作业
2011-2012学年度上学期小学数学期中试卷分析前言:一学期的一半过去了,作为对本学期期中的一次检测,我对前段时间的教学工作有了一个全面的了解。学校一直要求我们结合学生成绩,对自己的教学工作进行反思。我的感触很多,更何况教学工作是