概括 #
这个问题主要在这本xv6-ref的第一章的练习题2中提出来
问题如下
KERNBASE limits the amount of memory a single process can use, which might be irritating on a machine with a full 4 GB of RAM. Would raising KERNBASE allow a process to use more memory?
根据前面的知识可知这个问题翻译过来就是:在32位处理器下,当KERNBASE
为0x80000000
时用户最多可以用2GB内存,我们是否能够增加KERNBASE
让用户使用超过2GB内存呢?
ps: 下面KERNBASE
简写为KE
引言 #
要想解答这个问题,我们必须要知道xv6是如何分配内存的,以及如何区分用户和内核,以及KE
的作用。
xv6使用内存分页技术,如果你对这个不了解可以看这篇引导中的静态表和这篇内存分页设计细节,了解完这个我们谈谈用户和内核的内存分页应用。
注意我们所以计算的前提是在32位处理器的条件下
用户和内核应用内存分页 #
其实用户和内核都是程序,对于用户来说内核只有一个,对于内核来说,用户可以有无数个。为了隔离内核和用户我们必须在内存上就进行隔离,所以单一内核和每个用户都拥有一个内存目录。
内存分页的意义 #
其实我们可以把内存的目录对应的所有内存表看做一张大的内存映射表,我们使用1024 × 1024
个表单项代表4GB
内存地址,每个代表4K
连续的内存空间,当然大部分程序不会使用全部表单(一般就是内存目录一个,一到三个内存表)
当然好处有很多,比如任何程序都可以使用相同的虚拟地址但是在实际运行的时候不会相互影响,这个好处就是通过映射来实现的。
- 映射就是虚拟地址到物理地址的一种转换,这是一种
多对一
的映射,也就是一个虚拟地址只能对应一个物理地址,然而物理地址可以对应多个虚拟地址
这个转换带来了一个问题,给你一个虚拟地址,你可以查表然后得到物理地址,但是如果给你一个物理地址,你除了遍历没有很好的办法获取虚拟地址(而且有可能你会得到多个虚拟地址)。
这个问题非常重要,因为它影响了我们后面内核与用户切换的时候地址的搜索,接下来我们看看内核初始化后内存是一种什么样的存在。
内核内存利用 #
看xv6源码我们知道,内核这个程序将程序虚拟地址起点定在KE
上,我们知道一个程序由三部分组成指令、数据、堆栈。堆栈是向下生长,而数据、指令向上生长。而xv6把堆栈固定为32KB,所以内存只能向上生长,所以理论上内核最大占用空间只有2^32(4G) - KE
- 当
KE
为0xf0000000
时,只有256M - 当
KE
为0x80000000
,有2G。
由于用户内存空间不能超过KE
(实际系统中为UTOP
,值为KE
减去一些为系统功能预留的空间,这里我们忽略他们),所以用户所能得到的内存空间最大为KE
,也就是
- 当
KE
为0xf0000000
时,为 3840M(4G-256M) - 当
KE
为0x80000000
,有2G。
通过上面简单的理论值我们推断出答案是:
- 增大
KE
可以增大用户内存
这个结果看起来是正确的,但是我们知道作为一个操作系统不能固定用户的内存,我们接下来就看看在不同的内存下的实际应用以及xv6
自身设计问题,由此来探索这个问题的本质
我们就分两种情况来考虑
物理内存小于(4G-KERNBASE) #
这种情况在很早的时候经常出现,我们计算机的内存可能就几M,这个时候内核假如真的占了4G-KE
那连内核都跑不起来何况用户程序。
我们接下来进入源码来看xv6
将实际占用的空间体现在内存表中,我们看到kern/kernld.ld
配置文件,其中最主要的一行
PROVIDE(end = .);
这行位置出现在最后一行,它的作用是提供了一个变量end
,我们在C中就可以用这个变量代表机器码的最后一行的虚拟地址,有了这个地址之后,我们就获取了一个重要信息内核程序总长度,假如没有这个变量,我们只能人工设定一个操作系统的占用空间,假如定大了就是浪费,定小了程序就可能崩溃(内存溢出)。分配的细节可以看这篇文章,通过这个变量,我们把内核占用的物理空间给腾出来了。接下来的剩余的物理空间就留给用户了,所以用户能用到的内存最大为实际内存 - 操作系统占用的内存
。
而且我们能够知道操作系统还将内核的内存映射直接固定了,将KE:4G
的地址映射到0:4G-KE
,公式为虚拟内存=物理内存+KERNBASE
,这个映射存在于kern/pmap.c
的mem_init
函数中,这个带来了一个便利,就是程序变得很简单,假如我们得到一个物理内存地址,我们就能知道虚拟内存地址,但是这个也引来一个巨大的问题,就是当内存大于4G - KE
的时候。
PS:关于知道物理内存能知道虚拟地址在源码的好处主要体现在用户内存空间创建的时候,这个时候处于内核态,当我们得到一个空的物理空间页,我们只要简单的使用上面公式就能获取到虚拟地址,然后内核就能在直接通过指针修改程序。假如没有这个映射,我们就后面动态建立,但是这里涉及到一个非常棘手的成本
的问题,我们其实只需要一个值映射,然而我们却要浪费4K的连续内存空间,而且给程序带来了很大的复杂性。
我们接下来考虑系统内存大于那段映射最大值(4G-KE)时候会造成什么情况
物理内存大于 (4G-KERNBASE) #
在现在大家内存愈来愈大,这种情况比较常见,由于xv6在程序设计中是动态创建用户空间,当用户比较少占用空间比较小的时候,程序不会用到物理地址比较大的空间,但是当物理内存超过这个阀值(4G-E),在这里我们考虑KE=0xf0000000
,也就是物理内存地址大于256M的时候,由于内核没有映射到这么高的地址,所以我们用上面那个物理地址转虚拟地址公式就失效了。
由于xv6只是一个教学系统,所以为了简化系统,降低程序复杂性所以使用了最简单的直接映射的方法来处理内存,也正是由于这个原因提高KE
并不能增加用户最大内存,当然解决的方法也有,我想了两种。
- 在内核空间中分配用户内存目录空间,这种最为简单,但是会浪费一定的内存空间(必须在内核一开始运行就占用空间)
- 在内核空间中映射一段固定的地址来存放用户内存目录地址,这个程序设计比较复杂,但是只需要初始化一个内存表单就行
总结 #
通过上面的分析,我们得到一个结论:
由于xv6设计的局限性,增大KERNBASE
并不能增大用户最大可用空间,反而会减少。但是如果修改xv6设计可以实现增大KERNBASE
增大用户最大可用空间。