微波EDA网,见证研发工程师的成长!
首页 > 硬件设计 > 嵌入式设计 > ARM Linux (S3C6410架构/2.6.35内核)的内存映射(三)

ARM Linux (S3C6410架构/2.6.35内核)的内存映射(三)

时间:11-09 来源:互联网 点击:
这里记录一下Linux内核做二级内存映射的过程,以中断向量表的映射过程为例。

S3C6410架构下,Linux采用的是粗粒度小页内存管理方式,即内存段(section)的大小为1M,而页(page)的大小为4K。在第一级内存映射中,每一个PGD项覆盖1M的内存区域;如果有二级内存映射的话,每一个PTE项覆盖4K的内存区域。

下面我们来看一下二级内存映射表的设计。如果段的大小是1M而页的大小是4K的话,那么每一张二级映射表即页表中就需要有1M/4K=256个表项。而不论是PGD还是PTE,每一个表项的大小是4字节,即一个长整形数的大小。一张页表的大小为256*4=1024/1K字节,所以,页表的大小与页的大小并不能对并,一张4K大小的内存页可以存得下4张这样的页表。Linux采用了这样一种设计来存放页表:(文件arch/arm/include/asm/pgalloc.h)

在一张4K大小的内存页中,存放了4张不同的页表,它们依次是:第一张页表的ARM版本(也被叫做硬件版本),第二张(与第一张表的虚拟空间是连续的)页表的ARM版本,第一张页表的内核版本(也被叫做Linux版本),第二张页表的内核版本。同一张表的内核版本与ARM版本不是连续存放,而是间隔开的。

页表为什么会有内核版本和硬件版本的区分呢?因为内核需要的一些信息(比如dirty、access等)在ARM需要的页表信息中没有,所以Linux需要另外一份满足自己需要的映射表。

可能正是因为页表大小(1K)与页大小(4K)的不匹配,也造成了内存映射计算方面的很多麻烦。直观地来理解,既然每一个一级页表项映射的内存空间是1M,那么在代码中一个一级页表项pgd_t的大小就应该定义为4字节,PGDIR_SIZE应该定义为1M,但事实不是这样:

[c]#define PGDIR_SHIFT 21#define PGDIR_SIZE (1UL < PGDIR_SHIFT)typedef unsigned long pgd_t[2];[/c] 

PGDIR_AIZE被定义为2M,而pgd_t被定义为8个字节。其实这两个PGD仍然是互相独立的,并没有任何关联。
这给理解和计算都带来了麻烦,但唯一的一条好处就是更好地解决了页表大小与页大小不匹配的问题。因为每两个相邻的页表是放在一起处理的,所以干脆把两个相邻的PGD也定义在一起,这样当其中的一个被映射时也要保证另一个得到映射。
下面看一个映射中断向量表的实际过程,通过调用栈paging_init()->devicemaps_init()->create_mapping()->alloc_init_section()->alloc_init_pte(),最后到达了函数alloc_init_pte(),这段代码包含了我的注释和打印(以[Michael]开关):

[c]static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,unsigned long end, unsigned long pfn,const struct mem_type *type){pte_t *pte;printk(MICHAEL_DBG "alloc_init_pte()\n");if (pmd_none(*pmd)) {pte = alloc_bootmem_low_pages(2 * PTRS_PER_PTE * sizeof(pte_t));printk(MICHAEL_DBG "pmd is still blank, pte = 0x%x\n", pte);printk(MICHAEL_DBG "will populate pmd\n");__pmd_populate(pmd, __pa(pte) | type->prot_l1);}pte = pte_offset_kernel(pmd, addr);do {void *linux_pte = (void *)pte;void *hw_pte = linux_pte - 2048;printk(MICHAEL_DBG "pmd has been populated, pte = 0x%x, pfn = 0x%x, pfn_pte = 0x%x\n", pte, pfn, pfn_pte(pfn, __pgprot(type->prot_pte)));printk(MICHAEL_DBG "before set_pte_ext(): hw_pte = 0x%x, *hw_pte = 0x%x, linux_pte = 0x%x, *linux_pte = 0x%x\n", hw_pte, *((unsigned int*)hw_pte), linux_pte, *((unsigned int *)linux_pte));set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);printk(MICHAEL_DBG "after set_pte_ext(): hw_pte = 0x%x, *hw_pte = 0x%x, linux_pte = 0x%x, *linux_pte = 0x%x\n", hw_pte, *((unsigned int*)hw_pte), linux_pte, *((unsigned int *)linux_pte));pfn  ;} while (pte  , addr  = PAGE_SIZE, addr != end);}[/c] 

先看前面一段(去掉了注释和打印):

[c]if (pmd_none(*pmd)) {pte = alloc_bootmem_low_pages(2 * PTRS_PER_PTE * sizeof(pte_t));__pmd_populate(pmd, __pa(pte) | type->prot_l1);}[/c] 

先说明一下,因为在S3C6410上,最多只支持内存的二级映射即PGD->PTE->page,所以并不存在真正的PMD,即使当它出现时,它也与PGD相同。
这段代码检查一级映射项PGD是不是空,如果是空的话就说明一级映射还没有建立过,(二级)页表不存在,所以就先通过boomem来申请一张页面做为页表,有了页表就可以填充PGD了,填充PGD的代码__pmd_populate()在《Arm-Linux二级页表的问题》一篇中已经讲过,不再赘述。

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top