ハイメモリ(その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; }