伙伴算法系统、slab分配器都是要求物理页框连续。buddy 分配器并不能完全消除页外碎片问题,为了应付可能的情况(指总空闲空间大于请求 大小,但空闲空间却不连续,这种情况 buddy 分配器没有办法处理),Linux 提供了一种机 制,称 vmalloc 分配器。它允许将不连续的物理空间映射到连续的虚地址空间。这个虚地址空间的描述符:
struct vm_struct {
unsigned long flags;
void * addr;
unsigned long size;
struct vm_struct * next;
};
flags 标志
addr 起始虚地址
size 大小
next 下一个虚地址空间
vmalloc
return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);
size = PAGE_ALIGN(size);
area = get_vm_area(size, VM_ALLOC);
return __get_vm_area(size, flags, VMALLOC_START, VMALLOC_END);
addr = ALIGN(start, align);//往上对齐
area = kmalloc(sizeof(*area), GFP_KERNEL);
size += PAGE_SIZE;
for (p = &vmlist; (tmp = *p) != NULL ;p = &tmp->next)
if ((unsigned long)tmp->addr < addr)
if((unsigned long)tmp->addr + tmp->size >= addr)
addr = ALIGN(tmp->size + (unsigned long)tmp->addr, align);
continue;
if (size + addr <= (unsigned long)tmp->addr)
Goto found;
addr = ALIGN(tmp->size + (unsigned long)tmp->addr, align);
if (addr > end - size)
Goto out; //没有找到空闲的线性地址
Found:
找到空闲的线性地址,并将该部分线性地址插入到vmlist中
area->next = *p;
*p = area;
area->flags = flags;
area->addr = (void *)addr;
area->size = size;
return area;
nr_pages = size >> PAGE_SHIFT;
array_size = (nr_pages * sizeof(struct page *));
area->nr_pages = nr_pages;
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;
memset(area->pages, 0, array_size);
//优先从高端内存区分配物理页框
for (i = 0; i < area->nr_pages; i++)
area->pages[i] = alloc_page(gfp_mask);
map_vm_area(area, prot, &pages)
//开始的线性地址
unsigned long address = (unsigned long) area->addr;
unsigned long end = address + (area->size-PAGE_SIZE);
//使用pgd_offset_k来获得主内核页全局目录中的目录项。
pgd = pgd_offset_k(address);
pgd_offset(&init_mm, address)
((mm)->pgd+pgd_index(address))
(((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD-1))
//建立虚拟地址到物理地址的页目录和页表
for (i = pgd_index(address); i <= pgd_index(end-1); i++)
pud_t *pud = pud_alloc(&init_mm, pgd, address);
next = (address + PGDIR_SIZE) & PGDIR_MASK;
map_area_pud(pud, address, next, prot, pages)
do {
pmd_t *pmd = pmd_alloc(&init_mm, pud, address);
map_area_pmd(pmd, address, end - address, prot, pages)
do
pte_t * pte = pte_alloc_kernel(&init_mm, pmd, base + address);
map_area_pte(pte, address, end - address, prot, pages)
do
struct page *page = **pages;
set_pte(pte, mk_pte(page, prot));
address += PAGE_SIZE;
pte++;
(*pages)++;
while (address < end);
address = (address + PMD_SIZE) & PMD_MASK;
pmd++;
address = (address + PUD_SIZE) & PUD_MASK;
pud++;
} while (address && address < end);
address = next;
pgd++;
从上面可以看到vmalloc步骤
(1)从vmlist中找到一个满足大小要求的连续虚拟地址空间
(2)如果所需页描述符总大小大于页大小(4KB)就递归调用vmalloc申请页描述符的空间,否则调用kmalloc申请页描述符的空间
(3)调用alloc_page优先从高端内存区申请页框,alloc_page返回页框的页描述符地址
(4)调用map_vm_area建立从线性地址到物理页框地址的页目录、页表