内核的虚拟内存被连续映射到最低端的物理内存。这是所有问题的开始。
为什么要把内核的虚拟地址空间连续地映射到物理内存最低端?这个根本不是个问题。开发人员或是出于效率的原因或是出于实现的原因,就是做了这样的设计。但这种设计却引发了很多令人困惑的问题。
假设我们使用32位版本内核,系统装有2G物理内存,下面所说的“内核的虚拟内存”是指内核前896M虚拟内存。
"内核将自己的虚拟内存连续映射到低端物理内存"到底会产生什么样的结果?是说低端那896M物理内存已被内核所占而不能另做它用?昨天讨论内存的帖子里给出了否定答案,看起来这是个很老的问题了。
书上往往会还有这样的话语:内核只能使用最低端896M物理;超过896M的内存称之为“高端内存”,无法被内核直接使用。初学者读到这里,总觉得哪里不对劲,却又道不出个所以然来。
既然内核已将自己的虚拟内存映射了,内核页表里entries是怎样的呢?在entry中,我们当前感兴趣的有两项:映射地址与Present标志位。映射地址嘛,肯定安排好了。Present标志位是不是1呢?不妨认为是1。这样一来,从内核的页表上来看,前896M的物理内存的的确确被内核所“占用”,只要内核引用相应的虚拟地址,内核就会访问到相应的物理地址,不会发生任何意外。但是,如前面所说,进程也是可以使用这块内存的。如果某个进程的某个虚拟地址映射到了前896M中的某一段,那么内核与进程都可通过各自的页表访问到这块内存。这样会不会发生冲突呢?
描述页帧的数据结构是page结构体,一个页帧是否空闲正是取决于对应page结构体中的引用数。那么内核映射的那896M物理内存是否空闲呢?除了内核代码及静态数据所占的物理页外,其它的理应全是空闲状态。也就是说“内存管理子系统”可以把这些内存分配出去。昨天帖子里说的“被内核映射了不等于被内核使用了”就是这么回事。内核页表里的内容是说,前896M物理内存处于一种Ready to use的状态,然而没有被内核使用的内存,进程也可以使用。在896M内存中,内核不能使用“被进程使用的内存”,即内核不能引用相应的页表里的内容,即内核不能使用相应的虚拟地址。
内核怎么能保证不引用某段虚拟地址呢?换句话说,内核是怎么使用自己的虚拟地址的呢?她怎么知道哪一段的虚拟地址已经“被自己使用了”或者“因为进程而不能使用”呢?总之,内核如何管理自己的虚拟地址呢?
先看内核如何管理进程的虚拟地址。
当进程需要内存时,内核首先会为进程分配一段虚拟地址,即所谓的Memory Region。由于进程虚拟地址的使用情况记录在vm_area_struct中,所有的vm_area_struct都按照顺序连接在一个链表上,因此寻找某大小的虚拟地址十分简单,扫描这个链表,碰到一块大于或等于[所申请内存大小]的连续地址,便用新的vm_area_struct记录下来,并插到链表的合适位置,这块虚拟地址便被标记为“已用”了。其实这就是操作系统理论里所讲的first fit。早期的Unix就是这样管理物理内存的,而这里用之管理进程的虚拟内存。内核为进程分配内存的步骤如下:
一、找到合适大小的虚拟地址段;
二、向{内存管理子系统}申请物理页帧;
三、在进程页表中建立两者的映射关系。
然而,内核为<自己>分配内存时,只有#第二步#。
没有第三步,是因为一开始的时候,内核页表已经做好了映射。那第一步呢?内核为什么没有像进程一样“寻找合适大小的虚拟地址段”这一步骤?
内核自己需要内存时,总是向Slab层申请(?)。需要创建新的数据结构,需要一个buffer时,Slab便为之分配一块连续的内存。内核不需要对这块内存做映射,因为最开始就映射好了。只要这块连续的物理内存在前896M之内,它本来就处于一种“预备被内核使用”的状态。从Slab里分出来后,它就“正式被内核使用”。那么,内核{申请内存时}得到[虚拟地址]是由什么决定的呢?内核没有“额外管理”虚拟内存,具体使用哪一块虚拟内存取决于Slab分配哪块物理内存。Slab分配哪块物理内存又取决了{内存管理子系统}。{内存管理子系统}={Buddy System}
结论是:Buddy System管理了内核的虚拟内存。
严格来说,Buddy System在管理前896M物理内存时随便把内核的虚拟内存也管理了,因为两者是严格映射的。对进程来说,分配虚拟内存与分配物理内存是分开的步骤;对于内核来说,分配了物理内存就等于分配了虚拟内存。内核为自己分配的内存究竟在虚拟内存的什么位置取决于分[配到的内存的页帧]在物理内存中的哪个位置。进程需要额外的结构体记录虚拟内存的使用情况;内核虚拟内存的使用情况就是前896M物理内存的使用情况。换句话说,一旦进程申请到了前896M物理内存中的某一块,就相当于侵占了内核的虚拟地址空间,就相当于内核的这段虚拟地址段不可使用。如果一块物理内存被Buddy System分给了内核,进程是没有机会再得到这块内存的,也就没有机会映射这块内存。如果一块物理内存被Buddy System分给了进程,虽然内核已经预先映射了这块内存,但永远得不到相应的虚拟地址,也自然引用不到相应的表项。这样就避免了两者间的冲突。当然,如果内核有BUG,指针使用不慎,引用了不该引用的虚拟地址,很容易破坏进程的内存。
这样一来,问题都解决了。前896M物理内存由内核与进程混用。但内核只能使用这896M物理内存(因为它的页表映射是固定的),而进程可以使用任何地方的物理内存(因为它的页表可以随意设置)。内核高128M留作其它用途,可以随意映射。内核不能直接使用的内存即“高端内存”。当内核的线性地址空间大于物理内存时,“高端”便不存在了。这种情况实际上有两种可能:一是内核线性地址空间太大,如使用64位系统;二是系统物理内存太小,如机器仅配有512M内存。两者都是{内核线性地址空间大于物理内存}的结果。
管理进程的虚拟内存时,利用分页,可以把虚拟地址随意映射到任何物理地址,这正是我们印象中“分页机制的常规用法”。相反的,内核管理自己的虚拟内存时,利用分页,把“自己的虚拟内存固定到一块物理内存”,反倒限制内核能使用的内存在物理内存中的位置,正好与我们印象中{分页机制的常规用法}相悖。初学者看到这块内容时,总有种“说不清道不明”的不协调感,大概就是这个原因吧。
阅读(1302) | 评论(0) | 转发(0) |