没有合适的资源?快使用搜索试试~ 我知道了~
首页linux arm的高端内存映射详解
linux arm的高端内存映射详解

以一个实际的linux的arm设备(不是什么2440!)描述了高端内存原理和源码注释详解,能够清楚的理解什么是高端内存,什么是vmalloc,什么是永久映射,什么是临时映射,需要注意什么,应用场合是怎样,在源码中的前龙后脉都有详细的注释和解释
资源详情
资源评论
资源推荐

高端内存映射
与高端映射对立的是低端映射或所谓直接映射,内核中有关变量定义它们的它们的分
界点,全局变量 ,该变量定义在 文件中存在 的前提下,
可 见不区分体系 结构 , 对 于 当 前我 手 头 的 的 设备 即 对 于 体 系 结 构 ,
在初始化阶段的创建内存页表时初始化值,它的值就是:物理内存最后一个
的末尾,比如物理内存只有一个 ,大小是 ,再根据如下的算法就可以得
出 是多少:
!"#$%&'()*)+
代表的是当前 的在物理内存中的物理页地址,比如物理内存从 ,, 开始
由 $-#.&&#"' 决定,大小是 ,即 /,),,,,个物理页,那么 的值
为 ,),,,,,则 的值为该物理页地址转为物理地址再转为虚拟地址的结果:
,,,,,,,,。
high_memory 之上就是高端内存的范围,这样的说法也不一定对,比如对于有的体系
结构如 arm,它的永久映射实际上就在 high_memory 之下的地方,但它依然是高端内存,
所有物理内存都在初始化时映射在低端空间也是不一定正确的(这个可以在初始化时内存映
射中发现,哪样的物理内存是会属于 HIGHMEM 区),所以我想通常意义的高端内存可以基
本上定义为“不能直接通过偏移实现虚拟地址和物理地址映射”的虚拟空间、而“可以直接通
过偏移实现虚拟地址和物理地址映射”的虚拟空间是低端内存 (为什么低端映射也叫直接映
射,这里体现出了直接的感觉)这样的方式界定比较好一些。
大体上内核有 / 种高端映射的机制,分别是永久映射、临时映射、非连续内存分配映
射,
一、 非连续内存分配(vmalloc):
1.1、vmalloc 原理:
前面的文章细致描述过内核是如何通过 0112 获取大大小小的连续的物理内存的,
它们是 2 在低端虚拟地址空间的分配机制,使用连续物理内存是有很大好处的,对于充
分利用 3 的 有极大的利好;此外 12 和 01 分别尽力的避免了物理内存空间的
外碎片和内碎片;
只是有些时候比如我们的物理内存本身就不大,随着运行时间增长,物理内存的碎片
还是可能会越来越多,分配连续的物理内存尤其是大尺寸连续的物理内存将越来越费劲;
为了尽可能避免这种情况或者在出现这种情况下能够缓解进一步费劲,对于某些不频
繁的分配释放的内存申请,可以采用一种方式,即所谓的不连续内存分配如下图:

4 55.3#' 6'
4)
78 隔离带
4
78 隔离带
4/
78 隔离带
99
4 55.3":;
8 #"
< 隔离带
4:
<8 隔离带
低端 物理 内存映
射
&%= ;;6#' 6'
永久映射区 临时映射区
通过上图,不连续内存映射空间即 区在 7! 地址空间的什么位置应该很清楚
了,现在描述下所谓不连续内存映射空间是什么意思:
不连续内存映射,不连续是指物理内存不连续,后面描述它的具体实现就能很清楚的
发现它为什么是不连续,这里直接知道结果就是它映射物理地址的方式是一页一页映射的
比如需要映射 ), 页物理地址空间,那么它是 ), 次调用 > 从 12 获取,这样就会
存在物理地址不连续的可能,之所以这样做就是如上面所说,某些东西申请内存后不会频
繁的访问,并且它的长度如果不是很大的话,那么它适合 ,因为分配一个连续的很
大的物理地址是越来越困难的;
但是它的虚拟地址是连续的,如上面的图所示,每一个 = 就是一个 映
射,显然虚拟地址是连续的,只是它映射的物理地址是不一定连续的;
看到这里,就会明白为什么 需要增加内存页表的表项了,这都是初始化时没有
的映射关系,而且确切的说是需要二级映射的,因为虚拟地址和物理地址之间都是一页一
页映射的,可见 申请起来比较麻烦;另外,虚拟地址和物理地址之间都是一页一页
映射,说明最好在申请的时候也按页的整数倍即对齐值申请。
那么,宏 4 55.3#' 6' 和 4 55.3":; 是怎么定义的呢?如下:
?@4 55.3#' 6' 20 * 4 55.3.&&#"' A
B4 55.3.&&#"'()
?@4 55.3":; !".&&#"'*,/,,,,,,,
即 vmalloc 区始于 high_memory 加 8MB 的位置,结束于一个固定位置为 0xF0000000;
最后看看到底哪些场合使用 vmalloc:
1、swap;2、为 module 分配空间,见函数 module_alloc 就明白了;3、为 IO 驱动程
序分配缓冲区(ioremap);
1.2、vmalloc 实现:
)、接口函数:
接口函数就是 和,它俩的区别就是 只需指定长度即可,而
是 由 调 用 者 自 行 指 定 C>0D 和 >E 参 数 , >E 参 数 一 般 都 是
!"8"6:"5,C>0D 参数一般就是 !&8"6:"5F!&$%!$",调用 可能
会加上标志 !&G"6. 用于清零,它们都调用函数;此外还有个用于只申请
一段 区间但不映射物理地址的接口函数 E;
、具体实现:
0EHI200JK20K
!".&&#"'

