無料Wikiサービス | デモページ
検索

アクセス数
最近のコメント
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
はじめ - ノース
はじめ - ノース
はじめ - 楽打連動ユーザー
はじめ - 楽打連動ユーザー
Adsense
広告情報が設定されていません。

ハイメモリ(その3)


ハイメモリを利用できる3つ目の手法が、非連続カーネルマップと呼ばれるものです。これはまさに、ユーザプロセス空間にページをマップするのと考え方は同じです。ただユーザプロセスでは遅延割り当てでした。ユーザ空間にリニアアドレスを割り当てても物理ページは割り当てられません。使用時においてページフォルト毎に割り当てられるものでした。しかしカーネルマッピングではリニアアドレスを取得すると同時に、ページも割り当てているようです。たぶんカーネル使用ということで、優先的にページを確保する。と言うことでないでしょうか?

非連続カーネルマッピングは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;
}

最終更新 2010/12/13 16:42:46 - north
(2010/12/13 16:42:46 作成)