高端内存的理解
2012-12-25 20:55
141 查看
转自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
不考虑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
相关文章推荐
- Linux用户空间与内核空间(理解高端内存)
- Linux用户空间与内核空间(理解高端内存)
- 高端内存的理解
- 深入理解linux操作系统中的高端内存
- Linux用户空间与内核空间(理解高端内存)【转】
- Linux用户空间与内核空间(理解高端内存)
- Linux用户空间与内核空间(理解高端内存)
- linux中高端内存和低端内存的理解
- 理解sturct的内存存储
- 理解io端口和io内存--关于驱动开发的问题
- Windows系统内存计数器理解解析
- 理解I/O端口、I/O内存
- 对于内存分配的理解
- 深入理解Linux内核个人小结8---内存区管理
- 深入理解JVM虚拟机学习笔记(—)内存区域与内存溢出异常
- Linux内核高端内存
- memcached全面剖析–2.理解memcached的内存存储
- java内存区域——深入理解JVM读书笔记
- Linux 内存管理 -- 高端内存的映射方式
- 【转载】Java垃圾回收内存清理相关(虚拟机书第三章),GC日志的理解,CPU时间、墙钟时间的介绍