哈哈,我做面试官时,遇到对Linux kernel 内存子系统理解较深的同学,我基本都会问这个问题,看看他们的思考能力。我想说的是:
题主的问题是集中在第二点上。
先看第一个问题:映射跟分配是两回事,他们是不相关的
整个内核,由buddy管理算法来管理所有物理页,只有经过它分配出去的物理页,OS和应用程序才能使用。
映射是指建好页表,即某块虚拟地址空间到物理地址空间的映射,有了这个映射,处理器就可以访问这块虚拟地址空间。
所以,分配是管哪些物理地址可使用,而映射是管哪些虚拟地址可访问
那么问题来了:如果某个物理页没有被分配出来,但已有某个虚拟地址映射到这个物理页上,怎么办?会不会出问题呢?这就是内核lowmem给大家造成的疑惑。
再看第二个问题:但是如果能做到 分配后才映射,释放后就解映射,从运行安全的角度来说是最好的,可惜这个很难做到
我们先看高端内存(即highmem),请大家要注意概念,高端内存是指物理地址空间超过896M的内存地址,跟虚拟空间上的(3G+896M ~ 4G]没有任何关系。
高端内存是满足分配后才映射,释放后就解映射 这个原则的,这个空间主要给用户态进程(比如用户态代码段,数据段,栈,堆,匿名映射)或者用户态业务相关功能使用(访问文件产生的pagecache)。
但是lowmem却不是这样的。总结起来有两个原因:
1) 先有鸡还有先有蛋
虚拟地址到物理地址的映射关系,要写到页表里面,如果这个页表所在的址物理没有映射到虚拟地址,处理器是通过虚拟地址来修改页表的,但在MMU模式下,处理器只能使用虚拟地址来访问任何物理内存。为了解决这个矛盾,需要在进入保护模式(X86架构)/MMU模式(ARM架构)之前,先人为规划出一个固定映射,当然这个映射越简单越好,就是我们常说的线性映射。
2)固定映射应该是多大
从道理上讲,kernel必须的东西:比如代码段,数据段,page结构数组,缺页函数所涉及的访问地址空间,都要在这个预先规划出来的固定映射空间内,才能保证kernel功能的正常运行作。后续内核根据代码逻辑要求,动态申请内存时,建好页表就可以了。但是内核作为整个系统的管理者,它的性能必须高,不能成为系统的瓶颈;用户在运行过程中,内核态必须创建很多管理对象,才能将用户态业务管理好。比如task_struct, mm, vma,file, inode等关键数据结构。内核如果在创建这些对象时,从buddy里面分配内存(当然中间是通过slub来分配的),然后才建立映关系,那么性能必然会比较差。所以内核在运行过程中,动态创建的管理对象所在的内存空间,必须提前映射好。这个区域就是lowmem zone。
kernel运行过程中需要动态分配的管理对象,都是从lowmem zone里面分配的,这个zone的性能比highmem zone优,因为页表已提前建好的。
内核1G的虚拟空间里,使用896M空间做为lowmem空间,即固定映射空间(线性映射区)。内核使用1G的7/8空间大小作为线性区,剩下的1/8作为临时映射区,用于访问高端内存。
最后,回到题主的问题:如果整系统只有128M内存,情况会怎么样?
如果系统只有128M内存,那么系统没有highmem zone,128M都属于lowmem,kernel初始化时,会将这128M内存映射到[3G, 3G + 128M)虚拟空间上。
如果恶意写个KO,可以直接访问这128M内存空间的,因为页表已经建立了,从处理器层面来讲是可访问的。
用户态调用malloc做内存申请时,最终会通过系统调用mmap向kernel申请内存,kernel最终(缺页过程完成后)会为进程分配虚拟内存和物理内存。而这个物理内存就是这128M内存中,还没有分配的物理内存,但这块物理内存早已被映射到[3G, 3G + 128M)空间上了。但如果内核工作正常,由于这块物理内存没有被分配给内核态使用,那内核态就没有指针 指向 访物理内存对应的内核态虚拟地址空间,即内核态是不会访这块物理内存的。
在128M物理内存场景下,用户态进程之间页表是独立的,不可能相互踩,同时用户态是无法踩内核态内存的。由于只有lowmem区,理论上内核态能踩用户态内存,但经上一段分析,概率较低,只有编程错误时才会发生。
对于64位 kernel,没有highmem了,只有lowmem,所有物理内存都映射到lowmem了。与32位系统物理内存小于896M场景是一模一样的。