Chinaunix首页 | 论坛 | 博客
  • 博客访问: 215660
  • 博文数量: 90
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 845
  • 用 户 组: 普通用户
  • 注册时间: 2013-06-29 09:58
个人简介

兴趣是坚持一件事永不衰竭的动力

文章分类

全部博文(90)

文章存档

2019年(12)

2018年(9)

2016年(23)

2015年(43)

2013年(3)

我的朋友

分类: 嵌入式

2019-03-26 20:32:03

原文地址:http://blog.chinaunix.net/uid-26000137-id-3990647.html
linux paging init 分析

谨以此文纪念过往岁月

一.   前言

Linux中内存管理机制是一个很大的内容,在本文中,主要是来关注linux启动时,对于页表项的建立。其主要来关注paging_init这个函数,同时在其中掺杂cachetlb等内容。

二.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而困惑。为什么呢?我们以addr0xC0000000为例,pgd_index(addr) =0x600,init_mm->pgd=0x50004000,但是pgd_offset_k(addr)=0x50007000,这个是怎么得到的呢?0x50004000+0x600 =0x50004600啊,不应该是0x50007000。后来突然醍醐灌顶,顿悟了,为什么,因为这里面是指针相加,而pgdunsigned 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~0x0x5000400016k的内存则是用于ubootkernel之间的信息交互。如在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>>18R6中保存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 & 0xff000000KERNEL_START & 0x00f00000比较奇怪。

上面就是__create_page_tables,就是创建临时页表。到此你也不应该奇怪为什么lsr18bit了。

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刚好相反,不过其参数同上含义

以获取CPUmain id为例

MCR   CP15  0  R9  CR0   CR0  0

这样的话,各位看官应该明白这两个命令的含义了吧。

至于asm的规则,额,咱不看了,你可以参考http://linux.chinaunix.net/techdoc/beginner/2009/05/20/1113147.shtml在该博客中将的很详细。

上面的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;

}

在我们的linuxinitrd并没有使用,所以initrd_node无效啊。关于上面的node的理解,鄙人是这样理解的,如果有几个RAM卡槽(内有RAM)就有几个node。我们这里就只有一块ramnode0.那我们一个一个的来看上面提及的函数。

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); --linuxpmdpgd一致,此时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);   --刷新pmdentry

         } else {

                   alloc_init_pte(pmd, addr, end, __phys_to_pfn(phys), type);--创建二级页表

         }

}

上面就是创建段页表,也许上面的代码看不出什么,那下面的映射关系则很清晰了。

0x50007000~0x50007003  section0  0x50000000 ~ 0x50100000 

0x50007004~0x50007007  section1  0x50100000 ~ 0x50200000

其实是在内存0x50007000~0x50007003这四个字节中保存section0section信息。关于alloc_init_pte这个函数以后再说吧。咱玩不起了啊。

在这个函数完成时就是将物理内存0x5000000~0x57C00000创建一级section页表,在物理内存0x50007000~0x500077F0中存储着section的信息。我们再次回到bootmem_init_node这个函数中,话说map_memory_bank完成后将是bootmem_bootmap_pages

创建bootmem位图页,其中传入参数pages0x7C00,用于保存每一页的情况,因为在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所需要的byteslong对齐,其中使用一个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是对于已使用的PAGEbitmap中进行标记。未使用的标记为0,已经使用的标记为1.其中在bitmap中,每1bit代表1PAGEreserve_bootmem_node中其实是将该bitmap所在的页标记为1.boot_pfn地址为0x50493boot_pages=1.那我们来看free_bootmem_nodereserve_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,嘿嘿,这里主要是看框架对其比较特殊的地方是不管的,就好像我们刚开始学习驱动的时候,不管suspendresume一样一样的!这个函数不鸟它了。下面的函数才是正题。

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算是看完了。不知道各位看官有什么感想没?其实本文算不上是详解,算是一个粗解吧,各位看官最好边看源码,边以此文为参考,这样多有助益。

阅读(12036) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册