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を使用しなさい。ということなのでしょうか?





