linux kernel集中了世界顶尖程序猿们的编程智慧,犹记操作系统课上老师讲操作系统的四大功能:进程调度 内存管理 设备驱动 网络。从事嵌入式软件开发工作,对设备驱动和网络接触的比較多。
而进程调度和内存管理接触少之有少。很多其它的是敬而远之。
我的理解。想在内核开发上有更深层次的技术进步。应该对内核的内存管理进程调度等深层技术有一定的理解。只是这2块内容是内核最核心的部分。实际内核开发工作中涉及较少,非常少有问题点来切入进去进行研究,网上也没有系统的资料进行解说,学习起来谈何easy。
本着我不入地狱,谁入地狱的原则。这段时间利用工作之余对内存管理进行了一些研究,结合之前工作中遇到的一些内存管理的问题,对内存管理的框架有了一点理解。
仅仅能说,这一点点了解让我更加敬畏内核,不要说进程调度了,单单内存管理就够写一本500页的书了!
我的理解内存管理能够分为页表机制和内存分配机制两大块,这段时间的研究也仅仅让我对页表机制有些理解。先写几篇文章把页表机制写出来吧。把页表机制细节理通,再去学习内存分配机制,如bootmem和slab伙伴系统。
进程调度是比内存管理更大的坑,假设有幸从内存管理的大坑中活着爬出来,再去跳进程调度的坑吧。
忽然感觉自己肩上的担子非常重,这或许就是博客的作用吧,分享自己的知识给大家,也让大家督促自己去进一步学习。
可是自己还仅仅是參加工作上才几年的小学生,尝试着去分析这些高深的知识。肯定会有纰漏和错误的地方。只是既然分享出来,就是让大家和我一起去伪存真,进行完好。所以希望大家多提见解,一起努力!
由于内存管理是比較抽象的知识,因此本着三个原则去研究:
(1)带着一些问题去研究,提出一些疑问,由这些疑问点去切入
(2)将抽象的知识画出逻辑图,使框架更加清晰
(3)实例化,尽量由实际的设备来对内存管理进行研究
本系列文章是基于ARM架构。linux内核版本号号3.4.55
linux内核的页表机制简单说来就是管理着设备真实物理地址与虚拟地址的一个动态或静态的映射。是基于硬件的MMU进行的。处理器必须提供MMU内存管理单元,linux的页表机制才干正常工作。两者相辅相成。
学习内核的内存管理假设脱离了MMU的硬件原理。仅仅去学习其软件逻辑,真的非常难懂。
说究竟,软件代码的逻辑是为硬件服务。仅仅是为了充分发挥硬件的各项功能。因此学习linux的内存管理机制,首先要学习下该处理器架构下MMU的工作原理,这样对我们理解页表机制的逻辑非常有帮助。(作为底层软件project师。没事翻翻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的代码中去分析分析!