vmalloc


Rev.2を表示中。最新版はこちら

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関数で実際のページを獲得します。
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)))

最終更新 2011/02/03 19:19:47 - north
(2011/02/03 03:19:06 作成)


検索

アクセス数
3694358
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。