linux内存管理分析 二,linux内存管理分析【二】
為建立內存管理系統,在內核初始化過程中調用了下面幾個函數:
init/main.c
asmlinkage?void?__init?start_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?__init?setup_arch(char?**cmdline_p)
{
struct?machine_desc?*mdesc;
內核參數可以通過平坦設備樹或者tags由bootloader傳遞給內核。
每一個機器平臺都由一個struct?machine_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);
結構struct?mm_struct?管理進程的虛擬地址空間,所有內核線程都使用共同的地址空間,因為他們都是用相同的地址映射,這個地址空間由init_mm來描述。_text?和_etext表示內核鏡像代碼段的其實和結束位置,_etext和_edata之間是已初始化數據段,_edata到_end是未初始化數據段等,_end之后便是堆區。
init_mm.start_code?=?(unsigned?long)?_text;
init_mm.end_code???=?(unsigned?long)?_etext;
init_mm.end_data???=?(unsigned?long)?_edata;
init_mm.brk????????=?(unsigned?long)?_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);
內核中將許多物理資源用struct?resource結構來管理,下面函數就是將IO內存作為resource注冊到內核
request_standard_resources(mdesc);
......
如果內核命令行中有預留用于內核crash是的轉存空間,就將這些存儲空間標記為已分配????????reserve_crashkernel();
......
}
【start_kernel--->setup_arch--->setup_machine_tags】
arch/arm/kernel/setup.c
static?struct?machine_desc?*?__init?setup_machine_tags(unsigned?int?nr)
{
struct?tag?*tags?=?(struct?tag?*)&init_tags;
struct?machine_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:?%s\n",?p->name);
mdesc?=?p;
break;
}
......
Bootloader傳入的參數地址存放在__atags_pointer中
if?(__atags_pointer)
tags?=?phys_to_virt(__atags_pointer);
else?if?(mdesc->atag_offset)
tags?=?(void?*)(PAGE_OFFSET?+?mdesc->atag_offset);
......
內核參數是由struct?tag來管理,其中第一個tag類型必然是ATAG_CORE
if?(tags->hdr.tag?!=?ATAG_CORE)?{
......
tags?=?(struct?tag?*)&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);
return?mdesc;
}
【start_kernel--->setup_arch--->setup_machine_tags--->parse_tags】
arch/arm/kernel/setup.c
static?void?__init?parse_tags(const?struct?tag?*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
static?int?__init?parse_tag(const?struct?tag?*tag)
{
extern?struct?tagtable?__tagtable_begin,?__tagtable_end;
struct?tagtable?*t;
參數類型多種多樣解析方式也各不相同,所有針對每一種參數類型都有一個對應的解析函數,這些解析函數和其參數類型由結構struct?tagtable來管理。這些結構都存放在段.init.tagtable的__tagtable_begin和__tagtable_end之間。
for?(t?=?&__tagtable_begin;?t?
if?(tag->hdr.tag?==?t->tag)?{
t->parse(tag);
break;
}
return?t?
}
參數ATAG_MEM的解析函數定義如下:
arch/arm/kernel/setup.c
static?int?__init?parse_tag_mem32(const?struct?tag?*tag)
{
return?arm_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?__init?arm_add_memory(phys_addr_t?start,?unsigned?long?size)
{
struct?membank?*bank?=?&meminfo.bank[meminfo.nr_banks];
if?(meminfo.nr_banks?>=?NR_BANKS)?{
printk(KERN_CRIT?"NR_BANKS?too?low,?"
"ignoring?memory?at?0x%08llx\n",?(long?long)start);
return?-EINVAL;
}
size?-=?start?&?~PAGE_MASK;
bank->start?=?PAGE_ALIGN(start);
#ifndef?CONFIG_LPAE
if?(bank->start?+?size?start)?{
size?=?ULONG_MAX?-?bank->start;
}
#endif
bank->size?=?size?&?PAGE_MASK;
if?(bank->size?==?0)
return?-EINVAL;
meminfo.nr_banks++;
return?0;
}
【start_kernel--->setup_arch--->sanity_check_meminfo】
arch/arm/mm/mmu.c
void?__init?sanity_check_meminfo(void)
{
int?i,?j,?highmem?=?0;
遍歷每一個內存塊
for?(i?=?0,?j?=?0;?i?
struct?membank?*bank?=?&meminfo.bank[j];
*bank?=?meminfo.bank[i];
if?(bank->start?>?ULONG_MAX)
highmem?=?1;
#ifdef?CONFIG_HIGHMEM
vmalloc_min在文件arch/arm/mm/mmu.c中定義,它定義了高端內存的起始位置。PAGE_OFFSET是物理位置的起始處。如果內存塊起始位置大于vmalloc_min,表示存在高端內存。如果內存擴展超過32位,它就有可能小于PAGE_OFFSET。
if?(__va(bank->start)?>=?vmalloc_min?||
__va(bank->start)?
highmem?=?1;
標志該內存塊是否處于高端內存中
bank->highmem?=?highmem;
如果該內存塊部分處于高端內存中,部分處于低端內存中就將其分為兩個內存塊。
if?(!highmem?&&?__va(bank->start)?
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)?
......
continue;
}
if?(__va(bank->start?+?bank->size)?>?vmalloc_min?||
__va(bank->start?+?bank->size)?start))?{
unsigned?long?newsize?=?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?__init?arm_memblock_init(struct?meminfo?*mi,?struct?machine_desc?*mdesc)
{
int?i;
將所有內存模塊添加到memblock.memory中。結構體memblock在文件mm/memblock.c中定義,如下:
struct?memblock?memblock?__initdata_memblock?=?{
.memory.regions?????????=?memblock_memory_init_regions,
......
.reserved.regions???????=?memblock_reserved_init_regions,
......
};
for?(i?=?0;?i?nr_banks;?i++)
memblock_add(mi->bank[i].start,?mi->bank[i].size);
如果內核在rom中運行就只將它的數據段開始的空間添加到memblock.reserved中,否則將內核代碼段開始的空間添加到memblock.reserved中。
#ifdef?CONFIG_XIP_KERNEL
memblock_reserve(__pa(_sdata),?_end?-?_sdata);
#else
memblock_reserve(__pa(_stext),?_end?-?_stext);
#endif
#ifdef?CONFIG_BLK_DEV_INITRD
如果支持initrd啟動,此時它還不在內存中
if?(phys_initrd_size?&&
!memblock_is_region_memory(phys_initrd_start,?phys_initrd_size))?{
pr_err("INITRD:?0x%08lx+0x%08lx?is?not?a?memory?region?-?disabling?initrd\n",
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%08lx?overlaps?in-use?memory?region?-?disabling?initrd\n",
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?__init?paging_init(struct?machine_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
static?inline?void?prepare_page_table(void)
{
unsigned?long?addr;
phys_addr_t?end;
模塊加載的范圍應該是在MODULES_VADDR到MODULES_END之間,MODULES_VADDR在文件arch/arm/include/asm/memory.h中定義,如下:
#define?MODULES_VADDR???????????(PAGE_OFFSET?-?8*1024*1024)
對于arm處理器,該區域在正常內核虛擬地址之下。清除存儲空間在MODULES_VADDR之下的頁表項。
for?(addr?=?0;?addr?
pmd_clear(pmd_off_k(addr));
#ifdef?CONFIG_XIP_KERNEL
addr?=?((unsigned?long)_etext?+?PMD_SIZE?-?1)?&?PMD_MASK;
#endif
for?(?;?addr?
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?
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?__init?setup_per_cpu_areas(void)
{
unsigned?long?delta;
unsigned?int?cpu;
int?rc;
rc?=?pcpu_embed_first_chunk(PERCPU_MODULE_RESERVE,
PERCPU_DYNAMIC_RESERVE,?PAGE_SIZE,?NULL,
pcpu_dfl_fc_alloc,?pcpu_dfl_fc_free);
if?(rc?
panic("Failed?to?initialize?percpu?areas.");
數組?__per_cpu_offset中存儲了每個CPU,per_cpu變量區域的偏移,在以后訪問per_cpu變量時將用到。
delta?=?(unsigned?long)pcpu_base_addr?-?(unsigned?long)__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?__ref?build_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中定義如下:
typedef?struct?{?DECLARE_BITMAP(bits,?MAX_NUMNODES);?}?nodemask_t;
這個結構其實就是定義了一個位域,每個位對應一個內存結點,如果置1表示該節點內存可用。在下面函數中將這個位域中每個位置1。
cpuset_init_current_mems_allowed();
}?else?{
#ifdef?CONFIG_MEMORY_HOTPLUG
if?(data)
setup_zone_pageset((struct?zone?*)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?
page_group_by_mobility_disabled?=?1;
else
page_group_by_mobility_disabled?=?0;
......
}
【start_kernel--->build_all_zonelists--->__build_all_zonelists】
static?__init_refok?int?__build_all_zonelists(void?*data)
{
int?nid;
int?cpu;
......
遍歷每一個內存結點,初始化他們的備用結點內存域列表
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);
......
}
return?0;
}
【start_kernel--->build_all_zonelists--->__build_all_zonelists--->build_zonelists
】
struct?zonelist是備用結點內存域列表管理結構,該結構在文件include/linux/mmzone.h中定義,如下:
struct?zonelist?{
指針zlcache_ptr通常指向本結構中的zlcache成員
struct?zonelist_cache?*zlcache_ptr;
MAX_ZONES_PER_ZONELIST表示所有節點內存域總和
struct?zoneref?_zonerefs[MAX_ZONES_PER_ZONELIST?+?1];
#ifdef?CONFIG_NUMA
struct?zonelist_cache?zlcache;
#endif
};
struct?zoneref?{
struct?zone?*zone;???指向內存域管理結構
int?zone_idx;????內存域所在結點id
};
struct?zonelist_cache?{
存儲所有內存域對應結點號
unsigned?short?z_to_n[MAX_ZONES_PER_ZONELIST];
fullzones是所有內存域的一個位圖,如果內存域對應位置1,表示這個內存域已沒有可用內存
DECLARE_BITMAP(fullzones,?MAX_ZONES_PER_ZONELIST);
unsigned?long?last_full_zap;
};
備用結點內存域列表中內存域排列原則是:按分配代價由小到大排列。在節點間應當先排列本地結點的內存域后排其他結點內存域,在節點內先排列高端內存、然后是普通內存、再然后是DMA內存。
在結點描述結構中,節點的備用結點內存域列表定義如下:
typedef?struct?pglist_data?{
......
struct?zonelist?node_zonelists[MAX_ZONELISTS];
......
}
在多處理器系統中MAX_ZONELISTS定義為2,node_zonelists[0]中排列本結點內存域,node_zonelists[1]中排列其他備用結點內存域。如果在找node_zonelists[0]中不到可用內存就到node_zonelists[1]中去分配。
mm/page_alloc.c
static?void?build_zonelists(pg_data_t?*pgdat)
{
int?j,?node,?load;
enum?zone_type?i;
nodemask_t?used_mask;
int?local_node,?prev_node;
struct?zonelist?*zonelist;
int?order?=?current_zonelist_order;
初始化備用結點內存域
for?(i?=?0;?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)?{
int?distance?=?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;?/*?remember?order?*/
}
if?(order?==?ZONELIST_ORDER_ZONE)?{
/*?calculate?node?order?--?i.e.,?DMA?last!?*/
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
static?void?build_zonelists_in_node_order(pg_data_t?*pgdat,?int?node)
{
int?j;
struct?zonelist?*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
static?int?build_zonelists_node(pg_data_t?*pgdat,?struct?zonelist?*zonelist,
int?nr_zones,?enum?zone_type?zone_type)
{
struct?zone?*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);
return?nr_zones;
}
【start_kernel--->build_all_zonelists--->__build_all_zonelists--->build_zonelist_cache】
mm/page_alloc.c
static?void?build_zonelist_cache(pg_data_t?*pgdat)
{
struct?zonelist?*zonelist;
struct?zonelist_cache?*zlc;
struct?zoneref?*z;
zonelist?=?&pgdat->node_zonelists[0];
讓結構struct?zonelist的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
static?void?__init?mm_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?__init?mem_init(void)
{
unsigned?long?reserved_pages,?free_pages;
struct?memblock_region?*reg;
int?i;
......
系統中可能有多個存儲塊,每個存儲塊之間可能有沒用的區域,將這些?區域在bootmem中標記為空閑
free_unused_memmap(&meminfo);
將bootmem中空閑區域釋放到伙伴系統中去
totalram_pages?+=?free_all_bootmem();
#ifdef?CONFIG_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)?{
struct?membank?*bank?=?&meminfo.bank[i];
unsigned?int?pfn1,?pfn2;
struct?page?*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++;
else?if?(!page_count(page))
free_pages++;
page++;
}?while?(page?
}
......
}
【start_kernel--->
總結
以上是生活随笔為你收集整理的linux内存管理分析 二,linux内存管理分析【二】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux系统迁移的重要配置文件,myl
- 下一篇: qt 最小化到托盘linux,Qt窗口最