本函数的分析很难具体,因为涉及了很多arm的处理器型号和每个型号对应的cache和write buffer的工作方式,这片文章只是做简单的记录,方便以后了解更深后回来再来完善这个函数。
这个函数的调用过程如:start_kernel()->setup_arch()->paging_init()->memtable_init().传递的struct meminfo mi是一个全局变量,里面记录了整个系统所有bank的信息。我们来大概看看这个函数的代码。
void __init memtable_init(struct meminfo *mi)
{
struct map_desc *init_maps, *p, *q;//要注意这里很重要的结构-struct map_desc,这个结构体的内容等下会细讲的。
unsigned long address = 0;
int i;
build_mem_type_table();//这个函数可以说来是我最含糊的函数,我们来浅析一下吧。
static void __init build_mem_type_table(void)
{
struct cachepolicy *cp;
unsigned int cr = get_cr();//就是获得cp15的c1寄存器的内容
int cpu_arch = cpu_architecture();//获得寄存器的架构版本
int i;
#if defined(CONFIG_CPU_DCACHE_DISABLE)
if (cachepolicy > CPOLICY_BUFFERED)
cachepolicy = CPOLICY_BUFFERED;
#elif defined(CONFIG_CPU_DCACHE_WRITETHROUGH)
if (cachepolicy > CPOLICY_WRITETHROUGH)
cachepolicy = CPOLICY_WRITETHROUGH;
#endif//这里涉及到一个全局变量cachepolicy,我们可以称为cache政策,其实可以看出以上几行都是和系统配置选项有关的。
if (cpu_arch < CPU_ARCH_ARMv5) {
if (cachepolicy >= CPOLICY_WRITEALLOC)
cachepolicy = CPOLICY_WRITEBACK;
ecc_mask = 0;//这里要注意的是在ARMv5之前的型号是的以及描述符里面是没有保护标志位设置的。
}
if (cpu_arch <= CPU_ARCH_ARMv5) {//这里看出是对armv5或者是之前的型号的判断。
for (i = 0; i < ARRAY_SIZE(mem_types); i++) {//struct mem_types这个结构的成员要注意,请看如下:
//prot_l1:如果这个存储空间的一级描述符存放着二级描述符页表基址的话,这个空间用的就是二级页表映射,这时
//prot_l1就是该空间的一级描述符。
//prot_sect:这个空间的一级描述符保存的是物理地址的基址,说明这段空间是一级页表映射的,那么prot_sect就是
//这个空间的段描述符。
//prot_pte:这个其实就是这个空间的二级描述符。
//对于这样的mem_types的存储种类有7种,刚好对应了这个数组的每一个元素。
if (mem_types[i].prot_l1)
mem_types[i].prot_l1 |= PMD_BIT4;
if (mem_types[i].prot_sect)
mem_types[i].prot_sect |= PMD_BIT4;
}
}
/*
* ARMv6 and above have extended page tables.
*/
if (cpu_arch >= CPU_ARCH_ARMv6 && (cr & CR_XP)) {//#define CR_XP (1 << 23),这样是涉及到c1的附加内容。
/*
* bit 4 becomes XN which we must clear for the
* kernel memory mapping.
*/
mem_types[MT_MEMORY].prot_sect &= ~PMD_BIT4;
mem_types[MT_ROM].prot_sect &= ~PMD_BIT4;
/*
* Mark cache clean areas read only from SVC mode
* and no access from userspace.
*/
mem_types[MT_MINICLEAN].prot_sect |= PMD_SECT_APX|PMD_SECT_AP_WRITE;//这里对AP=01
mem_types[MT_CACHECLEAN].prot_sect |= PMD_SECT_APX|PMD_SECT_AP_WRITE;//同上
}
cp = &cache_policies[cachepolicy];
if (cpu_arch >= CPU_ARCH_ARMv5) {
mem_types[MT_LOW_VECTORS].prot_pte |= cp->pte & PTE_CACHEABLE;//根据当前系统cache政策设置
//各种存储空间类型,如果可以使用cache就在二级描述符CB=10
mem_types[MT_HIGH_VECTORS].prot_pte |= cp->pte & PTE_CACHEABLE;
} else {
mem_types[MT_LOW_VECTORS].prot_pte |= cp->pte;
mem_types[MT_HIGH_VECTORS].prot_pte |= cp->pte;
mem_types[MT_MINICLEAN].prot_sect &= ~PMD_SECT_TEX(1);
}
mem_types[MT_LOW_VECTORS].prot_l1 |= ecc_mask;
mem_types[MT_HIGH_VECTORS].prot_l1 |= ecc_mask;
mem_types[MT_MEMORY].prot_sect |= ecc_mask | cp->pmd;//这两句和上两句的区别在于多与上了cp->pmd,这个内容肯定是
mem_types[MT_ROM].prot_sect |= cp->pmd;//一级描述符所没有的。
for (i = 0; i < 16; i++) {
unsigned long v = pgprot_val(protection_map[i]);//protection_val在声明时被初始化,预定义了16个Linux
//版本也描述符中的控制位域设置。
v &= (~(PTE_BUFFERABLE|PTE_CACHEABLE)) | cp->pte;//这个是核心语句,为什么这么做,不懂。
protection_map[i] = __pgprot(v);
}
pgprot_kernel = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | L_PTE_WRITE |L_PTE_EXEC | cp->pte);
switch (cp->pmd) {
case PMD_SECT_WT:
mem_types[MT_CACHECLEAN].prot_sect |= PMD_SECT_WT;
break;
case PMD_SECT_WB:
case PMD_SECT_WBWA:
mem_types[MT_CACHECLEAN].prot_sect |= PMD_SECT_WB;
break;
}
printk("Memory policy: ECC %sabled, Data cache %s/n", ecc_mask ? "en" : "dis", cp->policy);
}
以上所有的操作都是为了给mem_types这个结构体中的各种类型中的页表参数添加上我们的要求,主要是一级页表,二级页表,ap(访问权限控制);至于domain是利用系统初始化时的值,不用我们再进行干预。
系统的domain类型一共有四种,kernel——0;user——1;io——2
现在回到memtable_init()函数中。
init_maps = p = alloc_bootmem_low_pages(PAGE_SIZE);//又在申请一页空闲空间用来上面所说的重要的结构体mep_desc
#ifdef CONFIG_XIP_KERNEL
p->physical = CONFIG_XIP_PHYS_ADDR & PMD_MASK;
p->virtual = (unsigned long)&_stext & PMD_MASK;
p->length = ((unsigned long)&_etext - p->virtual + ~PMD_MASK) & PMD_MASK;
p->type = MT_ROM;
p ++;
#endif
for (i = 0; i < mi->nr_banks; i++) {
if (mi->bank[i].size == 0)
continue;
p->physical = mi->bank[i].start;
p->virtual = __phys_to_virt(p->physical);
p->length = mi->bank[i].size;
p->type = MT_MEMORY;
p ++;
}//你会发现map_desc结构体的分量有如下几个:
physical:保存着存储空间映射的物理起始地址。
virtual:这个就是相对上面说的转换后的虚拟起始地址。
length:就是映射空间的大小。
type:这个存储映射空间的类型。
。。。。。
起始对于有特殊配置的我不多讲,一般情况下都是按照上面的for循环来创建这个结构体的。我们通过传递进来的struct meminfo mi全局
变量,根据系统BANK的信息为这些bank创建映射。
q = init_maps;
do {
if (address < q->virtual || q == p) {//就是从虚拟0地址开始,如果发现虚拟地址不再上面创建的范围内的话
clear_mapping(address);//我们来看下它的具体函数
static inline void clear_mapping(unsigned long virt)
{
pmd_clear(pmd_offset(pgd_offset_k(virt), virt));//#define pgd_offset_k(addr) pgd_offset(&init_mm, addr),我们在看看
//&init_mm 这个是如下定义的:struct mm_struct init_mm = INIT_MM(init_mm);
//pdg_offset的宏定义。#define pgd_offset(mm, addr) ((mm)->pgd+pgd_index(addr))//由上面的初始化可以知道(mm)->pgd
//=swapper_pg_dir这个是一级映射页表的虚拟起始地址,我们再看看pdg_index()的宏就一目了然了。
//#define pgd_index(addr) ((addr) >> PGDIR_SHIFT)会发现这里其实就是求出这个虚拟地址的段号,因为Linux一次是使用两个段
//的,所以这里用2MB。((mm)->pgd+pgd_index(addr))这个运算就是要求出对应两个描述的虚拟地址。
//最后翻译出来就是就是pmd_clear(刚才求得的描述符地址),一下就是具体的pmd_clear()函数,我们看函数来分析。
//#define pmd_clear(pmdp) /
// do { /
// pmdp[0] = __pmd(0); / //一下两句就是把对应的描述符项清零。
// pmdp[1] = __pmd(0); /
// clean_pmd_entry(pmdp); / //如果当前arm处理器是通过清空数据cache来清空TLB,那么就把这两个描述符条目所在
//地址对应的数据cache块清空。
// } while (0)
}
address += PGDIR_SIZE;//#define PGDIR_SIZE (1UL << PGDIR_SHIFT)其中PGDIR_SHIFT=21.这样就是以2段
//也就是2MB为的大小增加的。
} else {//如果虚拟地址在上述创建结构体的范围内的话
create_mapping(q);//这个是为该存储空间创建一二级页表,我们来仔细看看这部分代码。
static void __init create_mapping(struct map_desc *md)
{
unsigned long virt, length;
int prot_sect, prot_l1, domain;
pgprot_t prot_pte;
long off;
if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {//个人认为中断向量是映射到用户空间的,就是要分是高
//端的还是低端的,如果要创建高端中断向量,就要先设置,设置后vectors_base=0xFFFF0000这个就是高端中断向量的虚
//地址,如果没有设置的话,我们是映射低端中断向量,地址为:0x0。其实如果都不是着两个虚拟地址,又在用户空间的话
//我们就提示出错。
printk(KERN_WARNING "BUG: not creating mapping for " "0x%08lx at 0x%08lx in user region/n",
md->physical, md->virtual);
return;
}
if ((md->type == MT_DEVICE || md->type == MT_ROM) &&
md->virtual >= PAGE_OFFSET && md->virtual < VMALLOC_END) {//说明有些类型的空间是不属于这个虚拟地址范围
//的,这个时候就又要进行报错。
printk(KERN_WARNING "BUG: mapping for 0x%08lx at 0x%08lx ""overlaps vmalloc space/n",
md->physical, md->virtual);
}
domain = mem_types[md->type].domain;
prot_pte = __pgprot(mem_types[md->type].prot_pte);
prot_l1 = mem_types[md->type].prot_l1 | PMD_DOMAIN(domain);
prot_sect = mem_types[md->type].prot_sect | PMD_DOMAIN(domain);
virt = md->virtual;
off = md->physical - virt;
length = md->length;
if (mem_types[md->type].prot_l1 == 0 &&(virt & 0xfffff || (virt + off) & 0xfffff || (virt + length) & 0xfffff)){//一级页表无效,后面又确定为有效的虚拟地址,则我们确定有误。
printk(KERN_WARNING "BUG: map for 0x%08lx at 0x%08lx can not "
"be mapped using pages, ignoring./n",
md->physical, md->virtual);
return;
}
while ((virt & 0xfffff || (virt + off) & 0xfffff) && length >= PAGE_SIZE) {//物理起始地址和虚拟起始地址都不是按段对齐,同时lenght>一页的,这样我们就选择按页管理。
alloc_init_page(virt, virt + off, prot_l1, prot_pte);//我们来看看这个具体的函数啦。
static inline void alloc_init_page(unsigned long virt, unsigned long phys, unsigned int prot_l1, pgprot_t prot)
{
pmd_t *pmdp;
pte_t *ptep;
pmdp = pmd_offset(pgd_offset_k(virt), virt);//这个就是根据虚拟地址找到这个段对应的一级页表的页表条目。
if (pmd_none(*pmdp)) {//这里判断这个页表条目的内容为0
unsigned long pmdval;
ptep = alloc_bootmem_low_pages(2 * PTRS_PER_PTE * sizeof(pte_t));//为二级页表分配空间
pmdval = __pa(ptep) | prot_l1;//这里存放着二级页表存放的物理地址和一级页表的其他控制信息
pmdp[0] = __pmd(pmdval);//这里存放的是linux的二级页表
pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t));//这里存放的是硬件的二级页表
flush_pmd_entry(pmdp);//这里是刷新对应的数据cache和TLB。
}
ptep = pte_offset_kernel(pmdp, virt);
set_pte(ptep, pfn_pte(phys >> PAGE_SHIFT, prot));
}现在从函数返回到create_mapping函数来。
virt += PAGE_SIZE;//更新,因为刚才做了一页的虚拟地址映射
length -= PAGE_SIZE;//同上。
}//上面的while可以看出如果起始位置不是按段对齐的,我们先进行页管理。
while (length >= (PGDIR_SIZE / 2)) {//如果是段的话,
alloc_init_section(virt, virt + off, prot_sect);//很明显这里没有创建二级页表,只是简单地在一级页表项目附上一段物理基址。
virt += (PGDIR_SIZE / 2);
length -= (PGDIR_SIZE / 2);
}//这个while只是作为一级页表的虚拟映射。
}//这样我们就讲完了create_maping()函数了,我们回到了memtable_init()。
address = q->virtual + q->length;//更新分配后的虚拟地址。
address = (address + PGDIR_SIZE - 1) & PGDIR_MASK;//这里是对更新后的虚拟地址进行段对齐调整。
q ++;
}
} while (address != 0);
init_maps->physical = virt_to_phys(init_maps);
init_maps->virtual = 0xffff0000;
init_maps->length = PAGE_SIZE;
init_maps->type = MT_HIGH_VECTORS;
create_mapping(init_maps);//以上这几行就是在高端虚拟地址为高端中断向量分配一页来创建映射。
if (!vectors_high()) {
init_maps->virtual = 0;
init_maps->type = MT_LOW_VECTORS;
create_mapping(init_maps);
}//如果是没有配置的话,我们这里就从0x0处设置低端中断向量,分配一页来创建映射。
flush_cache_all();//刷新所有cache
flush_tlb_all();//刷新所有tlb
}
这样就把memtable_init()函数分析了一遍了。这里就可以大概了解页表映射的创建和数据cache的一些相关知识。