vmalloc申请线性地址空间(linux 2.4.22)
参考《ULK》 p343,vmalloc线性地址区范围为VMALLOC_START~ VMALLOC_END(ULK中定义为非连续内存区),在Physicalmemory mapping的末尾与第一个vmalloc area之间插入一个8M的隔离区,目的是为了捕获对内存的越界访问。出于同样的理由,插入4KB大小的安全区来隔离非连续的内存区。内存布局见图1。
图1 线性地址空间区间
用户通过vmalloc申请内存时,将获得非连续内存区VMALLOC_START ~ VMALLOC_END中一小段线性地址空间,对应上图中的vmallocarea,该区间用struct vm_struct结构体描述。
include/linux/vmalloc.h
struct vm_struct { unsigned long flags; void * addr;//内存区第一个内存单元的线性地址 unsigned long size;//内存区的大小加4KB安全区 struct vm_struct * next;//指向下一个vm_struct结构的指针 }; |
内核把所有已经分配出去的线性地址空间(图1中阴影vmalloc area)用vmlist链表管理。当需要在非连续内存区中申请size大小的线性地址空间时,扫描vmlist链表(链表中的元素为已经分配出去的地址空间),找到空闲的地址空间。
mm/vmalloc.c
@size是要申请的线性地址空间大小 struct vm_struct * get_vm_area(unsigned long size, unsigned long flags) { unsigned long addr, next; struct vm_struct **p, *tmp, *area;
area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL); size += PAGE_SIZE; addr = VMALLOC_START; for (p = &vmlist; (tmp = *p) ; p = &tmp->next) { if (size + addr <= (unsigned long) tmp->addr) break; next = tmp->size + (unsigned long) tmp->addr; if (next > addr) addr = next; if (addr > VMALLOC_END-size) goto out; } //area插入vmlist链表,代码省略 out: return NULL; } |
下面结合具体例子分析代码:
假设非连续内存区当前情况如图2,阴影部分表示已经分配出去的线性地址空间(含实际申请的size大小+4KB安全区),空白部分表示空闲的地址空间。用户通过vmalloc申请的线性地址空间大于area 2的大小,area 4区间可以满足用户需求。
图2 非连续内存区实际使用情况
对应到代码中,已分配出去的area通过vm_struct结构体管理,存放在vmlist链表中,链表的第一个元素对应area1,链表元素通过vm_struct->next指针管理。
图3 vm_struct组成的vmallocarea链表
对照图3,用户申请size大小的线性地址空间,需要扫描vmlist链表,找到前一个阴影area的vm_struct结构,该vm_struct->addr作为起始地址,加上size长度,如果小于下一个阴影area的起始地址vm_struct->addr,则说明这两个阴影之间的空闲线性地址区间满足vmalloc要求。
对于图3中从VMALLOC_START起始地址处就已经分配了一段区间出去的情况,上面描述中“找到前一个阴影area的vm_struct结构,该vm_struct->addr作为起始地址”直接用VMALLOC_START作为起始地址值。