ARM-LINUX内核管理——内存页表

Linux kernel集中了世界顶尖程序员们的编程智慧,犹记操作系统课上老师讲操作系统的四大功能:进程调度 内存管理 设备驱动 网络。从事嵌入式软件开发工作,对设备驱动和网络接触的比较多。而进程调度和内存管理接触少之有少,更多的是敬而远之。 
我的理解,想在内核开发上有更深层次的技术进步,应该对内核的内存管理进程调度等深层技术有一定的理解。不过这2块内容是内核最核心的部分,实际内核开发工作中涉及较少,很少有问题点来切入进去进行研究,网上也没有系统的资料进行讲解,学习起来谈何容易。 
本着我不入地狱,谁入地狱的原则,这段时间利用工作之余对内存管理进行了一些研究,结合之前工作中遇到的一些内存管理的问题,对内存管理的框架有了一点理解。只能说,这一点点了解让我更加敬畏内核,不要说进程调度了,单单内存管理就够写一本500页的书了! 
我的理解内存管理可以分为页表机制和内存分配机制两大块,这段时间的研究也仅仅让我对页表机制有些理解。先写几篇文章把页表机制写出来吧,把页表机制细节理通,再去学习内存分配机制,如bootmem和slab伙伴系统。 
进程调度是比内存管理更大的坑,如果有幸从内存管理的大坑中活着爬出来,再去跳进程调度的坑吧。 
忽然感觉自己肩上的担子很重,这也许就是博客的作用吧,分享自己的知识给大家,也让大家督促自己去进一步学习。 
但是自己还只是参加工作上才几年的小学生,尝试着去分析这些高深的知识,肯定会有纰漏和错误的地方,不过既然分享出来,就是让大家和我一起去伪存真,进行完善。所以希望大家多提见解,一起努力!

由于内存管理是比较抽象的知识,因此本着三个原则去研究: 
(1)带着一些问题去研究,提出一些疑问,由这些疑问点去切入 
(2)将抽象的知识画出逻辑图,使框架更加清晰 
(3)实例化,尽量由实际的设备来对内存管理进行研究

本系列文章是基于ARM架构,linux内核版本号3.4.55

linux内核的页表机制简单说来就是管理着设备真实物理地址与虚拟地址的一个动态或静态的映射,是基于硬件的MMU进行的,处理器必须提供MMU内存管理单元,linux的页表机制才能正常工作,两者相辅相成。 
学习内核的内存管理如果脱离了MMU的硬件原理,只去学习其软件逻辑,真的很难懂。说到底,软件代码的逻辑是为硬件服务,只是为了充分发挥硬件的各项功能,因此学习linux的内存管理机制,首先要学习下该处理器架构下MMU的工作原理,这样对我们理解页表机制的逻辑很有帮助。(作为底层软件工程师,没事翻翻datasheet很有用啊,多从硬件思维去考虑问题) 
MMU是处理器核内部的硬件逻辑,因此只有在处理器核的datasheet中才会有详细的说明,ARM的MMU逻辑对于不同版本处理器大同小异,我手头有一个ARM920T的手册,详细阅读了MMU一章,我有以下几个疑问需要解决:

