1. 内核空间和用户空间
过去,CPU的地址总线只有32位, 32的地址总线无论是从逻辑上还是从物理上都只能描述4G的地址空间(232=4Gbit),在物理上理论上最多拥有4G内存(除了IO地址空间,实际内存容量小于4G),逻辑空间也只能描述4G的线性地址空间。
为了合理的利用逻辑4G空间,Linux采用了3:1的策略,即内核占用1G的线性地址空间,用户占用3G的线性地址空间。所以用户进程的地址范围从0~3G,内核地址范围从3G~4G,也就是说,内核空间只有1G的逻辑线性地址空间。
把内核空间和用户空间分开是方便为了MMU映射
如果内核空间和用户空间都是0~4G范围的话, 那么当从用户态切入到内核态时(系统调用或者中断),就必须要切换MMU映射 (程序里一个逻辑地址在用户态和内核态肯定被映射到不同的物理地址上)
这种切换代价是很大,但是用户态/内核态的切换却是非常频繁的。
而且从技术上你根本没法切换,因为这个时候程序内的任何地址都被映射给用户进程,你根本没法取到内核数据。
就算进入内核态时你切换MMU映射,如果这个时候你要读写用户进程的数据怎么办呢? 难道又去映射MMU?
把0~3G分给用户进程、3~4G就能很好的解决这个问题.这样一来就是你用户程序里的所有变量和函数的 逻辑地址都是介于0~3G的,内核代码里所有变量和函数的逻辑地址都是介于3~4G的
所谓用户空间和内核空间,从cpu角度来说,只是运行的级别不一样,是否处于特权级别而已
逻辑地址到物理地址的MMU映射是不变的。
不管是用户态还是内核态,最终都是通过MMU映射到你的物理上的896M内存
mmap就可以映射物理内存到用户空间,你可以清楚的看到同一块物理内在用户态和内核态的不同逻辑地址
两个不同的逻辑地址(一个介于0~3G,一个介于3-4G) 通过MMU映射到同一块物理内存
1.2 linux为什么把内核映射到3G-4G这个地址呢
假如linux把内核映射到0-1G的空间,其他进程共享1-4G的空间不可以吗?
这个技术上也是可以的,而且不难实现。为什么不采用估计有历史原因吧
毕竟cpu和程序出来的年代比MMU早多了
没有MMU的年代里,对于x86,逻辑地址就是物理地址。物理地址从0开始,那么程序的逻辑地址从一开始也是从0开始的
对于任何用户进程,0~3G的映射都是不同的,但是所有用户进程3~4G的映射都是相同的
一个进程从用户态切入到内核态,MMU映射不需要变。你能很方便取得内核数据和用户进程的数据
1.3 应用程序线性地址和动态内存分配
应用程序能使用的最大线性地址就是3G, 根据linux应用的分区方法:
---------------------------- 4G
内核空间
---------------------------- 3G
栈 - 向下增长
map - 固定映射
堆 - 向上增长
数据段
代码段
---------------------------- 0G
应用层的动态内存分配, 比如malloc, 一般使用的是dlmalloc库, 具体见:http://opendevkit.com/?e=56
所以, 低端内核和高端内存是内核的概念, 跟应用程序没有直接关系.
如果Linux物理内存小于1G的空间,通常内核把物理内存与其地址空间做了线性映射,也就是一一映射,这样可以提高访问速度。但是,当Linux物理内存超过1G时,线性访问机制就不够用了,因为只能有1G的内存可以被映射,剩余的物理内存无法被内核管理,所以,为了解决这一问题,Linux把内核地址分为线性区和非线性区两部分,线性区规定最大为896M,剩下的128M为非线性区。从而,线性区映射的物理内存成为低端内存,剩下的物理内存被成为高端内存。与线性区不同,非线性区不会提前进行内存映射,而是在使用时动态映射。
1.4 高端内存和低端内存的划分
那么既然内核态的地址范围只有1G的,如果你有4G物理上的内存,显然你没法一次性全部映射所有的物理内存到内核态地址。
所有才有了高端内存。低896M的内存做直接映射,剩下的128M 根据需要动态的映射到剩下的物理内存。
64位的内核就可以很好的解决这个问题,但是64位系统不是意味着就有64根地址线。因为没必要,实际不需要那么大内存。
假设你有38位地址线,可以寻址到2048G的内存,也按照3:1划分,那么内核态就有512G范围,你的512G物理内存可以一次性的全部映射到内核空间,根本不需要高端内存
Linux物理内存空间分为DMA内存区(DMA Zone)、低端内存区(Normal Zone)与高端内存区(Highmem Zone)三部分。DMA Zone通常很小,只有几十M,低端内存区与高端内存区的划分来源于Linux内核空间大小的限制。
当物理内存大于1G的时候, 内核是不能完全用低端内存管理全部物理内存的, 所以低端内存剩下的部分就是高端内存了, 高端内存没有直接映射的, 是动态映射到内核空间的.
一般 128M给高端内存分配用, 因为很少, 所以不用要赶紧释放掉
64bit的时候, 就不存在低端内存和高端内存的概念了, 因为空间很大都可以直接管理了.
1.5 例子
区域 | 大小 |
---|---|
MemTotal | 1547MB |
HighTotal | 825MB |
LowTotal | 721MB |
申请高端内存时,如果高端内存不够了,linux也会去低端内存区申请,反之则不行。
2. Linux内核高端内存的由来
2.1 为什么需要高端内存?
高端内存是指物理地址大于 896M 的内存。对于这样的内存,无法在“内核直接映射空间”进行映射。
内核空间只有1GB线性地址,如果使用大于1GB的物理内存就没法直接映射到内核线性空间了。
当系统中的内存大于896MB时,把内核线性空间分为两部分,内核中低于896MB线性地址空间直接映射到低896MB的物理地址空间;高于896MB的128MB内核线性空间用于动态映射ZONE_HIGHMEM内存区域(即物理地址高于896MB的物理空间)。
实际上,“内核直接映射空间”也达不到 1G, 还得留点线性空间给“内核动态映射空间” 呢。
因此,Linux 规定“内核直接映射空间” 最多映射 896M 物理内存。
对于高端内存,可以通过 alloc_page() 或者其它函数获得对应的 page,但是要想访问实际物理内存,还得把 page 转为线性地址才行(为什么?想想 MMU 是如何访问物理内存的),也就是说,我们需要为高端内存对应的 page 找一个线性空间,这个过程称为高端内存映射。
2.2 高端内存
在传统的x86_32系统中, 当内核模块代码或线程访问内存时,代码中的内存地址都为逻辑地址,而对应到真正的物理内存地址,需要地址一对一的映射,如逻辑地址0xc0000003对应的物理地址为0×3,0xc0000004对应的物理地址为0×4,… …,
逻辑地址与物理地址对应的关系为
物理地址 = 逻辑地址 – 0xC0000000
这是内核地址空间的地址转换关系,注意内核的虚拟地址在“高端”,但是ta映射的物理内存地址在低端。
逻辑地址 物理内存地址
0xc0000000 0×0
0xc0000001 0×1
0xc0000002 0×2
0x