freeソース分析---1
24456 ワード
freeソース分析—_libc_free
本章では、前のglibcの「mallocソース分析」シリーズからfreeのソースコードの分析を開始します.mallocのソースコード分析は、ブログの同じカテゴリの文章の下の「mallocソース分析-1」から「mallocソース分析-5」を見ることができます.そのため、freeのソースコードにはmallocに似た構造のものがいくつかあります.
まずglibcのmalloc.cには以下の定義があり、
strong_alias( __libc_free, __cfree) weak_alias( __libc_free, cfree) strong_alias( __libc_free, __free) strong_alias( __libc_free, free)
したがって、
free
は__libc_free
の別名であり、実際に実行されるのは__libc_free
の関数である.void __libc_free(void *mem) {
mstate ar_ptr;
mchunkptr p;
void (*hook)(void *, const void *) = atomic_forced_read (__free_hook);
if (__builtin_expect(hook != NULL, 0)) {
(*hook)(mem, RETURN_ADDRESS(0));
return;
}
if (mem == 0)
return;
p = mem2chunk(mem);
if (chunk_is_mmapped(p)){
if (!mp_.no_dyn_threshold
&& p->size
> mp_.mmap_threshold&& p->size <= DEFAULT_MMAP_THRESHOLD_MAX) {
mp_.mmap_threshold = chunksize(p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
mp_.mmap_threshold, mp_.trim_threshold);
}
munmap_chunk(p);
return;
}
ar_ptr = arena_for_chunk(p);
_int_free(ar_ptr, p, 0);
}
__libc_free
は、まず、__free_hook
関数があるかどうかを確認し、ある場合は直接呼び出します.ここでは、デフォルトの関数が使用できないと仮定します.次に、mem2chunk
により仮想メモリのポインタmem
を対応するchunkポインタp
に変換する、#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ))
1つの使用中のchunk構造体は、その
prev_size
およびsize
フィールドのみを使用するため、ここでは2*SIZE_SZ
を減算する必要がある.次に、chunk_is_mmapped
はsizeの最低3ビットのフラグビットを検査し、chunkがmmapによって割り当てられたかどうかを判断するために使用され、もしそうであれば、munmap_chunk
がchunkを解放して戻り、munmap_chunk
を呼び出す前に、グローバルなmmapバルブ値と収縮バルブ値を更新する必要がある.さらに、chunkがmmapによって割り当てられていない場合、arena_for_chunk
によって割り当て領域ポインタar_ptr
が得られ、_int_free
によってメモリが解放されるように呼び出される._int_free
は次の章で分析し、本章ではmunmap_chunk
関数を重点的に分析する.munmap_chunk
munmap_chunkはmmapによって割り当てられたchunkを解放するために使用され、以下では、
static void internal_function munmap_chunk(mchunkptr p) {
INTERNAL_SIZE_T size = chunksize(p);
assert(chunk_is_mmapped (p));
uintptr_t block = (uintptr_t) p - p->prev_size;
size_t total_size = p->prev_size + size;
if (__builtin_expect(((block | total_size) & (GLRO(dl_pagesize) - 1)) != 0,
0)) {
malloc_printerr(check_action, "munmap_chunk(): invalid pointer",
chunk2mem(p), NULL);
return;
}
atomic_decrement(&mp_.n_mmaps);
atomic_add(&mp_.mmapped_mem, -total_size);
__munmap((char *) block, total_size);
}
まず、前のchunkのポインタ
block
が得られ、この2つのchunkのsize
の和をtotal_size
に計算し、次いで、グローバル構造mp_
を対応する設定した後、__munmap
を介して2つのchunkが解放される.mallocのソースコードから分かるように、mmapで割り当てられたchunkは独立しており、ほとんどの場合、p->prev_size
は0であるため、ここでは1つのchunkを解放し、特殊な場合は2つのchunkを解放する必要があります.特殊な場合は_int_malloc
のコードを参照してください.__munmap
はさらにシステム呼び出しであり、linuxカーネルコードに定義されたmmapである.cでは、SYSCALL_DEFINE2(munmap, unsigned long, addr, size_t, len){
profile_munmap(addr);
return vm_munmap(addr, len);
}
profile_munmap
は空の関数で、以下vm_を参照してください.munmap, int vm_munmap(unsigned long start, size_t len){
int ret;
struct mm_struct *mm = current->mm;
down_write(&mm->mmap_sem);
ret = do_munmap(mm, start, len);
up_write(&mm->mmap_sem);
return ret;
}
ここでは信号量の操作であり、主に
do_munmap
を実行してメモリを解放し、分析と表示を容易にするためにdo_munmap
のキーコードのみをコピーし、int do_munmap(struct mm_struct *mm, unsigned long start, size_t len){
unsigned long end;
struct vm_area_struct *vma, *prev, *last;
if ((start & ~PAGE_MASK) || start > TASK_SIZE || len > TASK_SIZE-start)
return -EINVAL;
len = PAGE_ALIGN(len);
vma = find_vma(mm, start);
prev = vma->vm_prev;
end = start + len;
if (vma->vm_start >= end)
return 0;
if (start > vma->vm_start) {
int error;
if (end < vma->vm_end && mm->map_count >= sysctl_max_map_count)
return -ENOMEM;
error = __split_vma(mm, vma, start, 0);
if (error)
return error;
prev = vma;
}
last = find_vma(mm, end);
if (last && end > last->vm_start) {
int error = __split_vma(mm, last, end, 1);
if (error)
return error;
}
vma = prev ? prev->vm_next : mm->mmap;
detach_vmas_to_be_unmapped(mm, vma, prev, end);
unmap_region(mm, vma, prev, start, end);
arch_unmap(mm, vma, start, end);
remove_vma_list(mm, vma);
return 0;
}
まず、送信されたパラメータをチェックします.解放する必要がある仮想メモリの開始アドレス
start
と長さlen
は、ページごとに整列し、カーネル空間のメモリを解放できません.次に、プロセスの管理メモリのAVLツリー上でfind_vma
より大きい最初の終了アドレスの仮想メモリstart
を検索し、vma
であれば、解放する必要がある仮想メモリはもともと存在しないので、何も返さない.vma->vm_start >= end
の場合、見つかったstart > vma->vm_start
は、解放する必要があるメモリを含んでいることを示し、このvma
は、__split_vma
関数によってvma
アドレスによって2つに分割されるので、仮想メモリの数がシステムの制限start
を超えているか否かを判断する必要がある.分析を容易にするために、sysctl_max_map_count
のいくつかのキーコードのみが与えられます.static int __split_vma(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long addr, int new_below){
struct vm_area_struct *new;
new = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
*new = *vma;
if (new_below)
new->vm_end = addr;
else {
new->vm_start = addr;
new->vm_pgoff += ((addr - vma->vm_start) >> PAGE_SHIFT);
}
if (new_below)
err = vma_adjust(vma, addr, vma->vm_end, vma->vm_pgoff +
((addr - new->vm_start) >> PAGE_SHIFT), new);
else
err = vma_adjust(vma, vma->vm_start, addr, vma->vm_pgoff, new);
if (!err)
return 0;
}
まず、
__split_vma
構造体vm_area_struct
が割り当てられ、その後、new
のすべてのコンテンツがvma
にコピーされ、new
は、new_below
がvma
で決定したアドレスに従って2つに分割された後、addr
に低アドレス部分と高アドレス部分のどちらが保存されるかを決定する.vma
が初めてdo_munmap
に入ったとき__split_vma
は0であったので、戻ったnew_below
は低アドレス部分を保存する.次に、vma
を呼び出して、低アドレス部分のvma_adjust
に対応する設定を行い、主にvma
変数をend
に変更し、高アドレス部分をプロセスメモリの管理ツリーに挿入する.addr
に戻ると、do_munmap
は最後尾のfind_vma(mm, end)
を取得し、このlast
が解放する必要がある仮想メモリを含んでいる場合は、last
が1であるため、返されるnew_below
は高アドレス部分である.戻ると、last
は低アドレス部分を指す.前述の解析によれば、
vma
を実行する前に、元のvmaが以下のように分割された|prev|vma|…|vma|last|detach_vmas_to_be_unmapped
の付与値はmm->mmap
であり、実際には分割後の低アドレスの仮想メモリである.次に、vma_adjust
は、解放されるメモリと交差するすべてのdetach_vmas_to_be_unmapped
を赤黒ツリーから削除し、vma
をチェーンヘッダとするチェーンテーブルを形成するために使用される.vma
が取り外されたばかりの結果、vma
とprev
を除くすべての要素がチェーンテーブルを構成している.すなわち、|prev|vma|……|vma|last|がlast
を経て、|prev|last||vma|……|vma|が第2の部分を解放する.static void detach_vmas_to_be_unmapped(struct mm_struct *mm, struct vm_area_struct *vma,
struct vm_area_struct *prev, unsigned long end){
struct vm_area_struct **insertion_point;
struct vm_area_struct *tail_vma = NULL;
insertion_point = (prev ? &prev->vm_next : &mm->mmap);
vma->vm_prev = NULL;
do {
vma_rb_erase(vma, &mm->mm_rb);
mm->map_count--;
tail_vma = vma;
vma = vma->vm_next;
} while (vma && vma->vm_start < end);
*insertion_point = vma;
if (vma) {
vma->vm_prev = prev;
vma_gap_update(vma);
} else
mm->highest_vm_end = prev ? prev->vm_end : 0;
tail_vma->vm_next = NULL;
vmacache_invalidate(mm);
}
detach_vmas_to_be_unmapped
に戻ると、do_munmap
はメモリを解放するために使用されます.次に見ると、static void unmap_region(struct mm_struct *mm,
struct vm_area_struct *vma, struct vm_area_struct *prev,
unsigned long start, unsigned long end){
struct vm_area_struct *next = prev ? prev->vm_next : mm->mmap;
struct mmu_gather tlb;
lru_add_drain();
tlb_gather_mmu(&tlb, mm, start, end);
update_hiwater_rss(mm);
unmap_vmas(&tlb, vma, start, end);
free_pgtables(&tlb, vma, prev ? prev->vm_end : FIRST_USER_ADDRESS,
next ? next->vm_start : USER_PGTABLES_CEILING);
tlb_finish_mmu(&tlb, start, end);
}
unmap_region
は、lru_add_drain
変数percpu
に対応する各pagevec
を対応するpage
のzone
チェーンテーブルに戻すために使用され、すぐにマッピングが解除されるので、これらのキャッシュされたpage変数は変更される可能性がある.lru
は、tlb_gather_mmu
変数を構築し、初期化する.次のmmu_gather
は、物理ページマッピングが存在する仮想メモリを解放するためにマッピングを解除するために使用される.void unmap_vmas(struct mmu_gather *tlb, struct vm_area_struct *vma, unsigned long start_addr, unsigned long end_addr){
struct mm_struct *mm = vma->vm_mm;
mmu_notifier_invalidate_range_start(mm, start_addr, end_addr);
for ( ; vma && vma->vm_start < end_addr; vma = vma->vm_next)
unmap_single_vma(tlb, vma, start_addr, end_addr, NULL);
mmu_notifier_invalidate_range_end(mm, start_addr, end_addr);
}
ここでは
unmap_vmas
のチェーンテーブルを巡回し、vma
毎にvma
が呼び出されて解放され、static void unmap_single_vma(struct mmu_gather *tlb, struct vm_area_struct *vma, unsigned long start_addr, unsigned long end_addr, struct zap_details *details){
unsigned long start = max(vma->vm_start, start_addr);
unsigned long end;
if (start >= vma->vm_end)
return;
end = min(vma->vm_end, end_addr);
if (end <= vma->vm_start)
return;
if (vma->vm_file)
uprobe_munmap(vma, start, end);
if (unlikely(vma->vm_flags & VM_PFNMAP))
untrack_pfn(vma, 0, 0);
if (start != end) {
if (unlikely(is_vm_hugetlb_page(vma))) {
if (vma->vm_file) {
i_mmap_lock_write(vma->vm_file->f_mapping);
__unmap_hugepage_range_final(tlb, vma, start, end, NULL);
i_mmap_unlock_write(vma->vm_file->f_mapping);
}
} else
unmap_page_range(tlb, vma, start, end, details);
}
}
ここでは主に
unmap_single_vma
によって解放される.さらに、linuxカーネルメモリ管理の知識が多すぎるため、ここでは深く分析しません.最後に、仮想アドレスを通じてページテーブルunmap_page_range
を見つけ、物理ページとの間のマッピングを解き、page構造を設定します.pte
の後、一部のページには対応する物理ページがないため、unmap_vmas
はこれらのページを解放します.void free_pgtables(struct mmu_gather *tlb, struct vm_area_struct *vma,
unsigned long floor, unsigned long ceiling){
while (vma) {
struct vm_area_struct *next = vma->vm_next;
unsigned long addr = vma->vm_start;
unlink_anon_vmas(vma);
unlink_file_vma(vma);
if (is_vm_hugetlb_page(vma)) {
hugetlb_free_pgd_range(tlb, addr, vma->vm_end,
floor, next? next->vm_start: ceiling);
} else {
while (next && next->vm_start <= vma->vm_end + PMD_SIZE
&& !is_vm_hugetlb_page(next)) {
vma = next;
next = vma->vm_next;
unlink_anon_vmas(vma);
unlink_file_vma(vma);
}
free_pgd_range(tlb, addr, vma->vm_end,
floor, next? next->vm_start: ceiling);
}
vma = next;
}
}
ここでは主に
free_pgtables
が呼び出されます.この関数では、解放する仮想メモリがvmaであると仮定し、その前のvmaがfree_pgd_range
、後の1つがprev
であり、last
が解放された後、vma
からprev->vm_end
が1つのpgd管理のメモリサイズ(32ビットシステム下では4 MB)より大きい場合、pgdのすべてのページテーブルを解放し、4 MB未満であれば何も返さない.さらに
last->vm_start
に戻ると、do_munmap
は、いくつかのアーキテクチャに関連する動作であり、それにかかわらず.arch_unmap
は、各remove_vma_list
に対応するvma
構造をslab分配器に解放する.static void remove_vma_list(struct mm_struct *mm, struct vm_area_struct *vma){
unsigned long nr_accounted = 0;
update_hiwater_vm(mm);
do {
long nrpages = vma_pages(vma);
if (vma->vm_flags & VM_ACCOUNT)
nr_accounted += nrpages;
vm_stat_account(mm, vma->vm_flags, vma->vm_file, -nrpages);
vma = remove_vma(vma);
} while (vma);
vm_unacct_memory(nr_accounted);
validate_mm(mm);
}
主な関数は
vm_area_struct
であり、remove_vma
によって対応するkmem_cache_free
が解放され、チェーンテーブル上の次のvma
に戻る.static struct vm_area_struct *remove_vma(struct vm_area_struct *vma){
struct vm_area_struct *next = vma->vm_next;
might_sleep();
if (vma->vm_ops && vma->vm_ops->close)
vma->vm_ops->close(vma);
if (vma->vm_file)
fput(vma->vm_file);
mpol_put(vma_policy(vma));
kmem_cache_free(vm_area_cachep, vma);
return next;
}