文章目录
Linux内存管理第七章 – Non-Contiguous Memory Allocation(vmalloc)
因为cache相关原因和内存访问延迟的原因,在处理大量内存的时候应该尽量选择连续pages的物理内存。但不幸的是由于buddy allocator造成的外部碎片的问题,系统中不可能任何时刻都有大块的连续物理内存。Linux通过vmalloc()提供了一种机制让非连续的物理内存在虚拟地址中是连续的,可以当做是虚拟地址连续的内存来使用。
在虚拟地址空间中有预留出VMALLOC_START ~ VMALLOC_END之间的一块虚拟内存区域。VMALLOC_START的具体位置取决于可用物理内存的数量,但是此内存区域的大小始终至少为VMALLOC_RESERVER大小,在x86中该区域的大小是128MB。该区域的具体大小在第四章中有详细讨论。
该区域中的内核页表需要按需调整指向通过buddy allocator分配的物理pages。这就意味着每次分配的大小是物理page的整数倍大小。因为这种分配要求修改内核页表,因此使用vmalloc()映射多少内存是由限制的因为虚拟地址空间中之后VMALLOC_START ~ VMALLOC_END之间的虚拟地址可供vmalloc()使用。所以vmalloc()在内核核心区域小心谨慎地被使用。
本章将从内核如何追踪vamlloc 地址空间中的小region开始描述,然后再来描述这些小的region如何分配和释放。
Describing Virtual Memory Areas(描述虚拟内存区域)
Linux中使用资源映射allocator来管理vmalloc虚拟地址空间。struct vm_struct用来存放基础的成对的size。它在<linux/vmalloc.h>中的定义如下:
struct vm_struct {
void *addr;
unsigned long size;
unsigned long flags;
struct page **pages;
unsigned int nr_pages;
unsigned long phys_addr;
struct vm_struct *next;
};
其中字段含义如下:
- flags: 该flag要么是VM_ALLOC,要么是VM_IOREMAP。如果是VM_ALLOC,那么该区域就是被vmalloc()所使用。如果是VM_IOREMAP,就是将high memory通过ioremap的方式映射到内核虚拟地址空间
- addr: 当前内存块的起始地址,应该是虚拟地址
- size:当前内存的大小,单位是字节
- next:指向下一个vm_struct.vm_struct通过地址大小顺序存放在一个链表中,该链表被vmlist_lock所保护。
通过上面的描述可以清晰地得知,vm_struct通过next字段连接在一起并且通过地址大小排序方便查找。每个vm_struct中的地址区间有至少一个page的间隔用来阻止内存覆盖问题。具体实例如下图:
当内核想分配一个新的区域,vm_struct list将会被get_vm_area()函数扫描。而vm_struct结构体可以通过kmalloc()从通用slab cache Size-N中分配。如果该虚拟内存区域是被用来重映射IO,该函数将直接被直接调用来映射IO数据。
Allocating A Non-Contiguous Area
vmalloc(),vmalloc_dma()和vmalloc_32()被提供用来分配一个虚拟地址连续物理地址不连续的内存区域。这些函数只有一个输入参数size,该size是与page size 对齐的。也就是通过vmalloc分配的内存大小是page size的整数倍。者三个函数都是返回该区域的虚拟地址。
void * vmalloc(unsigned long size) |
---|
从vmalloc虚拟地址空间分配size大小的内存区域 |
void * vmalloc_dma(unsigned long size) |
---|
从ZONE_DMA中分配物理pages,并映射到vmalloc虚拟地址空间 |
void * vmalloc_32(unsigned long size) |
---|
分配适用于32位地址的内存区域。该函数会保证物理pages是从ZONE_NORMAL中进行分配并且要求当前设备是32位的 |
下面再来看下vmalloc的大致调用流程:
从上图中可以清晰地看出,vmalloc有三步:
- first,通过get_vm_area()函数查找一个足够大的空间的虚拟地址段,然后再通过kmalloc分配一个新的vm_struct结构体
- second,计算当前分配的内存大小需要占用多少个page,然后通过kmalloc分配一组struct page指针数组,再通过调用buddy allocator接口alloc_page()每次获取一个物理页框填入到vm_struct中的struct page* 数则
- third,分配PMD,PTE更新内核页表,返回映射后的虚拟地址。
被vmalloc()更新的页表不是当前进程的页表而是全局引用页表init_mm->pgd.这就意味着一个访问vmalloc分配的内存区域会引起缺页异常因为当前进程的页表没有指向正确的区域。在缺页异常处理代码中,这是一种特殊的情况。缺页异常代码知道哪些缺页异常是发生在vmalloc区域,并且从master页表中的信息更新当前进程的页表。vmalloc()与buddy allocator以及page faulting之间的关系如下图:
Freeing A Non-Contiguous Area
函数vfree()用来释放一个虚拟内存区域。它线性地扫描vm_structS,来寻找期望要释放的区域然后调用vmfree_area_pages()来释放该region中对应的物理内存。