1. free源码分析—__libc_free
本章继续之前的glibc中的《malloc源码分析》系列开始分析free的源代码,malloc的源码分析可以查看博客里同类别文章下的《malloc源码分析—1》到《malloc源码分析—5》,因此free的源码中有一些结构和malloc相似的地方就不会做过多的介绍了。
首先在glibc的malloc.c中有如下定义,
|
|
因此,free
是__libc_free
的别名,实际执行的是__libc_free
函数,下面来看,
|
|
__libc_free
首先查看是否有__free_hook
函数,如果有就直接调用,这里假设没有默认函数可用。接下来通过mem2chunk
将虚拟内存的指针mem
转换为对应的chunk指针p
,
|
|
因为一个使用中的chunk结构体只使用其prev_size
和size
字段,因此这里只需要减去2*SIZE_SZ
。
接下来,chunk_is_mmapped
用来检查size最低三位中的标志位,判断该chunk是否是由mmap分配的,如果是,就调用munmap_chunk
释放该chunk并返回,在调用munmap_chunk
之前,需要更新全局的mmap阀值和收缩阀值。
再往下,如果该chunk不是由mmap分配的,就通过arena_for_chunk
获得分配区指针ar_ptr
,并调用_int_free
释放内存。_int_free
放在下一章分析,本章重点分析munmap_chunk
函数。
1.1 munmap_chunk
munmap_chunk用来释放由mmap分配的chunk,下面来看,
|
|
首先获得前一个chunk的指针block
,计算这两个chunk的size
之和至total_size
,接着对全局结构mp_
进行相应的设置后,就通过__munmap
释放这两个chunk。根据malloc的源码可知,由mmap分配的chunk是独立的,大部分情况下,p->prev_size
为0,因此这里还是释放一个chunk,特殊情况下需要释放两个chunk,特殊情况请参考_int_malloc
中的代码。__munmap
再往下就是系统调用了,定义在linux内核代码的mmap.c中,
|
|
profile_munmap
为空函数,下面看vm_munmap,
|
|
这里就是信号量的操作,最主要是执行do_munmap
释放内存,为了方便分析和查看,只复制了do_munmap
的关键代码,
|
|
首先对传入的参数进行检查,需要释放的虚拟内存的开始地址start
和长度len
必须按页对齐,且不能释放内核空间的内存。
接着通过find_vma
在进程的管理内存的AVL树上查找第一个结束地址大于start
的虚拟内存vma
,如果vma->vm_start >= end
,说明需要释放的虚拟内存本来就不存在,因此什么也不做返回;如果start > vma->vm_start
,则表示找到的vma
包含了需要释放的内存,这时候通过__split_vma
函数将该vma
根据start
地址划分成两块,因此需要判断虚拟内存的数量是否超过了系统的限制sysctl_max_map_count
。为了方便分析,下面只给出了__split_vma
的几行关键代码,
|
|
首先分配一个vm_area_struct
结构体new
,然后将vma
中的所有内容拷贝到new
中,new_below
决定将原vma
按照addr
决定的地址分割成两个后,vma
中保存低地址部分还是高地址部分。do_munmap
第一次进入__split_vma
时new_below
为0,因此返回的vma
保存低地址部分。然后调用vma_adjust
对低地址部分的vma
进行相应的设置,主要是更改其end
变量为addr
,并将高地址部分插入进程内存的管理树中。
回到do_munmap
中,find_vma(mm, end)
获得最尾部的last
,如果该last
包含了需要释放的虚拟内存,就继续将其拆成两部分,这时候由于new_below
为1,因此返回的last
为高地址部分。返回后,vma
将指向低地址部分。
结合前面的分析,在执行detach_vmas_to_be_unmapped
之前,原来的vma被拆成如下所示
| prev | vma | … | vma | last |mm->mmap
的赋值是在vma_adjust
中,其实就是拆分后低地址处那块虚拟内存。
接下来detach_vmas_to_be_unmapped
用于将所有和要释放的内存有交集的vma
从红黑树中删除,并形成一个以vma
为链表头的链表。根据刚刚vma
被拆开成的结果,其实就是取数组中所有除了prev
和last
的元素构成一个链表。即
| prev | vma | … | vma | last |
经过detach_vmas_to_be_unmapped
后变成,
| prev| last |
| vma | … | vma |
往下就是要释放第二部分。
|
|
回到do_munmap
中,unmap_region
就是用于释放内存了。下面来看,
|
|
lru_add_drain
用于将percpu
变量pagevec
对应的每个page
放回其对应的zone
的lru
链表中,因为马上要解映射了,这些缓存的page变量由可能被改变。tlb_gather_mmu
构造了一个mmu_gather
变量并初始化。
接下来的unmap_vmas
用于解映射,即释放存在物理页面映射的虚拟内存,
|
|
这里开始遍历vma
链表,对每个vma
调用unmap_single_vma
进行释放,
|
|
这里主要就是通过unmap_page_range
进行释放。再往下因为涉及太多linux内核内存管理的知识,这里就不深入分析了,最后就是通过虚拟地址找到页表pte
,解开和物理页面之间的映射,并设置一些page结构。
由于unmap_vmas
后,一些页表里没有了相对应的物理页面,free_pgtables
将这些页表释放。
|
|
这里主要是调用free_pgd_range
。该函数中,假设要释放的虚拟内存为vma,其前一个vma为prev
,后一个为last
,如果释放完vma
后,prev->vm_end
到last->vm_start
大于一个pgd管理的内存大小(32位系统下为4MB),就释放pgd里的所有页表,如果小于4MB,就什么也不做返回。
再回到do_munmap
中,arch_unmap
是一些体系结构相关的操作,不管它。remove_vma_list
释放每个vma
对应的vm_area_struct
结构至slab分配器中。
|
|
主要的函数是remove_vma
,该函数通过kmem_cache_free
释放对应的vma
,并返回链表上的下一个vma
。
|
|