在start_kernel对内存的初始化中,会调用arch_setup对平台进行初始化。在该函数中,为创建一级页表,有这样的调用顺序:bootmem_init() --> bootmem_init_node() ->map_memory_bank() --> create_mapping()。
create_mapping函数的开始是一系列的检查,有意义的代码是最后的这个循环:
pgd = pgd_offset_k(addr); //一级数组中addr对应的段在init_mm->pgd的下标
end = addr + length; //计算结束地址
do {
unsigned long next = pgd_addr_end(addr, end); //获得下一段开始地址
//申请并初始化一个段
//段码,虚拟地址,结束地址,物理地址,内存类型
alloc_init_section(pgd, addr, next, phys, type);
phys += next - addr; //物理地址累加
addr = next; //地址为下一段的开始地址
} while (pgd++, addr != end); //下一段,地址不等于结束地址
可见,核心函数是alloc_init_section:
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); //直接将pdg给pmd
//addr, end, phys都是1M对齐,则直接进行段映射(大部分情况下应该是满足条件)
if (((addr | end | phys) & ~SECTION_MASK) == 0) {
pmd_t *p = pmd;
if (addr & SECTION_SIZE)
pmd++; //奇数段(段的处理总是2段一起处理的)
do {
//物理地址 | 段属性à填充进段描述符中。(2410规格书P560)
*pmd = __pmd(phys | type->prot_sect);
phys += SECTION_SIZE; //下一段(1M)
} while (pmd++, addr += SECTION_SIZE, addr != end);
//这个循环只运行一次
flush_pmd_entry(p); //同步数据进RAM(由于有cache机制,所以数据操作的时候是先保存在cache中的,这里是强制将数据从cache中刷进RAM中)
} else {
//没有按1M对齐,直接初始化二级页表。
//由于这个函数实际是调用不到的,所以这里我们就不做分析了。
alloc_init_pte(pmd, addr, end, __phys_to_pfn(phys), type);
}
}
小结:
在系统初始化的时候,会开辟内存的前16K空间来保存一级页表数组(内存一共4G,每个段为1M,共4K个段,由于ARM是32位的,所以字长为4个字节,所以保存这4K个段就需要16K内存)。
在看create_map之前,我们需要下看一下S3C2410的规格书中MMU部分一级页表的说明,我们可以知道:
1、高10位为段地址
2、19..12位未用
3、11..0 为一级页表的描述属性。
而create_map函数实际上就是根据参数指定的内存类型,将段和其属性合成为一级页表的描述符,然后保存到前16K的管理数组中。
可见,create_map和MMU其实并不神秘。