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_belowvmaで決定したアドレスに従って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が取り外されたばかりの結果、vmaprevを除くすべての要素がチェーンテーブルを構成している.すなわち、|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を対応するpagezoneチェーンテーブルに戻すために使用され、すぐにマッピングが解除されるので、これらのキャッシュされた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;
}