vmalloc
vmalloc関数は、複数ページのサイズのリニアアドレス空間を必要とする時にコールされます。獲得する複数ページは物理的に連続である必要はありません。ある意味この区間はストレートマップでないとも言えるわけです。実装の概略は、カーネルメモリー区間から必要サイズのリニアメモリー空間を割り当て、必要ページを1ページづつ確保した後、そのページをページグローバルテーブル/ページアッパーテーブル/ページミドルテーブル/ページエントリーテーブルとするページテーブルに設定していくだけです。
ここで問題となるのが、カーネル動作時のメモリー空間です。カーネルは独自のメモリー空間を有していません。カレントプロセスの0xC0000000以降のメモリー空間で動作します。このリニアメモリー空間/ページテーブルはすべてのプロセスで同じ内容となっているからです。
そうなると、vmalloc関数で設定したリニアメモリー空間/ページテーブルをすべてのプロセスに反映させる必要があります。いちいち全プロセスにvmalloc関数毎に反映させていたら大変です。そこで、vmalloc関数で設定するリニアメモリー空間/ページテーブルは動作しているプロセスのそれに反映させるのでなく、init_mmとするマスタカーネルページに施します。プロセスがその動的メモリーをアクセスすると、そのプロセスにはその設定がされていないため、例外が発生します。その例外処理の中で、init_mmとするマスタカーネルページにその設定がされているなら、その設定を動作しているプロセスに反映させるというものです。マスタカーネルページに設定されていない場合、カーネルのバグということです。
上でリニアメモリー空間/ページテーブルはすべてのプロセスで同じ内容。と言いましたが、実は各プロセスに反映させるのはページテーブルだけです。リニアメモリー空間は論理的に管理する内容であり、カーネルがその空間を必要とする場合、カーネルのリニア空間も管理しているvmlistから取得すればいいからです。(たぶん)
vmalloc関数は所定の引数で__vmalloc_node関数をコールします。まず要求サイズをページアライメントに調整し、そのページ数が物理ページ数より小さいかのチェックをします。そして__get_vm_area_node関数で、カーネル用リニア区間を管理するvmlistから、利用可能なリニア空間をvm_struct *areaとして取得した後、__vmalloc_area_node関数で実際のページを獲得します。
struct page **pagesを使うのは、非連続での物理ページを割り当てるためです。連続であるならこのバッファーは必要ありません。先頭物理ページとその個数だけでいいわけですから。
そして必要ページでループするfor (i = 0; i < area->nr_pages; i++)内で、alloc_pageないしalloc_pages_node関数で1ページを確保し、その物理アドレスをarea->pages[i]に設定します。このループを抜けると必要として確保した全ページが、area->pagesに設定されたわけです。
あとはこれをページテーブルに反映させるだけです。それを行うのがmap_vm_area関数です。
それを、pgd++としながら、addr != endまで、while ループで、vmap_pud_range関数そのページグローバルテーブルに対する、ページアップテーブルを設定していきます。vmap_pud_range関数でも同じように今度はミドルテーブルを、そしてミドルテーブル処理ではページエントリーテーブルをそして最後に、そのエントリにページの物理アドレスを設定して、この物理ページがvm_struct *areaのリニアアドレスで利用できるわけです。
vmalloc関数と同じような処理をするvmap関数というのがあります。これも動的にメモリーを割り当てるわけですが、vmalloc関数と違って、既に確保したページを引数にして呼ばれ、そのページにリニアアドレス空間に割り当てるものです。vmalloc関数で確保するページの連続性は保障されないわけで、処理上連続である必要がある処理等で使用されるのでは。と思います。たぶんですが。
ここで問題となるのが、カーネル動作時のメモリー空間です。カーネルは独自のメモリー空間を有していません。カレントプロセスの0xC0000000以降のメモリー空間で動作します。このリニアメモリー空間/ページテーブルはすべてのプロセスで同じ内容となっているからです。
そうなると、vmalloc関数で設定したリニアメモリー空間/ページテーブルをすべてのプロセスに反映させる必要があります。いちいち全プロセスにvmalloc関数毎に反映させていたら大変です。そこで、vmalloc関数で設定するリニアメモリー空間/ページテーブルは動作しているプロセスのそれに反映させるのでなく、init_mmとするマスタカーネルページに施します。プロセスがその動的メモリーをアクセスすると、そのプロセスにはその設定がされていないため、例外が発生します。その例外処理の中で、init_mmとするマスタカーネルページにその設定がされているなら、その設定を動作しているプロセスに反映させるというものです。マスタカーネルページに設定されていない場合、カーネルのバグということです。
上でリニアメモリー空間/ページテーブルはすべてのプロセスで同じ内容。と言いましたが、実は各プロセスに反映させるのはページテーブルだけです。リニアメモリー空間は論理的に管理する内容であり、カーネルがその空間を必要とする場合、カーネルのリニア空間も管理しているvmlistから取得すればいいからです。(たぶん)
vmalloc関数は所定の引数で__vmalloc_node関数をコールします。まず要求サイズをページアライメントに調整し、そのページ数が物理ページ数より小さいかのチェックをします。そして__get_vm_area_node関数で、カーネル用リニア区間を管理するvmlistから、利用可能なリニア空間をvm_struct *areaとして取得した後、__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);
}
__vmalloc_area_node関数では、nr_pages に必要とするページ数、array_sizeにページの物理アドレスをセットするnr_pagesの配列を用意したときのサイズをセットします。array_sizeはstruct page **pagesにページの物理アドレスをセットするのですが、それが1ページで収まるか、収まらないかチェックするためです。if (array_size > PAGE_SIZE)で1ページに収まらなければ、__vmalloc_node関数で、収まればkmalloc_node関数でその配列を確保しているようです。struct page **pagesを使うのは、非連続での物理ページを割り当てるためです。連続であるならこのバッファーは必要ありません。先頭物理ページとその個数だけでいいわけですから。
そして必要ページでループするfor (i = 0; i < area->nr_pages; i++)内で、alloc_pageないしalloc_pages_node関数で1ページを確保し、その物理アドレスをarea->pages[i]に設定します。このループを抜けると必要として確保した全ページが、area->pagesに設定されたわけです。
あとはこれをページテーブルに反映させるだけです。それを行うのがmap_vm_area関数です。
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;
area->caller = caller;
if (!area->pages) {
remove_vm_area(area->addr);
kfree(area);
return NULL;
}
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)) {
/* Successfully allocated i pages, free them in __vunmap() */
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関数の引数のstruct vm_struct *areaは、割り当てるメモリーのリニアアドレス空間です。addrにはその先頭アドレスを、endに終了アドレスをセットして、先頭アドレスを引数にしてpgd_offset_kマクロで、そのアドレスに対するページグロバルテーブル内のインデックスアドレスを求めます。それを、pgd++としながら、addr != endまで、while ループで、vmap_pud_range関数そのページグローバルテーブルに対する、ページアップテーブルを設定していきます。vmap_pud_range関数でも同じように今度はミドルテーブルを、そしてミドルテーブル処理ではページエントリーテーブルをそして最後に、そのエントリにページの物理アドレスを設定して、この物理ページがvm_struct *areaのリニアアドレスで利用できるわけです。
int map_vm_area(struct vm_struct *area, pgprot_t prot, struct page ***pages)
{
pgd_t *pgd;
unsigned long next;
unsigned long addr = (unsigned long) area->addr;
unsigned long end = addr + area->size - PAGE_SIZE;
int err;
BUG_ON(addr >= end);
pgd = pgd_offset_k(addr);
do {
next = pgd_addr_end(addr, end);
err = vmap_pud_range(pgd, addr, next, prot, pages);
if (err)
break;
} while (pgd++, addr = next, addr != end);
flush_cache_vmap((unsigned long) area->addr, end);
return err;
}
ここで味噌となるのが、ページグローバルテーブルのエントリーを求めるのにpgd_offsetでなく、pgd_offset_kとなっているところです。pgd_offset_kマクロはpgd_offsetマクロの引数に、init_mmを引数にして展開しているというろころです。その設定はマスタカーネルページに施していることです。#define pgd_offset_k(address) pgd_offset(&init_mm, (address)) #define pgd_offset(mm, address) ((mm)->pgd + pgd_index((address)))
vmalloc関数と同じような処理をするvmap関数というのがあります。これも動的にメモリーを割り当てるわけですが、vmalloc関数と違って、既に確保したページを引数にして呼ばれ、そのページにリニアアドレス空間に割り当てるものです。vmalloc関数で確保するページの連続性は保障されないわけで、処理上連続である必要がある処理等で使用されるのでは。と思います。たぶんですが。
void *vmap(struct page **pages, unsigned int count,
unsigned long flags, pgprot_t prot)
{
struct vm_struct *area;
if (count > num_physpages)
return NULL;
area = get_vm_area_caller((count << PAGE_SHIFT), flags,
__builtin_return_address(0));
if (!area)
return NULL;
if (map_vm_area(area, prot, &pages)) {
vunmap(area->addr);
return NULL;
}
return area->addr;
}