一 MMU利用TLB进行PA(物理地址)和VA(虚拟地址)之间的转换,处理器寻址是直接在TLB中进行地址匹配。但内核初始化时会在内存中建立页表,页表与TLB什么关系? 
ARM的MMU中分别有64个指令TLB和数据TLB,处理器寻址时的虚实地址转换是MMU在TLB之间进行匹配完成映射的,但是在内核初始化中会在内存中建立页表swapper_pg_dir(该过程可看我的另一篇博文:http://blog.csdn.net/skyflying2012/article/details/41447843),并将该地址配置到CP15寄存器中。这个页表跟ARM的TLB什么关系? 
920T的MMU一章我找到了答案,如下:

这里写图片描述

这里写图片描述

CPU在访问VA(虚拟地址)时,TLB硬件完成VA到PA(物理地址)的转换,但是如果没有该VA的TLB entry,MMU的硬件单元translation table walk hardware(页表索引单元)会索引CP15寄存器c0提供的内存页表,进行地址转换,获取PA进行访问。并且会将该页表信息更新到TLB中,页表跟TLB可不是一个概念,TLB是针对于内存页表的一个缓存硬件! 
也就是说ARM的MMU不仅使用TLB进行地址转换,还能够对内存中提供的页表进行解析并地址转换,而TLB中存储的是CPU最常用的一些地址。TLB速度快,这样可以加快地址转换效率。 
如果都找不到该VA的页表信息,MMU会向CPU发出异常(根据data还是instruct不同,发出data abort或者instruct abort),异常处理函数中进行页表填充。 
这也就让我明白了为什么在内核初始化的create_mapping函数(内存映射的关键函数,以后会讲到)以及缺页异常处理函数do_page_fault中看到的都是对内存页表的更新,而没有操作TLB。因为ARM的MMU本身就会使用内存页表! 
并且ARM直接操作TLB比较复杂,不如操作内存页表,让MMU根据内存页表自己去更新TLB。 
当然了,ARM的MMU所能操作的内存页表也是有固定格式的,这就是我们的下一个问题了。

二 看内核代码,ARM LINUX使用的二级页表映射,那么ARM的MMU硬件如何完成VA到PA的转换? 
ARM的MMU使用内存页表如何完成地址转换,手册一张图将MMU操作页表的几种方式列了出来,如下: 
这里写图片描述 
如果这张图能够完全看懂,ARM的MMU硬件的地址转换就算完全明白了。可以看出ARM的MMU完成地址转换的方式有好多种,总体分为2种,section-mapping和page-mapping。linux的二级页表方式属于page-mapping,不过2种方式的映射linux内核都用到了,这个以后再说。 
我们交给CPU的内存页表(写入CP15的C0)是一级页表(也可以称为页目录)地址,该页表总共有4096个索引,每个索引占4 bytes,单个表项可以映射1MB的地址空间,这样16KB大小的页表就可以囊括32位CPU可以寻址到的最大4GB空间。 
可以想象,查询这4096个索引,仅需32位虚拟地址的高12位,CPU首先获取页目录基地址(TTB),加上待转换虚拟地址的高12位,即获取了该虚拟地址的页目录项。这个过程对于section-mapping和page-mapping都是一样的,那如何区分映射方式呢,关键在与页目录项的最低2bit,如下: 
这里写图片描述 
MMU根据页目录项最低2bit来判断接下来该如何操作,全0,无效页目录,MMU会向CPU发出缺页异常。page-mapping又会细分为coarse page table(粗页表)和fine page table(细页表),区别在于二级页表映射的是64K/4K页还是1K页,linux内核采用的是4K页,因此本文章着重说明粗页表中的4K页。 
接下来来看section-mapping和page-mapping的具体虚实地址转换原理。

1 section-mapping 
一图流如下: 
这里写图片描述 
这一张图很清晰的说明了section-mapping方式的工作原理,根据高12位索引和TTB相加获取的页目录项,MMU发现低2位为10,是section-mapping,取该页目录项的高12位与虚拟地址的低20位拼接,就获取到了物理地址,完成转换。

2 page-mapping 
一图流如下: 
这里写图片描述 
这张图说明白了page-mapping方式中4K页的工作原理,是一个二级页表方式,可以细分为5步: 
(1)MMU由CP15的C0取出TTB(页目录)基址,与VA(虚拟地址)高12位相加,获取该VA在页目录中的对应页目录项值。 
(2)MMU获取页目录项最低2bit,是01,说明本次映射的1MB数据为4k小页的page-mapping。 
(3)MMU获取页目录项的高22位(页表是256X4=1K,所以页表基址是1K对齐的)是页表基地址,与VA的中间8位相加,即该VA的对应页表项地址,从而获取VA对应的页表项值(page table entry) 
(4)MMU获取页表项值的高20位,这就是该4K页对应的物理地址了,与VA低12位相加(也就是4K页内的偏移),这就是VA对应的物理地址了 
(5)MMU访问该物理地址,进行CPU给出的读写操作

上面说明了2种映射方式的虚实地址转换逻辑,可以看出,不管section-mapping还是page-mapping,在一级页表中都是完成1MB地址的映射,而page-mapping的第二级页表项中完成4K页的映射。 
因此不管第一级页表项还是第二级页表项中除了存储物理地址,还会有很多bit是空余的,这些空余的bit完成了对所映射地址的访问权限以及操作属性的控制,主要包括AP位(access permission)和cache属性位,对于section-mapping,其控制位在第一级页表中(因为它只有一级啊),section-mapping的第一级页表项位定义如下: 
这里写图片描述 
对于page-mapping,其控制位主要在第二级页表项中,定义如下: 
这里写图片描述 
对于这些bit这里不详细说了,待后续遇到具体问题时在来分析。

到这里,对于ARM的MMU在虚实地址转换的工作原理上已经都解释清楚了,有了这些硬件基础,再去学习linux内核的页表机制就会更加轻松一些。 
接下来,咱们就跳进linux的代码中去分析分析!

你可能感兴趣的:(驱动)