非连续内存空间
时间:2006-03-06 来源:ChinaE_OS
Linux在多种情况下使用非连续内存空间,如:激活交换区域的数据结构,某块空间,I/O驱动的缓存,使用高内存页面帧。
1:非连续空间的线性地址
VMALLOC_START和VMALLOC_END宏定义了非连续空间的开始和结束位置。
2:vm_struct
vm_struct |
||
Type |
Name |
Description |
void * |
addr |
第一块内存块的线性地址 |
unsigned long |
size |
加4096后的空间大小 |
unsigned long |
flags |
非连续地址空间的映射方式 |
struct page ** |
pages |
页描述的数组指针 |
unsigned int |
nr_pages |
内存区域中的页数 |
unsigned long |
phys_addr |
如果不是硬件设备的I/O共享空间,设置为0 |
struct vm_struct * |
next |
指向下一块vm_struct的指针。 |
Flags的选项:
(1) VM_ALLOC:通过vmalloc分配的页。
(2) VM_MAP:通过vmap映射的已经分配的页。
(3) VM_IOREMAP:通过ioremap映射的硬件设备的板上地址。
3:get_vm_area
查找一块从VMALLOC_START开始VMALLOC_END结束的线性地址。需要两个参数,size,需要创建的空间大小,flag:指定区域的类型。
直接调用__get_vm_area
struct vm_struct *get_vm_area(unsigned long size, unsigned long flags)
{
return __get_vm_area(size, flags, VMALLOC_START, VMALLOC_END);
}
__get_vm_area执行的步骤如下:
(1) 调用kalloc获取vm_struct描述符的地址。
(2) 获取vmlist_lock,遍历vm_struct链表,找到一块至少为size+4096大小的空地址空间。
(3) 如果找到,初始化描述符域,释放vmlist_lock,并且返回初始化的非连续空间的的地址。否则,释放描述符域,释放vmlist_lock,并且返回NULL.
4:vmalloc
分配一块size大小的非连续地址空间,如果分配失败,返回NULL.该函数调用了内部实现函数__vmalloc。
void *vmalloc(unsigned long size)
{
return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);
}
__vmalloc的步骤如下:
(1) 检查参数size的合法性。
size = PAGE_ALIGN(size);
if (!size || (size >> PAGE_SHIFT) > num_physpages)
return NULL;
(2) 调用get_vm_area获取一块空的地址空间。如果不成功,返回NULL,并且退出执行。
area = get_vm_area(size, VM_ALLOC);
if (!area)
return NULL;
(3) 然后调用内部__vmalloc_area分配页面,并且直接返回分配结果。
return __vmalloc_area(area, gfp_mask, prot);
__vmalloc_area的步骤如下:
(1) 获取页数,以及所有页数据结构需要的空间大小。
nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;
array_size = (nr_pages * sizeof(struct page *));
area->nr_pages = nr_pages;
(2) 检查array_size是否大于PAGE_SIZE,如果是,递归调用__vmalloc,否则调用kmalloc分配页目录。如果分配的页目录为空,释放已经获取得vm_struct,并且退出。
if (array_size > PAGE_SIZE)
pages = __vmalloc(array_size, gfp_mask, PAGE_KERNEL);
else
pages = kmalloc(array_size, (gfp_mask & ~__GFP_HIGHMEM));
area->pages = pages;
if (!area->pages) {
remove_vm_area(area->addr);
kfree(area);
return NULL;
}
(3) 分配页面。
memset(area->pages, 0, array_size);
for (i = 0; i < area->nr_pages; i++) {
area->pages[i] = alloc_page(gfp_mask);
if (unlikely(!area->pages[i])) {
/* Successfully allocated i pages, free them in __vunmap() */
area->nr_pages = i;
goto fail;
}
}
(4) 调用map_vm_area,建立内核使用的页表入口,标示已经分配的非连续内存空间的每个页面已经和vmalloc分配的连续线性地址联系起来。
if (map_vm_area(area, prot, &pages))
goto fail;
return area->addr;
5:vfree
Vfree释放vmalloc 和vmalloc_32分配的非线性地址空间,vunmap释放vmap创建的地址空间。不过这两个函数都之间调用了_vunmap实现。
void vfree(void *addr)
{
BUG_ON(in_interrupt());
__vunmap(addr, 1);
}
void vunmap(void *addr)
{
BUG_ON(in_interrupt());
__vunmap(addr, 0);
}
_vunmap的实现如下:
(1) 调用remove_vm_area获取vm_struct描述符的地址,清除非线性地址空间线性地址对应的内核页表入口。
area = remove_vm_area(addr);
if (unlikely(!area)) {
printk(KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n",
addr);
WARN_ON(1);
return;
}
(2) 检查deallocate_pages,如果设置为1,察看指向页描述符指针的area->pages数组,对于数组的每个元素,调用__free_page释放每个页帧到zone页帧分配器中;执行kfree(area->pages)释放数组本身。
if (deallocate_pages) {
int i;
for (i = 0; i < area->nr_pages; i++) {
if (unlikely(!area->pages[i]))
BUG();
__free_page(area->pages[i]);
}
if (area->nr_pages > PAGE_SIZE/sizeof(struct page *))
vfree(area->pages);
else
kfree(area->pages);
}
(3) 调用kfree(area)释放vm_struct.
5: remove_vm_area
查找并且删除连续的内核虚拟空间。
struct vm_struct *remove_vm_area(void *addr)
{
struct vm_struct *v;
write_lock(&vmlist_lock);
v = __remove_vm_area(addr);
write_unlock(&vmlist_lock);
return v;
}
__remove_vm_area步骤如下:
(1) 根据地址查找要删除的描述符vm_struct,如果找到,跳到2,否则返回NULL.
for (p = &vmlist ; (tmp = *p) != NULL ;p = &tmp->next) {
if (tmp->addr == addr)
goto found;
}
return NULL;
(2) 调用unmap_vm_area释放该块。
found:
unmap_vm_area(tmp);
*p = tmp->next;
/*
* Remove the guard page.
*/
tmp->size -= PAGE_SIZE;
return tmp;
}