关闭

linux(arm架构)内存管理学习(1)

728人阅读 评论(0) 收藏 举报
分类:
        在研究内核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 (\base << 20) | (\ap << 10) | \
     (\d << 5) | (1<<4) | (\c << 3) | (\b << 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
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:480266次
    • 积分:6176
    • 等级:
    • 排名:第4140名
    • 原创:117篇
    • 转载:89篇
    • 译文:0篇
    • 评论:208条
    文章分类
    最新评论