Arm-linux内存管理(2)

来源:互联网 发布:数据恢复大师手机版 编辑:程序博客网 时间:2024/06/05 16:23

毛德操《嵌入式系统》读书笔记。

1、在ARM-Linux内核的代码中,页面大小采用4KB,区段大小为1MB,并且使页面目录PGDIR对应于ARM的首层映射表,而中间目录PMD则设置成与PGDIR等同,这样就把概念上的三层映射合并成了物理上的二层映射。

#define PAGE_SHIFT 12
#define PAGE_SIZE (1UL << PAGE_SHIFT)

#define PMD_SHIFT 20

#define PGDIR_SHIFT 20

#define  PMD_SIZE (1UL <<PMD_SHIFT)

#define  PGDIR_SIZE (1UL << PGDIR_SHIFT)

这就以为着,页面的大小为4KB;而一个PGDIR和一个PMD所覆盖的区间为1MB。

2、ARM-Linux内核怎样建立起具体页面映射

内核中有个函数create_mapping(),其作用就是为一个给定的区间建立页面映射。调用这个函数前要先准备好一个map_desc数据结构,其源码如下:

struct map_desc {
unsigned long virtual;虚拟地址起点
unsigned long pfn;物理地址起点
unsigned long length;长度
unsigned int type;说明这个区间所属的域,以及是否可读、写、可高速缓存等属性
};

ARM-linux的MMU允许使用16个不同的域,但内核只使用了3个:

*  DOMAIN_IO     - domain 2 includes all IO only
 *  DOMAIN_USER   - domain 1 includes all user memory only
 *  DOMAIN_KERNEL - domain 0 includes all kernel memory only

#define DOMAIN_KERNEL0
#define DOMAIN_TABLE 0
#define DOMAIN_USER 1
#define DOMAIN_IO 2

注:这里的值如DOMAIN_USER都只是编号,说明这个域的访问控制位段在域访问控制寄存器中的位置,就好像下标,而控制着具体域的访问方式的是位段的值。每个域访问控制位段包含两位,所以有4种:


3、这样,一个map_desc数据结构就完整描述了一个内存区间,或者说板块的映射,调用create_mapping()就以此结构指针为调用参数。



/*
 * Create the page directory entries and any necessary
 * page tables for the mapping specified by `md'.  We
 * are able to cope here with varying sizes and address
 * offsets, and we take full advantage of sections and
 * supersections.
 */
void __init create_mapping(struct map_desc *md)
{
unsigned long phys, addr, length, end;
const struct mem_type *type;
pgd_t *pgd;


if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {
printk(KERN_WARNING "BUG: not creating mapping for "
      "0x%08llx at 0x%08lx in user region\n",
      __pfn_to_phys((u64)md->pfn), 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%08llx at 0x%08lx "
      "overlaps vmalloc space\n",
      __pfn_to_phys((u64)md->pfn), md->virtual);
}


type = &mem_types[md->type];得到映射类型和属性


/*
* Catch 36-bit addresses
*/
if (md->pfn >= 0x100000) {
create_36bit_mapping(md, type);
return;
}


addr = md->virtual & PAGE_MASK;
phys = (unsigned long)__pfn_to_phys(md->pfn);
length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));


if (type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)) {
printk(KERN_WARNING "BUG: map for 0x%08lx at 0x%08lx can not "
      "be mapped using pages, ignoring.\n",
      __pfn_to_phys(md->pfn), addr);
return;
}


pgd = pgd_offset_k(addr);
end = addr + length;
do {
unsigned long next = pgd_addr_end(addr, end);


alloc_init_section(pgd, addr, next, phys, type);以1MB逐段为单位建立单层映射。


phys += next - addr;
addr = next;
} while (pgd++, addr != end);
}

4、对于1MB的区段,这里采用的是一层映射。为什采用一层映射?

答:我们的目的是在1MB的虚拟地址与物理地址建立起映射,这个目的已经达到。我们并不采用内容的换入/换出,所以没有必要把这1MB空间分成页面。需要映射的时候,MMU以虚拟地址高12位为下标,从首层映射表中找到相应的表项。由于最低2位为10,MMU知道这是一层映射,所以表项的最高12位就是物理段地址,在后面拼接上虚拟地址的低20位,就得到32位的物理地址。

5、对于二层映射表,采用两套互相平行的页面映射表,一套是“逻辑的”,即Linux内核所要求的页面映射表。另一套是“物理的”即ARM MMU所要求的,而通过软件维持二者在逻辑上的一致。