kunmap
Rev.2を表示中。最新版はこちら。
kmap()のZONE_HIGHMEMは、pkmap_count[]=0のエントリーに割り当てるわけですが、pkmap_count[]=0が無い時、pkmap_count[]=1を0にして、そこに割り当てています。今までこの意味が疑問でずっと悩んでいたのですが、なるほど。と言う思いで先のkmapの記事をアップしました。でちょっと力が入ったのが、後で読み返すと、このあたりの解説がぼやけてしまいました。で、このあたりの説明にはkunmap()を見るのが一番。と思いこの記事をアップしました。map_new_virtual()でページに仮想アドレスが割り当てられます。flush_all_zero_pkmaps()はpkmap_count[]=0のエントリーが無い場合、pkmap_count[]=1のエントリーを0にするために呼ばれる関数です。でどっちにしろ仮想アドレスが割り当てられたら、pkmap_count[last_pkmap_nr] = 1とし、set_page_address()で、そのページをリストした後、kmap()に復帰し、pkmap_count[last_pkmap_nr]++とし、kmap()を呼び出したプロセスに復帰しています。そうなると、pkmap_count[]=1となるケースは、set_page_address()でのリスト処理、その後、再度pkmap_count[]=0を検索し、ページテーブルに所定の値をセットする処理の間に、他のプロセスによりkmap()を実行した時でのパスであるわけです。しかしkmap()の処理では、ロックを取得して実行しています。そのような形でpkmap_count[]=1になることはありません。
static inline unsigned long map_new_virtual(struct page *page) { unsigned long vaddr; int count; start: count = LAST_PKMAP; for (;;) { last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK; if (!last_pkmap_nr) { flush_all_zero_pkmaps(); count = LAST_PKMAP; } if (!pkmap_count[last_pkmap_nr]) break; /* Found a usable entry */ if (--count) continue; { DECLARE_WAITQUEUE(wait, current); __set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&pkmap_map_wait, &wait); unlock_kmap(); schedule(); remove_wait_queue(&pkmap_map_wait, &wait); lock_kmap(); if (page_address(page)) return (unsigned long)page_address(page); goto start; } } vaddr = PKMAP_ADDR(last_pkmap_nr); set_pte_at(&init_mm, vaddr, &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot)); pkmap_count[last_pkmap_nr] = 1; set_page_address(page, (void *)vaddr); return vaddr; }kunmap()はkmap()でページに割り当てた仮想アドレスを開放します。ZONE_HIGHMEMでないなら、カーネル自身の使用しているページで開放できません。ZONE_HIGHMEMの時のみ開放します。その処理はkunmap_high()で行います。
void kunmap(struct page *page) { if (in_interrupt()) BUG(); if (!PageHighMem(page)) return; kunmap_high(page); }kunmap_high()では、ページを管理しているpage_address_htable[]を走査して、そのページの仮想アドレスを取得し、この仮想アドレスに対応するインデックスので管理している参照カウンターpkmap_count[]をデクリメントしてチェックします。
それが0ならバグです。kmap()でプロセスに復帰した時点で、最低でも2となっているからです。1なら他にそのページを参照しているプロセスはいません。処理の流れからいうと、本来ここで仮想アドレスを開放(ページテーブルをクリア)してページを回収ということですが、実装ではwaitqueue_active()でpkmap_map_waitリストでウエイトしているプロセスがいるかどうかチェックし、いるならwake_up()でそのプロセスを起床させているだけです。
void kunmap_high(struct page *page) { unsigned long vaddr; unsigned long nr; unsigned long flags; int need_wakeup; lock_kmap_any(flags); vaddr = (unsigned long)page_address(page); BUG_ON(!vaddr); nr = PKMAP_NR(vaddr); need_wakeup = 0; switch (--pkmap_count[nr]) { case 0: BUG(); case 1: need_wakeup = waitqueue_active(&pkmap_map_wait); } unlock_kmap_any(flags); if (need_wakeup) wake_up(&pkmap_map_wait); }kunmap_high()してもそのページに割り当てられた仮想アドレスは開放しません。その開放はkmap()で割り当てpkmap_count[]が無くなった時点で開放しています。そうすることで、再度同じページに仮想アドレスを割り当てる必要がないからです。仮想アドレス割り当てはTBLをフラッシュします。これはパフォーマンス的によくありません。