虚拟地址空间0~3G用于应用层

虚拟地址空间3~4G用于内核层

内核又将3~4G的虚拟地址空间,划分为如下几个部分:

896MB又可以细分为ZONE_DMA和ZONE_NORMAL区域。

低端内存(ZONE_DMA):3G-3G+16M 用于DMA __pa线性映射

普通内存(ZONE_NORMAL):3G+16M-3G+896M __pa线性映射 (若物理内存<896M,则分界点就在3G+实际内存)

高端内存(ZONE_HIGHMEM):3G+896-4G 采用动态的分配方式

ZONE_DMA+ZONE_NORMAL属于直接映射区:虚拟地址=3G+物理地址 或 物理地址=虚拟地址-3G,从该区域分配内存不会触发页表操作来建立映射关系。

ZONE_HIGHMEM属于动态映射区:128M虚拟地址空间可以动态映射到(X-896)M(其中X位物理内存大小)的物理内存,从该区域分配内存需要更新页表来建立映射关系,vmalloc就是从该区域申请内存,所以分配速度较慢。

直接映射区的作用是为了保证能够申请到物理地址上连续的内存区域,因为动态映射区,会产生内存碎片,导致系统启动一段时间后,想要成功申请到大量的连续的物理内存,非常困难,但是动态映射区带来了很高的灵活性(比如动态建立映射,缺页时才去加载物理页)。

整体的映射关系如下:

内核将物理内存等分成N块4KB,称之为一页,每页都用一个struct page来表示。

下图是通过vmalloc申请了3个page大小的内存示例图,由此可见该区域是虚拟地址连续,物理地址不一定连续:

alloc_page和__get_free_page最终都是调用的同一个子函数:

kmalloc: 只能在低端内存区域分配(基于ZONE_NORMAL),最大32个PAGE,共128K,kzalloc/kcalloc都是其变种

(slab.h中如果定义了KMALLOC_MAX_SIZE宏,那么可以达到8M或者更大)

vmalloc: 只能在高端内存区域分配(基于ZONE_HIGHMEM)

alloc_page: 可以在高端内存区域分配,也可以在低端内存区域分配,最大4M(2^(MAX_ORDER-1)个PAGE)

__get_free_page: 只能在低端内存区域分配,get_zeroed_page是其变种,基于alloc_page实现

ioremap是将已知的一段物理内存映射到虚拟地址空间,物理内存可以是片内控制器的寄存器起始地址,也可以是显卡外设上的显存,甚至是通过内核启动参数“mem=”预留的对内核内存管理器不可见的一段物理内存。

kmalloc和vmalloc申请的内存块大小是以字节为单位(实际上考虑到最小细分度,开辟的可能比申请的多,存在些许浪费),而__get_free_page申请的内存块大小是以PAGE数量为单位。

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

{

struct page *page;

/*

* __get_free_pages() returns a 32-bit address, which cannot represent

* a highmem page

*/

VM_BUG_ON((gfp_mask & __GFP_HIGHMEM) != 0);

page = alloc_pages(gfp_mask, order);

if (!page)

return 0;

return (unsigned long) page_address(page);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

由上可见,__get_free_pages不能为带__GFP_HIGHMEM标志的请求分配内存。

kmalloc和__get_free_page函数返回的地址都是虚拟地址,和物理地址只差一个固定的偏移值3G,无需操作页表。

alloc_page返回的是第一个物理页的page指针,需要经过page_address函数才能取得虚拟地址。

(使用alloc_page(s)在高端内存申请大于1个page的内存,就无法保证物理地址上的连续性)

kmalloc基于slab实现的,slab是为分配小内存提供的一种高效机制(slab会把page再细分成更小的颗粒),

 

大页:

如果进程太多,页表数据会非常大,此时可以把默认的页大小由4K改为2M或者更大。

如果想要实现大数据DMA,可以用kmalloc申请N个buffer,用SG DMA(非SG的叫Block

【作者】张昺华


【微信公众号】 张昺华