在研究内核hibernate实现的时候,由于需要对内存管理如MMU配置、内存分布和分配有相应了解,因此进行了专门的学习。最终结果是hibernate未研究出什么成果,反倒是内存管理的学习小有心得,就此予以总结。当然,按照一贯做法少上代码、多提思路和关键点。
我所使用的平台是全志的平台T3(四核Cortex-A7、2G DDR),内核版本是3.10.65,硬件物理地址分布是:
SRAM 0x00000000---0x0000BFFF
IO 0x01000000---0x01EFFFFF
DRAM 0x40000000---0xBFFFFFFF
BROM 0xFFFF0000---0xFFFF8FFF
一、MMU
关于MMU,一直以来都知其然而不知其所以然,因此利用这次机会做了一次亲密接触。文中提到的很多专业名词不会进行单独解释,引用的内容会附上相应的链接。
1、基础概念(具体参引用一)
其中第一级页表(L1)是由虚拟地址的高12bit(bits[31:20])组成,所以第一级页表有4096个item,每个item占4个字节,所以一级页表的大小为16KB,而在第一级页表中的每个entry的最低2bit可以用来区分具体是什么种类的页表项,2bit可以区分4种页表项,具体每种页表项的结构如下:
10b: Section entry
01b: Coarse page table
11b: Fine page table
00b: Fault
简而言之L1页表的页表项主要有两大类:
第一大类是指向第二级页表(L2页表)的基地址
第二类直接指向1MB的物理内存
PGD(Page Global Directory):第一级页表(页目录)
PUD(Page Upper Directory):中间级页表(页目录),arm架构其实不需要
PMG(Page Mid-level Directory):第三级页表(页目录)
PTE(Page Table Entry):物理页表项
对于内核,不让3G-4G的空间都使用一一映射,而是将物理地址的[0x00,fix_addr](fix_addr<1GB)映射到内核空间虚拟地址[0x00+3G,fix_addr+3G],然后将[fix_addr+3G,4G]这段空间保留下来用于动态映射,这样我们可以通过这段虚拟地址来访问从fix_addr到4GB的物理内存空间。怎么做到的呢?
譬如我们想要访问物理地址[fix_addr,4GB]这段区间中的任何一段,我就用宝贵的内核虚拟地址[fix_addr+3G,4G]的一段去映射他,建立好mmu硬件使用的页表,访问完后,将映射清除,将内核的这段虚拟地址释放,以供下次访问其他的物理内存使用。这样就可以达到访问所有4GB的物理内存的目的。
二、代码片断
1、Bootloader:如果按照常用的两步启动法,Bootloader是由Boot0和Boot1组成:
1) Boot0:说法不一,从业以来见得到说法就还有IPL、SPL
Boot0由于功能不复杂,所以仅需要创建一级页表即可。由于没有找到T3的Boot0代码,用另一平台(Cortex-A8)的代替,但仍具有一定的代表性:
void mmu_system_init(__u32 dram_base, __u32 dram_size, __u32 mmu_base)
{
__s32 *mmu_tlb_address = (__s32 *)mmu_base;
__u32 i;
//建立16k的段表,表项大小为1M
for(i = 0; i < 4 * 1024; i++)
{
mmu_tlb_address[i] =
(i << 20) |
(0 << 19) |
(0 << 18) |
(0 << 17) |
(0 << 16) |
(0 << 15) |
(0 << 12) |
(3 << 10) |
(0 << 9) |
(15 << 5) |
(0 << 4) |
(0 << 3) |
(0 << 2) |
(2 << 0);
}
//cache sram
mmu_tlb_address[0] =
(0 << 20) | // 地址
(0 << 19) | // 安全区域
(0 << 18) | // 1M段表
(0 << 17) | // not global
(0 << 16) | // not shared
(0 << 15) | //
(0 << 12) | //
(3 << 10) | // 访问权限 特权
(0 << 9) | //
(15 << 5) | // 域控制(arm特有)
(0 << 4) | //
(1 << 3) | // 有cache
(0 << 2) | // 无buffer
(2 << 0); // 段表
//cache dram
for(i = 0; i < dram_size; i++)
{
mmu_tlb_address[i + (dram_base>>20)] =
(dram_base + (i << 20)) |
(0 << 19) |
(0 << 18) |
(0 << 17) |
(0 << 16) |
(0 << 15) |
(0 << 12) |
(3 << 10) |
(0 << 9) |
(15 << 5) |
(0 << 4) |
(1 << 3) |
(0 << 2) |
(2 << 0);
}
//set ttb
__asm{mcr p15, 0, mmu_base, c2, c0, 0}
__asm{mcr p15, 0, mmu_base, c2, c0, 1}
//clean i/d cache
flush_icache();
flush_dcache();
//set domain controller
mmu_set_domain_access();
return ;
}
分析:从上面可以看到一级页表空间占用16KB,全部按段表方式创建表项,虚拟到物理的映射规则是一一对应的(即偏移为0)。
2) Boot1: 使用的是U-boot,也仅创建了一级页表,创建页表的宏是:
/* form a first-level section entry */
.macro FL_SECTION_ENTRY base,ap,d,c,b
.word (ase << 20) | (ap << 10) |
(d << 5) | (1<<4) | (c << 3) | ( << 2) | (1<<1)
.endm
2、Kerenl
内核会创建二级页表,具体代码就不做分析了(可参引用三),创建二级页表的接口是下面两个:
一级页表:__map_init_section
二级页表:alloc_init_pte
关于内核启动后,内核页表的分布会另写一文进行描述;
三、调试工具
1、proc文件系统
/proc/iomem
/proc/vmallocinfo
/proc/dma-mappings
/proc/meminfo
/proc/pidX/pagemap
2、系统工具
busybox devmem
procrank
showmap
pmap
3、自行开发工具
sw_mem
pagemap
四、引用
1、linux arm mmu基础
http://blog.csdn.net/xiaojsj111/article/details/11065717
2、linux arm的存储分布那些事之一
http://blog.csdn.net/xiaojsj111/article/details/11724081
3、Linux内核页表
http://blog.csdn.net/lieye_leaves/article/details/50809973