ハイメモリ(その3)
ハイメモリを利用できる3つ目の手法が、非連続カーネルマップと呼ばれるものです。これはまさに、ユーザプロセス空間にページをマップするのと考え方は同じです。ただユーザプロセスでは遅延割り当てでした。ユーザ空間にリニアアドレスを割り当てても物理ページは割り当てられません。使用時においてページフォルト毎に割り当てられるものでした。しかしカーネルマッピングではリニアアドレスを取得すると同時に、ページも割り当てているようです。たぶんカーネル使用ということで、優先的にページを確保する。と言うことでないでしょうか?
非連続カーネルマッピングはvmallock関数から所定の引数で__vmalloc_node関数がコールされます。
先の説明で、非連続の割り当ては、リニアアドレスを割り当てる同時に、ページも割り当てるようになっています。ここで重要なメンバーはarea->pages = NULLです。このメンバーは配列となり、非連続な実ページを設定していきます。そしてこのメンバーでPTEにページのアドレスを設定していきます。
そして、for (i = 0; i < area->nr_pages; i++)で、1ページずつページをアロケートし、それをarea->pages[]に設定しています。それを引数として、map_vm_area関数をコールすることでPETに設定されます。
非連続カーネルマッピングはvmallock関数から所定の引数で__vmalloc_node関数がコールされます。
void *vmalloc(unsigned long size)
{
return __vmalloc_node(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL,
-1, __builtin_return_address(0));
}
__vmalloc_node関数で、カーネルの空いているリニアアドレスを__get_vm_area_node関数で取得し、__vmalloc_area_node関数で実際にページを獲得し、それを__get_vm_area_node関数で取得したリニアアドレスのPTEにページアドレスを設定します。__vmalloc_area_node関数も戻り値は、先頭リニアアドレスです。
static void *__vmalloc_node(unsigned long size, gfp_t gfp_mask, pgprot_t prot,
int node, void *caller)
{
struct vm_struct *area;
size = PAGE_ALIGN(size);
if (!size || (size >> PAGE_SHIFT) > num_physpages)
return NULL;
area = __get_vm_area_node(size, VM_ALLOC, VMALLOC_START, VMALLOC_END,
node, gfp_mask, caller);
if (!area)
return NULL;
return __vmalloc_area_node(area, gfp_mask, prot, node, caller);
}
__get_vm_area_node関数で、カーネルの空きリニアアドレスを取得します。カーネルの非連続のリニアアドレスの管理は、静的変数vmlistにリニアアドレス区間をvm_structをリストする事で管理しています。そのリストから、順に取得できる空間(size + addrサイズが重ならないか)をチェックします。取得できたら、kmalloc_node関数で取得した、vm_struct areaにその情報を設定します。先の説明で、非連続の割り当ては、リニアアドレスを割り当てる同時に、ページも割り当てるようになっています。ここで重要なメンバーはarea->pages = NULLです。このメンバーは配列となり、非連続な実ページを設定していきます。そしてこのメンバーでPTEにページのアドレスを設定していきます。
static struct vm_struct *
__get_vm_area_node(unsigned long size, unsigned long flags, unsigned long start,
unsigned long end, int node, gfp_t gfp_mask, void *caller)
{
struct vm_struct **p, *tmp, *area;
unsigned long align = 1;
unsigned long addr;
:
area = kmalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);
:
size += PAGE_SIZE;
write_lock(&vmlist_lock);
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) < addr)
goto out;
if (size + addr <= (unsigned long)tmp->addr)
goto found;
addr = ALIGN(tmp->size + (unsigned long)tmp->addr, align);
if (addr > end - size)
goto out;
}
if ((size + addr) < addr)
goto out;
if (addr > end - size)
goto out;
found:
area->next = *p;
*p = area;
area->flags = flags;
area->addr = (void *)addr;
area->size = size;
area->pages = NULL;
area->nr_pages = 0;
area->phys_addr = 0;
area->caller = caller;
write_unlock(&vmlist_lock);
return area;
:
}
__vmalloc_area_node関数でリニアアドレスに実ページをマップします。割り当てるページの配列area->pagesが1ページで収まるかチェックします。収まらないと__vmalloc_node関数で(リカーシブ呼び出し)、収まるようだとkmalloc_node関数で、そのページを割り当てています。そして、for (i = 0; i < area->nr_pages; i++)で、1ページずつページをアロケートし、それをarea->pages[]に設定しています。それを引数として、map_vm_area関数をコールすることでPETに設定されます。
static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
pgprot_t prot, int node, void *caller)
{
struct page **pages;
unsigned int nr_pages, array_size, i;
nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;
array_size = (nr_pages * sizeof(struct page *));
area->nr_pages = nr_pages;
/* Please note that the recursion is strictly bounded. */
if (array_size > PAGE_SIZE) {
pages = __vmalloc_node(array_size, gfp_mask | __GFP_ZERO,
PAGE_KERNEL, node, caller);
area->flags |= VM_VPAGES;
} else {
pages = kmalloc_node(array_size,
(gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO,
node);
}
area->pages = pages;
:
for (i = 0; i < area->nr_pages; i++) {
struct page *page;
if (node < 0)
page = alloc_page(gfp_mask);
else
page = alloc_pages_node(node, gfp_mask, 0);
if (unlikely(!page)) {
area->nr_pages = i;
goto fail;
}
area->pages[i] = page;
}
if (map_vm_area(area, prot, &pages))
goto fail;
return area->addr;
fail:
vfree(area->addr);
return NULL;
}
map_vm_area関数で、PGD,PUD,PMD,PTEと、各エントリーを順にアロケートしていき、最終的にvmap_pte_range関数でPTEを設定するようになっています。
static int vmap_pte_range(pmd_t *pmd, unsigned long addr,
unsigned long end, pgprot_t prot, struct page ***pages)
{
pte_t *pte;
pte = pte_alloc_kernel(pmd, addr);
if (!pte)
return -ENOMEM;
do {
struct page *page = **pages;
WARN_ON(!pte_none(*pte));
if (!page)
return -ENOMEM;
set_pte_at(&init_mm, addr, pte, mk_pte(page, prot));
(*pages)++;
} while (pte++, addr += PAGE_SIZE, addr != end);
return 0;
}





