高端内存的理解

来源:互联网 发布:免费电子杂志设计软件 编辑:程序博客网 时间:2024/04/30 18:48

转自http://blog.csdn.net/chobit_s/article/details/6029527

不考虑PAE,并且是x83_32 

首先谈谈对于 “32位” 的一点理解。简单说来,对于32-bit os,如果你想读写内存(简单来说就是int a; a = 4),首先你要定位到你要读写的内存的具体位置,就是内存地址了,

但是这个内存地址的数值有着一些限制,就是 0x00000000~0xffffffff,这里刚好是32-bit

数据大小(0xffffffff = 2 ^ 32 - 1)。那么好了,32-bit os可以定位内存范围就是0~4GB范围内任何地址空间。

 

但是这么有限的4GB地址空间 要被划分成2个部分 即用户态地址空间 和 内核态地址空间,而如果用户太地址空间要去划分3GB(0x00000000~0xbfffffff),那么留给内核态的地址空间就只有1GB了(就是0xc0000000~0xffffffff)。

现在问题来了,内核态下如果要读写内存,那么就需要通过这有限的1GB内存地址空间去定位我们实际物理内存,然而实际物理内存大小是不确定的,而对于现在2G以上内存条漫天飞的情况,我们那1GB的内存空间就显的力不从心了。

 

为了解决这个问题该怎么办呢?

首先,我们来考虑先如果就用内核地址空间直接映射实际物理内存,那么我们就忍痛丢到超过1GB以上的所用物理内存空间,方法很简单,内核地址设为x ,实际物理内存地址设为y,

那么 y = f(x)= x - 0xc0000000, 因为x范围只有1GB, 所以y范围也是1GB。现在好了,我们只能跟高端游戏说byebye了,再大的内存条买来只能用到1GB。

+---+ 4G         +---+

 | x  |  ------+    |     |

+---+ 3G    |    +---+

 |     |          |    |     |

+---+ 2G    |    +---+

 |     |          |    |     |

+---+ 1G    |    +---+

 |     |         +-->| y  |

+---+ 0            +---+ 

 

但是,从上面分析,我们也发现了解决问题方法,就是 x 到 y 映射的函数是 f:线性映射,太过简单暴力了。那如果我们买了个2GB的内存,当我们想用到超过内存1GB以上的物理空间怎么办呢,非常好办:y = g(x)= x - 0xc0000000 + 1GB 就行了,只不过现在只能用物理内存范围是:1GB~2GB,而无法使用0~1GB。 当我们又想用0~1GB的时候该怎么办呢,很简单,我们更改映射函数g就可以了。这样改来改去,显然是会影响系统效率的,没办法想多用内存,就是需要牺牲一点效率(用时间换空间)。

+---+ 4G           +---+

 | x |  ------+      |     |

+---+ 3G    |      +---+

 |    |          |      |     |

+---+ 2G    |      +---+

 |    |          +->  |  y |

+---+ 1G            +---+

 |    |                  |     |

+---+ 0              +---+ 

 

既然大体方法找到了,具体实现起来还是有很多要考虑的,比如是否可以找到一个对于效率减小最少的映射方法呢?(这里我就不继续想下去了,直接看看内核里面怎么做的)

在linux中,地址空间映射是这样的,把0xc0000000~0xffffffff这1GB内核地址空间划分成2个部分低端的796MB + 高端的128MB,低端796MB就使用f映射,直接映射到物理内存的前796MB上,而高端128MB就用来随时变更g来映射到物理内存超过796MB的范围上,这里对应了3种映射算法:动态映射,永久内核映射,临时映射,具体实现我就不讲了。

 

说下“映射”是什么,其实就是x86的内存分页机制,我们只要通过修改分页的页表项就可达到更改 “映射” 的目的。(可以参考intel手册)

另外在调试kernel时候,如果遇到 BUG: Unable handle page request at 0x????????, 如果这里0x????????<0xc0000000 就要注意了,这里你使用的变量地址不在内核空间,定是某个地方地址用错,或者指针使用出错!


附上一篇讲的比较贴近内核的文章,感觉我自己写的太过抽象了:

http://blog.csdn.net/littlehedgehog/archive/2008/08/19/2796669.aspx

80386的线性寻址空间是4G,内核空间从3G开始,如果全部采用"线性映射"(物理地址和逻辑地址只差一个常量 PAGE_OFFSET ),最多管理1G物理内存,也就是1G的物理内存挨着挨着对应的是虚拟地址的3G到4G的位置。你想想如果多于1G的内存,我们用什么线性地址来装下这些多出的地址呢? 我朋友的机器有2G,据说玩游戏巨爽(AMD64300+).显然如果线性映射我的朋友就会浪费1G内存.为了使内核能够访问这些"高端内存",内核使用HighMem.做法是不将内核1G的虚拟地址空间全部映射成物理内存,而是预留一部分给高端内存做临时映射使用.

 

其实内核不仅仅预留了highmem的地址空间,还给fixmap,vmalloc预留了虚存空间.实际上,系统初始化的时候预留128M虚存,896M用于"直接"映射物理内存。下面我们先贴上一幅图,摘自《understand linux kernel 》

 

 

毕竟意淫是很考大家的空间想象能力的,所以我们还是依照图片来看图说话:

 

高端内存映射有三种方式:

1、映射到“内核动态映射空间”
这种方式很简单,因为通过 vmalloc() ,在"内核动态映射空间"(上图的VMALLOC_START到VMALLOC_END)申请内存的时候,就可能从高端内存获得页面(参看 vmalloc 的实现),因此说高端内存有可能映射到"内核动态映射空间" 中。

2、永久内核映射
如果是通过 alloc_page() 获得了高端内存对应的 page,如何给它找个线性空间?
内核专门为此留出一块线性空间,从 PKMAP_BASE 到 FIXADDR_START (上图的倒数第二块区域),用于映射高端内存。在 2.4 内核上,这个地址范围是 4G-8M 到 4G-4M 之间。这个空间起叫“内核永久映射空间”或者“永久内核映射空间”。
这个空间和其它空间使用同样的页目录表,对于内核来说,就是 swapper_pg_dir,对普通进程来说,通过 CR3 寄存器指向。

通常情况下,这个空间是 4M 大小,因此仅仅需要一个页表即可,内核通过来 pkmap_page_table 寻找这个页表。通过 kmap(), 可以把一个 page 映射到这个空间来。由于这个空间是 4M 大小,最多能同时映射 1024 个 page。因此,对于不使用的的 page,及应该时从这个空间释放掉(也就是解除映射关系),通过 kunmap() ,可以把一个 page 对应的线性地址从这个空间释放出来。

3、临时映射

内核在 FIXADDR_START 到 FIXADDR_TOP 之间保留了一些线性空间用于特殊需求。这个空间称为“固定映射空间”

在这个空间中,有一部分用于高端内存的临时映射。

这块空间具有如下特点:

1、每个 CPU 占用一块空间

2、在每个 CPU 占用的那块空间中,又分为多个小空间,每个小空间大小是 1 个 page,每个小空间用于一个目的,这些目的定义在 kmap_types.h 中的 km_type 中。

当要进行一次临时映射的时候,需要指定映射的目的,根据映射目的,可以找到对应的小空间,然后把这个空间的地址作为映射地址。这意味着一次临时映射会导致以前的映射被覆盖。

通过 kmap_atomic() 可实现临时映射。




查看系统内存映射:

# dmesg | grep "Memory:" -n -A 8


原创粉丝点击