兴趣是坚持一件事永不衰竭的动力
分类: 嵌入式
2019-03-26 20:32:03
谨以此文纪念过往岁月
一. 前言
Linux中内存管理机制是一个很大的内容,在本文中,主要是来关注linux启动时,对于页表项的建立。其主要来关注paging_init这个函数,同时在其中掺杂cache,tlb等内容。
二.paging_init详解
这个函数看上去很简单,但是他却一点也不简单,在其中遍布了杀机,一步不慎就将陷入那不可自拔的深潭,咱们还是步步为营,一步一步来理解其中的种种。
void __init paging_init(struct meminfo *mi, struct machine_desc *mdesc)
{
void *zero_page;
build_mem_type_table(); --根据不同的arm版本,设置不同mem_type。对于该函数不做详解。
sanity_check_meminfo(mi);
prepare_page_table(mi); --创建页表前的准备
bootmem_init(mi);
devicemaps_init(mdesc);
top_pmd = pmd_off_k(0xffff0000);
/*
* allocate the zero page. Note that we count on this going ok.
*/
zero_page = alloc_bootmem_low_pages(PAGE_SIZE);
memzero(zero_page, PAGE_SIZE);
empty_zero_page = virt_to_page(zero_page);
flush_dcache_page(empty_zero_page);
}
在那linux刚启动时,系统创建了一个临时页表,那个是临时的,既然正式的要上场了,临时的当然要退休了。他不退,哼哼,就磨刀霍霍向prepare_page_table,不退也得退。
2.1 prepare_page_table
static inline void prepare_page_table(struct meminfo *mi)
{
unsigned long addr;
for (addr = 0; addr < MODULES_VADDR; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
for ( ; addr < PAGE_OFFSET; addr += PGDIR_SIZE) -- PAGE_OFFSET=0xC0000000
pmd_clear(pmd_off_k(addr));
for (addr = __phys_to_virt(mi->bank[0].start + mi->bank[0].size);
addr < VMALLOC_END; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
}
MODULES_VADDR =0xC0000000-16M,就是内核以下的映射。
VMALLOC_END=0xE0000000清除直到vmalloc区的所有的内核映射,除了第一个内存块。
其实这些都是次要的,主要看pmd_clear(pmd_off_k(addr))这个东东。
pmd_off_k查找一个虚拟地址的内核页表目录项
static inline pmd_t *pmd_off_k(unsigned long virt)
{
return pmd_off(pgd_offset_k(virt), virt);
}
static inline pmd_t *pmd_off(pgd_t *pgd, unsigned long virt)
{
return pmd_offset(pgd, virt);
}
#define pgd_index(addr) ((addr) >> PGDIR_SHIFT)
#define pgd_offset(mm, addr) ((mm)->pgd+pgd_index(addr))
#define pgd_offset_k(addr) pgd_offset(&init_mm, addr)
#define pmd_offset(dir, addr) ((pmd_t *)(dir))
#define PGDIR_SHIFT 21
在我们的开发板上init_mm->pgd的地址为0x50004000,这个是静态编译时就被定义了。曾经为了pgd_offset而困惑。为什么呢?我们以addr为0xC0000000为例,pgd_index(addr) =0x600,而init_mm->pgd=0x50004000,但是pgd_offset_k(addr)=0x50007000,这个是怎么得到的呢?0x50004000+0x600 =0x50004600啊,不应该是0x50007000。后来突然醍醐灌顶,顿悟了,为什么,因为这里面是指针相加,而pgd为unsigned long int pgd[2];每一次增加都是以8个字节增加,故pgd_offset_k(addr)为0x50007000,其中一个section的信息占有四个字节。到此先暂停一下,咱们跳到混沌初开,linux刚启动时创建临时页表的代码中。
在没有创建临时页表以及启动mmu之前CPU运行于物理地址
__create_page_tables:
pgtbl r4 --r4=0x50004000 该值被静态设置
mov r0, r4
mov r3, #0
add r6, r0, #0x4000
1: str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r6
bne 1b
上面是将0x50004000 到0x50008000这段16k的内存清零。也许大家会困惑那kernel image被复制到哪儿呢,其实kernel image其实地址为0x50008000.而0x50004000 ~ 0x50008000这段16k内存用于创建临时页表,该临时页表将在paging_init中清除。而0x50000000~0x0x50004000这16k的内存则是用于uboot与kernel之间的信息交互。如在uboot中设置的bootcmd会通过这段内存传替过来。
ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
在r10保存了处理器的信息的首地址,那在s3c6410中其为__v6_proc_info,上面的命令是将r10的地址偏移8byte后取其值,其为section的信息。
mov r6, pc, lsr #20 @ start of kernel section
r6=pc>>20,因为当前运行于物理地址,就是kernel的段地址。
orr r3, r7, r6, lsl #20 @ flags + kernel base
r3 =r7|r6<<20 在r3中存储段信息
str r3, [r4, r6, lsl #2] @ identity mapping
上面命令不知道干什么,我屏蔽掉也没有什么事,其是[r4+r6<<2] = r3,没有什么实际含义
add r0, r4, #(KERNEL_START & 0xff000000) >> 18
KERNEL_START =0xC0008000 R0=R4+0x3000=0x50007000
str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
[R0+ KERNEL_START & 0x00f00000>>18] = R3
ldr r6, =(KERNEL_END - 1)
KERNEL_END=0x50493000 R6=0x50493000-1
add r0, r0, #4
R0=R0+4
add r6, r4, r6, lsr #18
R6=R4+R6>>18在R6中保存kernel image结束地址
1: cmp r0, r6
add r3, r3, #1 << 20
strls r3, [r0], #4
bls 1b
创建section页表
映射内存第1M,在其中包含了kernel的参数
add r0, r4, #PAGE_OFFSET >> 18
orr r6, r7, #(PHYS_OFFSET & 0xff000000)
.if (PHYS_OFFSET & 0x00f00000)
orr r6, r6, #(PHYS_OFFSET & 0x00f00000)
.endif
str r6, [r0]
mov pc, lr
ENDPROC(__create_page_tables)
上面的code比较简单,有注释也就没有什么挑战行了。其中需要注意一点就是KERNEL_START & 0xff000000和KERNEL_START & 0x00f00000比较奇怪。
上面就是__create_page_tables,就是创建临时页表。到此你也不应该奇怪为什么lsr为18bit了。
Ok言归正传,咱们还是来pmd_clear吧,那个pgd_offset_k在理解是指针加就好理解很多了。
#define pmd_clear(pmdp) \
do { \
pmdp[0] = __pmd(0); \
pmdp[1] = __pmd(0); \
clean_pmd_entry(pmdp); \
} while (0)
#define __pmd(x) (x)
上面的code很清晰就是将pmd值赋值0,说白还是类似于__create_page_tables刚开始的那一段就是将0x50004000~0x50008000清零。不过这里面要涉及一个很头大的东西,就是清除pmd entry。当我的好奇的打开这个函数的定义时,我就怕了,汇编!不过神说:当你明白了汇编,你也就悟了!这时我没有看懂汇编却也悟了。
static inline void clean_pmd_entry(pmd_t *pmd)
{
const unsigned int __tlb_flag = __cpu_tlb_flags;
if (tlb_flag(TLB_DCLEAN))
asm("mcr p15, 0, %0, c7, c10, 1 @ flush_pmd"
: : "r" (pmd) : "cc");
if (tlb_flag(TLB_L2CLEAN_FR))
asm("mcr p15, 1, %0, c15, c9, 1 @ L2 flush_pmd"
: : "r" (pmd) : "cc");
}
#define __cpu_tlb_flags cpu_tlb.tlb_flags
第一个参数就让人很困惑,这个参数从哪儿跑出来的,难道是石头里蹦出来的吗,那肯定不是的,他一定是某某里出来的,其实在__v6_proc_info –>v6wbi_tlb_fns-> v6wbi_tlb_flags
#define v6wbi_tlb_flags (TLB_WB | TLB_DCLEAN | \
TLB_V6_I_FULL | TLB_V6_D_FULL | \
TLB_V6_I_PAGE | TLB_V6_D_PAGE | \
TLB_V6_I_ASID | TLB_V6_D_ASID)
默认回首,那值就在眼角下。关于上面的宏定义值的意义,额,鄙人不解,还望众高手帮忙。
从v6wbi_tlb_flags上看就知道,第一个if会被执行,而第二就就不行了,那我们先来看第一个if
asm("mcr p15, 0, %0, c7, c10, 1 @ flush_pmd"
: : "r" (pmd) : "cc");
标准的asm,这里面的牵涉的东东就有多了,一个CP15协控制器,一个是asm的规则,还有就是这个命令是干什么呢。不急不急,听我慢慢道来。
CP15是什么咱就不说了,主要看针对于其最主要的两个命令就是
1. mcr 将ARM处理器的寄存器数据写入协处理器的寄存器中。
2. mrc 将协处理器的寄存器数据读入ARM处理器的寄存器中。
先来看mcr 吧,以下面的命令为例:
MCR CP15
Op_1值永远为0,如果非零的话指令操作结果未知。
当然就是作为源寄存器的ARM寄存器,不可为PC
作为目标寄存器的协处理器的寄存器
附加的目标寄存器或源操作数寄存器,用于区分同一个编号的不同物理寄存器。当指令中不需要提供附加信息,就将C0作为,否则结果未知。
同样是提供附加信息。当指令中不需要提供附加信息,就将其指定为0或省略。
MCR CP15
而MRC刚好相反,不过其参数同上含义
以获取CPU的main id为例
MCR CP15 0 R9 CR0 CR0 0
这样的话,各位看官应该明白这两个命令的含义了吧。
至于asm的规则,额,咱不看了,你可以参考在该博客中将的很详细。
上面的code呢就是将pmd的值传替到协处理器中,那来看这个指令有什么作用。在ARM1176JZF_S 的spec中说明。
Instruction Data Function
MCR p15, 0, , c7, c10, 1 MVA Clean Data Cache Line, using MVA
清除数据缓冲区Line使用的装换的虚拟地址。
关于cache的问题以后在仔细的研究一下,这里就不扩展了主要是我也没有搞定里面的一堆繁杂的东东。
到此我们才算是将prepare_page_table看完了。不过这个才是正餐前的一道小小开胃菜而已啊,额,下面进入正题。
2.2 bootmem_init
这个函数其实是将物理内存进行映射,并创建页表。这个也是一个巨麻烦的函数
void __init bootmem_init(struct meminfo *mi)
{
unsigned long memend_pfn = 0;
int node, initrd_node;
memcpy(&meminfo, mi, sizeof(meminfo));
initrd_node = check_initrd(mi); --本文不涉及
for_each_node(node) {
unsigned long end_pfn = bootmem_init_node(node, mi);
if (node == 0)
reserve_node_zero(NODE_DATA(node));
if (node == initrd_node)
bootmem_reserve_initrd(node);
if (end_pfn > memend_pfn)
memend_pfn = end_pfn;
}
sparse_init();
for_each_node(node)
bootmem_free_node(node, mi);
high_memory = __va(memend_pfn << PAGE_SHIFT);
max_pfn = max_low_pfn = memend_pfn - PHYS_PFN_OFFSET;
}
在我们的linux中initrd并没有使用,所以initrd_node无效啊。关于上面的node的理解,鄙人是这样理解的,如果有几个RAM卡槽(内有RAM)就有几个node。我们这里就只有一块ram那node为0.那我们一个一个的来看上面提及的函数。
2.2.1 bootmem_init_node
在看下面的code前,我再次的说明一下
mi->bank.start = 0x5000000;
mi->bank.size = 0x7C00000;
mi->bank.node = 0;
static unsigned long __init bootmem_init_node(int node, struct meminfo *mi)
{
unsigned long start_pfn, end_pfn, boot_pfn;
unsigned int boot_pages;
pg_data_t *pgdat;
int i;
start_pfn = -1UL;
end_pfn = 0;
for_each_nodebank(i, mi, node) {
struct membank *bank = &mi->bank[i];
unsigned long start, end;
start = bank_pfn_start(bank); --start=0x50000
end = bank_pfn_end(bank); --end=0x57C000
if (start_pfn > start)
start_pfn = start;
if (end_pfn < end)
end_pfn = end;
map_memory_bank(bank); --创建页表
}
if (end_pfn == 0)
return end_pfn;
boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn);
boot_pfn = find_bootmap_pfn(node, mi, boot_pages);
node_set_online(node);
pgdat = NODE_DATA(node);
init_bootmem_node(pgdat, boot_pfn, start_pfn, end_pfn);
for_each_nodebank(i, mi, node) {
struct membank *bank = &mi->bank[i];
free_bootmem_node(pgdat, bank_phys_start(bank), bank_phys_size(bank));
memory_present(node, bank_pfn_start(bank), bank_pfn_end(bank));
}
reserve_bootmem_node(pgdat, boot_pfn << PAGE_SHIFT,
boot_pages << PAGE_SHIFT, BOOTMEM_DEFAULT);
return end_pfn;
}
static inline void map_memory_bank(struct membank *bank)
{
struct map_desc map;
map.pfn = bank_pfn_start(bank); --0x500000
map.virtual = __phys_to_virt(bank_phys_start(bank)); --0xC0000000
map.length = bank_phys_size(bank); --0x7C00000
map.type = MT_MEMORY;
create_mapping(&map);
}
void __init create_mapping(struct map_desc *md)
{
unsigned long phys, addr, length, end;
const struct mem_type *type;
pgd_t *pgd;
type = &mem_types[md->type];
addr = md->virtual & PAGE_MASK; --addr=0xC0000000
phys = (unsigned long)__pfn_to_phys(md->pfn); --0x50000000
length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));
pgd = pgd_offset_k(addr); --pgd=0x50007000
end = addr + length; --end=0xC7C00000
do {
unsigned long next = pgd_addr_end(addr, end); --next =addr+0x200000
alloc_init_section(pgd, addr, next, phys, type);
phys += next - addr;
addr = next;
} while (pgd++, addr != end);
}
#define pgd_addr_end(addr, end) \
({ unsigned long __boundary = ((addr) + PGDIR_SIZE) & PGDIR_MASK; \
(__boundary - 1 < (end) - 1)? __boundary: (end); \
})
static void __init alloc_init_section(pgd_t *pgd, unsigned long addr,
unsigned long end, unsigned long phys,
const struct mem_type *type)
{
pmd_t *pmd = pmd_offset(pgd, addr); --在linux中pmd与pgd一致,此时pmd的地址为pgd地址即0x50004000
if (((addr | end | phys) & ~SECTION_MASK) == 0) { --如果满足section对齐创建一级页表
pmd_t *p = pmd;
if (addr & SECTION_SIZE)
pmd++;
do {
*pmd = __pmd(phys | type->prot_sect); --在该地址上保存section地址以及信息。
phys += SECTION_SIZE;
} while (pmd++, addr += SECTION_SIZE, addr != end);
上面的的循环会循环两次
flush_pmd_entry(p); --刷新pmd的entry
} else {
alloc_init_pte(pmd, addr, end, __phys_to_pfn(phys), type);--创建二级页表
}
}
上面就是创建段页表,也许上面的代码看不出什么,那下面的映射关系则很清晰了。
0x50007000~0x50007003 section0 0x50000000 ~ 0x50100000
0x50007004~0x50007007 section1 0x50100000 ~ 0x50200000
其实是在内存0x50007000~0x50007003这四个字节中保存section0的section信息。关于alloc_init_pte这个函数以后再说吧。咱玩不起了啊。
在这个函数完成时就是将物理内存0x5000000~0x57C00000创建一级section页表,在物理内存0x50007000~0x500077F0中存储着section的信息。我们再次回到bootmem_init_node这个函数中,话说map_memory_bank完成后将是bootmem_bootmap_pages。
创建bootmem位图页,其中传入参数pages为0x7C00,用于保存每一页的情况,因为在RAM内存管理以PAGE为单位,所以这里计算所需PAGE数
unsigned long __init bootmem_bootmap_pages(unsigned long pages)
{
unsigned long bytes = bootmap_bytes(pages);
return PAGE_ALIGN(bytes) >> PAGE_SHIFT;
}
static unsigned long __init bootmap_bytes(unsigned long pages)
{
unsigned long bytes = (pages + 7) / 8;
return ALIGN(bytes, sizeof(long));
}
这是bootmem_bootmap_pages返回值为1
boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn); -- boot_pages=1
boot_pfn = find_bootmap_pfn(node, mi, boot_pages); --查询bootmap的页
下面这个函数还是比较好懂的特别是我们的node只有一个
static unsigned int __init
find_bootmap_pfn(int node, struct meminfo *mi, unsigned int bootmap_pages)
{
unsigned int start_pfn, i, bootmap_pfn;
start_pfn = PAGE_ALIGN(__pa(&_end)) >> PAGE_SHIFT; --前面的page被占用了
bootmap_pfn = 0;
for_each_nodebank(i, mi, node) {
struct membank *bank = &mi->bank[i];
unsigned int start, end;
start = bank_pfn_start(bank);
end = bank_pfn_end(bank);
if (end < start_pfn)
continue;
if (start < start_pfn)
start = start_pfn;
if (end <= start)
continue;
if (end - start >= bootmap_pages) {
bootmap_pfn = start;
break;
}
}
if (bootmap_pfn == 0)
BUG();
return bootmap_pfn;
}
其实上面的函数就是查找出_end后第一个page,没有呢办法啊,因为前面的页都被占用了,
我们以_end=0x50493000为例。那我们为了bootmem的位图信息保留的页为0x50493000 ~0x50494000。其boot_pfn值为0x0x50493。下面来看init_bootmem_node初始化node
init_bootmem_node->init_bootmem_core
static unsigned long __init init_bootmem_core(bootmem_data_t *bdata,
unsigned long mapstart, unsigned long start, unsigned long end)
{
unsigned long mapsize;
bdata->node_bootmem_map = phys_to_virt(PFN_PHYS(mapstart)); --0xC0493000
bdata->node_min_pfn = start; --0x50000
bdata->node_low_pfn = end; --0x57C00
link_bootmem(bdata);
mapsize = bootmap_bytes(end - start); --计算bitmap所需要的bytes,long对齐,其中使用一个bit用于标示PAGE的使用情况
memset(bdata->node_bootmem_map, 0xff, mapsize); --将值设置为0xFF
return mapsize;
}
这个很重要,以后要开辟内存还要从这个链表中把bootmem_data_t摘下
static void __init link_bootmem(bootmem_data_t *bdata)
{
struct list_head *iter;
list_for_each(iter, &bdata_list) {
bootmem_data_t *ent;
ent = list_entry(iter, bootmem_data_t, list);
if (bdata->node_min_pfn < ent->node_min_pfn)
break;
}
list_add_tail(&bdata->list, iter);
}
还是继续bootmem_init_node函数
for_each_nodebank(i, mi, node) {
struct membank *bank = &mi->bank[i];
free_bootmem_node(pgdat, bank_phys_start(bank), bank_phys_size(bank)); --将所有的页标记为0
}
reserve_bootmem_node(pgdat, boot_pfn << PAGE_SHIFT,boot_pages << PAGE_SHIFT, BOOTMEM_DEFAULT);
上面的code是对于已使用的PAGE在bitmap中进行标记。未使用的标记为0,已经使用的标记为1.其中在bitmap中,每1bit代表1PAGE。reserve_bootmem_node中其实是将该bitmap所在的页标记为1.其boot_pfn地址为0x50493,boot_pages=1.那我们来看free_bootmem_node和reserve_bootmem_node的实现,其实这两个函数实现是比较简单的。这里就不拓展了。
到这里就将bootmem_init_node函数看完了,下面还是来继续bootmem_init中的reserve_node_zero(NODE_DATA(node));这段code。
2.2.2 reserve_node_zero
将node0中已经使用PAGE标记。
void __init reserve_node_zero(pg_data_t *pgdat)
{
unsigned long res_size = 0;
reserve_bootmem_node(pgdat, __pa(&_stext), &_end - &_stext,BOOTMEM_DEFAULT); --标记内核存储的内存。0x50008~0x050493
/*
* Reserve the page tables. These are already in use,
* and can only be in node 0.
*/
reserve_bootmem_node(pgdat, __pa(swapper_pg_dir),PTRS_PER_PGD * sizeof(pgd_t), BOOTMEM_DEFAULT); --标示一级页表存储的内存 0x50004~0x50008,其中swapper_pg_dir在静态编译时设定好值为0x50004000
}
2.2.3 bootmem_free_node
该函数也是一个巨大的函数,咱们还是一点一点的来解析他。
static void __init bootmem_free_node(int node, struct meminfo *mi)
{
unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];
unsigned long start_pfn, end_pfn;
pg_data_t *pgdat = NODE_DATA(node);
int i;
start_pfn = pgdat->bdata->node_min_pfn; --0x50000
end_pfn = pgdat->bdata->node_low_pfn; --0x57C00
memset(zone_size, 0, sizeof(zone_size)); --将zone区设置为0
memset(zhole_size, 0, sizeof(zhole_size)); --将空洞区初始化
zone_size[0] = end_pfn - start_pfn; --0x7C00
zhole_size[0] = zone_size[0];
for_each_nodebank(i, mi, node)
zhole_size[0] -= bank_pfn_size(&mi->bank[i]); --在这里zhole_size[0]=0
arch_adjust_zones(node, zone_size, zhole_size);
free_area_init_node(node, zone_size, start_pfn, zhole_size);
}
下面来看主要的函数free_area_init_node。
void __paginginit free_area_init_node(int nid, unsigned long *zones_size,
unsigned long node_start_pfn, unsigned long *zholes_size)
{
pg_data_t *pgdat = NODE_DATA(nid);
pgdat->node_id = nid; -- 0
pgdat->node_start_pfn = node_start_pfn; --0x50000
calculate_node_totalpages(pgdat, zones_size, zholes_size); --计算该node下的所有页数
alloc_node_mem_map(pgdat);
free_area_init_core(pgdat, zones_size, zholes_size);
}
2.2.3.1 calculate_node_totalpages
static void __meminit calculate_node_totalpages(struct pglist_data *pgdat,
unsigned long *zones_size, unsigned long *zholes_size)
{
unsigned long realtotalpages, totalpages = 0;
enum zone_type i;
for (i = 0; i < MAX_NR_ZONES; i++)
totalpages += zone_spanned_pages_in_node(pgdat->node_id, i,zones_size);
pgdat->node_spanned_pages = totalpages;
realtotalpages = totalpages;
for (i = 0; i < MAX_NR_ZONES; i++)
realtotalpages -=zone_absent_pages_in_node(pgdat->node_id, i,zholes_size);
pgdat->node_present_pages = realtotalpages;
}
由于该文的设定这里的totalpages= realtotalpages=0x7C00,嘿嘿,这里主要是看框架对其比较特殊的地方是不管的,就好像我们刚开始学习驱动的时候,不管suspend和resume一样一样的!这个函数不鸟它了。下面的函数才是正题。
2.2.3.2 alloc_node_mem_map
这个函数就是开辟存储page信息的内存。我们仅仅有page是否被使用的信息是不够的,我们还必须要有每一页的详细信息。
static void __init_refok alloc_node_mem_map(struct pglist_data *pgdat)
{
if (!pgdat->node_spanned_pages)
return;
if (!pgdat->node_mem_map) {
unsigned long size, start, end;
struct page *map;
start = pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES - 1); --0x50000
end = pgdat->node_start_pfn + pgdat->node_spanned_pages; --0x57C00
end = ALIGN(end, MAX_ORDER_NR_PAGES); -- MAX_ORDER_NR_PAGES=1<<11 end = 0x58000
size = (end - start) * sizeof(struct page); -- sizeof(struct page) =0x20 size=0x100000
map = alloc_remap(pgdat->node_id, size);
if (!map)
map = alloc_bootmem_node(pgdat, size);
pgdat->node_mem_map = map + (pgdat->node_start_pfn - start);
}
if (pgdat == NODE_DATA(0)) {
mem_map = NODE_DATA(0)->node_mem_map;
}
}
上面的函数看起来比较晕,不要晕,人还是要活下去的。其实上面的本质还是开辟内存,用于存储PAGE信息。在这里我们有设定alloc_remap(pgdat->node_id, size)这个函数为NULL。呵呵,下面就是内存开辟的正主alloc_bootmem_node。这个函数我们这里也不扩展了,只要知道其原理就是从刚才那个bitmap中找出一段bit被设置为0的所对应的页。这里就是0x50494~0x50594这段页号被用于保存页信息。所以mem_map =0xC0494000。其物理地址为0x50494000.到这里alloc_node_mem_map是完了。不过这里还挖了一个大大的坑。
2.2.3.3 free_area_init_core
这个函数就是初始化zone的信息。这个zone也就是传说中dma区,normal区和highmem区。不过这里设定整个内存区都是dma区,而其余两个区的大小均为0.关于这个函数我还是不太清楚其具体是怎么实现的。故留下。
static void __paginginit free_area_init_core(struct pglist_data *pgdat,
unsigned long *zones_size, unsigned long *zholes_size)
{
enum zone_type j;
int nid = pgdat->node_id;
unsigned long zone_start_pfn = pgdat->node_start_pfn;
int ret;
pgdat_resize_init(pgdat);
pgdat->nr_zones = 0;
init_waitqueue_head(&pgdat->kswapd_wait);
pgdat->kswapd_max_order = 0;
pgdat_page_cgroup_init(pgdat);
for (j = 0; j < MAX_NR_ZONES; j++) {
struct zone *zone = pgdat->node_zones + j;
unsigned long size, realsize, memmap_pages;
enum lru_list l;
size = zone_spanned_pages_in_node(nid, j, zones_size);
realsize = size - zone_absent_pages_in_node(nid, j,
zholes_size);
/*
* Adjust realsize so that it accounts for how much memory
* is used by this zone for memmap. This affects the watermark
* and per-cpu initialisations
*/
memmap_pages =
PAGE_ALIGN(size * sizeof(struct page)) >> PAGE_SHIFT;
if (realsize >= memmap_pages) {
realsize -= memmap_pages;
printk(KERN_DEBUG
" %s zone: %lu pages used for memmap\n",
zone_names[j], memmap_pages);
} else
printk(KERN_WARNING
" %s zone: %lu pages exceeds realsize %lu\n",
zone_names[j], memmap_pages, realsize);
/* Account for reserved pages */
if (j == 0 && realsize > dma_reserve) {
realsize -= dma_reserve;
printk(KERN_DEBUG " %s zone: %lu pages reserved\n",
zone_names[0], dma_reserve);
}
if (!is_highmem_idx(j))
nr_kernel_pages += realsize;
nr_all_pages += realsize;
zone->spanned_pages = size;
zone->present_pages = realsize;
//printk("size %lx realsize %lx memmap_pages %lx dma_reserve %lx nr_kernel_pages %lx nr_all_pages %lx \n",
// size,realsize,memmap_pages,dma_reserve,nr_kernel_pages,nr_all_pages);
#ifdef CONFIG_NUMA
zone->node = nid;
zone->min_unmapped_pages = (realsize*sysctl_min_unmapped_ratio)
/ 100;
zone->min_slab_pages = (realsize * sysctl_min_slab_ratio) / 100;
#endif
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;
if (!size)
continue;
set_pageblock_order(pageblock_default_order());
setup_usemap(pgdat, zone, size);
ret = init_currently_empty_zone(zone, zone_start_pfn,
size, MEMMAP_EARLY);
BUG_ON(ret);
memmap_init(size, nid, j, zone_start_pfn);
zone_start_pfn += size;
}
}
其实到这里bootmem_init可以说是看完了,不过在这里还留下不少遗憾。有不少函数具体是如何实现的仍然没有去理解。
2.3 devicemaps_init
将设备的物理地址映射到虚拟内核空间中。为了不产生遗憾我们还是硬着头皮把这些东西看下去。
static void __init devicemaps_init(struct machine_desc *mdesc)
{
struct map_desc map;
unsigned long addr;
void *vectors;
vectors = alloc_bootmem_low_pages(PAGE_SIZE); --开辟矢量表
for (addr = VMALLOC_END; addr; addr += PGDIR_SIZE) --清除剩下的pmd区
pmd_clear(pmd_off_k(addr));
map.pfn = __phys_to_pfn(virt_to_phys(vectors)); --映射到最高地址
map.virtual = 0xffff0000;
map.length = PAGE_SIZE;
map.type = MT_HIGH_VECTORS;
create_mapping(&map);
if (!vectors_high()) {
map.virtual = 0;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
}
if (mdesc->map_io)
mdesc->map_io(); --执行平台的IO映射。这个比较简单其实现类似上面的mapping
local_flush_tlb_all();
flush_cache_all();
}
到此paging_init算是看完了。不知道各位看官有什么感想没?其实本文算不上是详解,算是一个粗解吧,各位看官最好边看源码,边以此文为参考,这样多有助益。