vfree/vunmap(非連続メモリの解放)
vmalloc/vmap関数でアロケートしたメモリはvfree/vunmap関数で返されます。両者とも__vunmap関数で実際の処理が行われます。違いはvfreeから呼ばれた__vunmapでは、ページその物も返しますが、vunmapからではページそのものは解放しません。vmap関数でのメモリ取得は、ページそのものはvmap関数を呼だす側で用意しておく必要があり、メモリー解放において__vunmap関数でページは解放させないというのは処理の流れで頷けます。
__vunmap関数はページ単位にメモリー解放で、その点スラブ単位のメモリー解放のkfree関数と比べたら簡単です。まず解放するアドレスの正当性をチェックします。if ((PAGE_SIZE-1) & (unsigned long)addr)はアドレスがページアライメントかどうかのチェックをしています。
remove_vm_area関数でカーネルのリニア空間を管理しているvmlistから、そのメモリのメモリーリージョンを削除します。
deallocate_pagesが0でないなら、struct vm_structの情報をもとに、そのページを__free_pageでバディーシステムに戻します。すべてのページを戻した後そのページ情報を管理しているarea->pagesの領域を開放します。なお、その領域がvmallocで取得したか、kmallocで取得したかで、vfreeないしkfree関数を呼び出します。
そして最後にそのメモリリージョンを管理しているstruct vm_structを解放して終了です。
見つかると、そのメモリ空間をunmap_vm_area関数で削除し、かかるページテーブルをクリアします。*p = tmp->nextについては、たぶん*pがtmpの前のメモリー空間で、nextで、そこに削除するtmp->nextを次のメモリ空間としてセットしているのでは・・・
ページテーブルのクリアはカーネルマスタディレクトリに施しています。そうなるとカーネルモードで動作しているプロセスがメモリを解放しても、そのプロセスのページテーブルは、解放されたページがまだ設定されているということになるのでは・・・。そうなるそのプロセスでは解放したエリアでも参照できるのでは? たぶんカーネルということで、その辺りは踏まえてvmalloc/vfrreeを使用しなさい。ということなのでしょうか?
__vunmap関数はページ単位にメモリー解放で、その点スラブ単位のメモリー解放のkfree関数と比べたら簡単です。まず解放するアドレスの正当性をチェックします。if ((PAGE_SIZE-1) & (unsigned long)addr)はアドレスがページアライメントかどうかのチェックをしています。
remove_vm_area関数でカーネルのリニア空間を管理しているvmlistから、そのメモリのメモリーリージョンを削除します。
deallocate_pagesが0でないなら、struct vm_structの情報をもとに、そのページを__free_pageでバディーシステムに戻します。すべてのページを戻した後そのページ情報を管理しているarea->pagesの領域を開放します。なお、その領域がvmallocで取得したか、kmallocで取得したかで、vfreeないしkfree関数を呼び出します。
そして最後にそのメモリリージョンを管理しているstruct vm_structを解放して終了です。
static void __vunmap(const void *addr, int deallocate_pages) { struct vm_struct *area; if (!addr) return; if ((PAGE_SIZE-1) & (unsigned long)addr) { WARN(1, KERN_ERR "Trying to vfree() bad address (%p)\n", addr); return; } area = remove_vm_area(addr); if (unlikely(!area)) { WARN(1, KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n", addr); return; } debug_check_no_locks_freed(addr, area->size); debug_check_no_obj_freed(addr, area->size); if (deallocate_pages) { int i; for (i = 0; i < area->nr_pages; i++) { struct page *page = area->pages[i]; BUG_ON(!page); __free_page(page); } if (area->flags & VM_VPAGES) vfree(area->pages); else kfree(area->pages); } kfree(area); return; }カーネルのリニア空間(vm_struct)の管理はvmlistをヘッドとして、vmlist->nextを次のメモリ空間とする単方向リストで管理され、最後はvmlist->next=NULLとなっています。従ってif (tmp->addr == addr)なるメモリ空間をvmlistのリストを辿ることで見つけます。
見つかると、そのメモリ空間をunmap_vm_area関数で削除し、かかるページテーブルをクリアします。*p = tmp->nextについては、たぶん*pがtmpの前のメモリー空間で、nextで、そこに削除するtmp->nextを次のメモリ空間としてセットしているのでは・・・
static struct vm_struct *__remove_vm_area(const void *addr) { struct vm_struct **p, *tmp; for (p = &vmlist ; (tmp = *p) != NULL ;p = &tmp->next) { if (tmp->addr == addr) goto found; } return NULL; found: unmap_vm_area(tmp); *p = tmp->next; tmp->size -= PAGE_SIZE; return tmp; }unmap_vm_area関数では削除するメモリー空間が有しているページテーブルを、pgd_offset_kマクロでマスタカーネルページグローバルディレクトリを取得し、順にページアッパディレクトリ,ページミドルディレクトリ、ページテーブルエントリーとそのエントリをクリアしています。
static void unmap_vm_area(struct vm_struct *area) { unmap_kernel_range((unsigned long)area->addr, area->size); } void unmap_kernel_range(unsigned long addr, unsigned long size) { pgd_t *pgd; unsigned long next; unsigned long start = addr; unsigned long end = addr + size; BUG_ON(addr >= end); pgd = pgd_offset_k(addr); flush_cache_vunmap(addr, end); do { next = pgd_addr_end(addr, end); if (pgd_none_or_clear_bad(pgd)) continue; vunmap_pud_range(pgd, addr, next); } while (pgd++, addr = next, addr != end); flush_tlb_kernel_range(start, end); }補足
ページテーブルのクリアはカーネルマスタディレクトリに施しています。そうなるとカーネルモードで動作しているプロセスがメモリを解放しても、そのプロセスのページテーブルは、解放されたページがまだ設定されているということになるのでは・・・。そうなるそのプロセスでは解放したエリアでも参照できるのでは? たぶんカーネルということで、その辺りは踏まえてvmalloc/vfrreeを使用しなさい。ということなのでしょうか?