C>EC>0DK>>EE>EK
EKI
L
0E2E0E2EI+
I+
200J0J+
/*size 页对齐,因为 vmalloc 映射的物理内存不连续,所以是一页一页的映射,
即映射的物理内存大小必然是页的倍数,所以必须页对齐*/
0J !" 5%!:0J+
/*检查 size 正确性,不能为 0 且不能大于 totalram_pages,
totalram_pages 是 bootmem 分配器移交给伙伴系统的物理内存页数总和*/
CM0JFF0JNN !"#$%&'NEE>0
E2:55+
/*申请一个 vm_struct 插入 vmlist 链表,申请一个 vmap_area 并插入红黑树
完成非连续内存区的高端虚拟地址分配,注意 size 总会额外在最后加一页,用于安
全区(上图的 4KB 隔离带)
注意: vm_struct 本身是使用 kmalloc_node()在 slab,所以在低端内存中;
而函数 alloc_vmap_area 真正分配了连续的高端虚拟地址
简单的总结: 分配一个 vm_struct 结构,获取对应长度(注意额外加一页)高端连续地
址,最终插入 vmlist 链表*/
E0JKK4 55.3K4 55.3#' 6'K
4 55.3":;KKC>0DK+
CM
E2:55+
/*本函数实际的给虚拟地址映射了不连续的物理内存(调用函数 alloc_page 一页一页
的分配物理地址,函数 map_vm_area 实现映射)
返回值是分配的高端虚拟地址的起始*/
KC>0DK>EKK+
I
I C2E/0120E0E2E>
I0E2E20EEEC2HE
IC0EEE200CEO1D
I
DDK0JK/KC>0D+
/*返回值是分配的高端虚拟地址的起始*/
E2+
P
主要就是两大部分:分配高端虚拟地址即分配一段 区间*给虚拟地址映射物

理地址;
1.2.1、高端地址分配:
进入函数E:
0EH0E2E0E2EIE200JK
20K20Q0K200EEK
20KEKC>EC>0DKI
L
0EH0E2E>I+
0E2E0E2EI+
!.:E2>E+
CQ0A4%.6" L
E1EQ00J+
C1EN%.6" =.6;"6
1E%.6" =.6;"6+
0C1E !"#$%&'
1E !"#$%&'+
)21E+
P
0J !" 5%!:0J+
C2DM0J
E2:55+
/*申请一个 vm_struct,本质还是通过 kmalloc 申请,申请的是高端的虚拟内存
kmalloc 可保证虚拟内存的连续性,这验证了 vmalloc 申请的虚拟地址是连续的
本质就是: 使用 kmalloc_node()在 slab 中,分配一个 vm_struct 结构*/
DJ0JCIKC>0DA!&6"35 % #8K+
C2DM
E2:55+
I
IR0E2>
I
/*vmalloc 总是要将 size 加上一个页框的大小作为安全区*/
0J* !"#%G"+
/*在 start 到 end 中,分配足够 size 大小的内核虚拟空间*/
/*注意: vmap_area 结构体(返回值 va)本身也是通过 kmalloc 分配,所以也在低端内
存中,

它的成员 va_start 和 va_end 指示了真正申请的高端虚拟内存的地址范围,可见是
线性的(连续的)
[va_start---va_end]落在高端内存的非连续映射区(vmalloc 区)中,va_end - va_start =
size = 实际需要映射长度 + 4KB(安全区)
寻找新节点在红黑树的插入点并计算出应该的高端地址值(addr),关于红黑树,细
节暂不讨论留在后续
将最终的高端地址值赋给 va,并插入红黑树中*/
>0JKK0EEKKKC>0D+
C%#"66L
DC+
E2:55+
P
/*将 va 的值(高端地址起始和长度)赋给 area,最终把 area 插入 vmlist 链表*/
0EKKQ0K+
/*这里 area 已经被赋值的成员有,addr 和 size(高端地址)、5ag、caller*/
E2+
P
首先注意结构体 vm_struct,它是 vmalloc 的管理方法非常重要:
0E2E0E2EL
0E2E0E2E IE+I指向下一个 区域I
I+I指向第一个内存单元线性地址I
20 0J+I该块内存区的大小I
20 Q0+I内存类型的标识字段I
0E2E> II>0+I指向页描述符指针数组I
20E >0+I内存区大小对应的页框数I
20 >0+I用来映射硬件设备的 %. 共享内存,其他情况下为
,I
I+I调用 类的函数的返回地址I
P+
全局变量 0E 是管理所有 对象的链表表头,每个 映射都要把它的映
射结果即一个 0E2E0E2E 型的描述符加入链表中,成员 E 用于这个链表; 指示
这段 区的虚拟地址起始;0J 标识这段 区的长度;Q0 标识映射方式,在
22 文 件 中 有 明 确 的 使 用 方 式 , 像 在 调 用 就 是
4 55.3:
?@4%.6" ,,,,,,,,) I>C0I
?@4 55.3 ,,,,,,,, II
?@4 ,,,,,,,,7 I>>0I
?@4#"6 ,,,,,,,,< I02E1C>I
?@44 !"# ,,,,,,,), I12SC>00OI
成员 >0 是一个数组,每个成员都是所映射的物理页的 > 描述符地址;>0
标识所映射的物理页,注意它不包括一页的隔离带; >0 用来映射硬件设备的 %. 共
享内存,其他情况下为 ,; 是调用 类的函数的返回地址,它是用于调试和找
问题的比如可以通过 > 下的 C 看是哪个函数在申请高端虚拟内存;
剩余23页未读,继续阅读

















安全验证
文档复制为VIP权益,开通VIP直接复制

评论2