堆的Tcache机制
堆的Tcache机制
tcache是glibc 2.26(ubuntu 17.10)之后引入的一种技术 ,目的是提升堆管理的性能。但提升性能的同时舍弃了很多安全检查,也因此有利很多新的利用方式。
相关结构体
tcache 引入了两个新的结构体,tcache_entry
和tcache_perthread_struct
。
这其实和fastbin很像,但又不一样。
tcache_entry
1 | /* We overlay this structure on the user-data portion of a chunk when |
tcache_entry用于链接空闲的chunk结构体,其中的 next指针指向下一个大小相同的chunk。需要注意的是这里的next指向chunk的user data,而fastbin的fd指向chunk开头的地址。
而且,tcache_entry 会复用空闲chunk的user data部分。
tcache_perthread _struct
1 | /* There is one of these for each thread, which contains the |
每个tread(线程)都会维护一个 tcache_perthread_struct,他是整个tcache的管理结构,一共有 TCACHE_MAX_BINS个计数器和 TCACHE_MAX_BINS项tcache_entry,其中
- tcache_entry用单项链表的方式链接了相同大小的处于空闲状态(free后)的chunk,这一点上和fastbin很像。
- counts记录了 tcache_entry链上空闲chunk的数目,每个链条上最多可以有七个chunk。
基本工作方式
- 第一次malloc时,会先malloc一快内存用来存放tcache_perthread_struct。
- free内存,且size小于small bin size时
- tcache 之前会放到fastbin或者unsorted bin中
- tcache后:
- 先放到对应的tcache中,直到tcache被填满(默认为七个)
- tcache被填满之后,再次free的内存和之前一样被放到fastbin或者unsorted bin中
- tcache 中的chunk不会被合并(不取消inuse bit)
- malloc内存,且size在tcache范围内
- 先从tcache取chunk,直到tcache为空
- tcache为空后,从bin中找
- tcache 为空时,如果 fastbin/smallbin/unsorted bin中有size符合的chunk,会先把 fastbin/smallbin/unsorted bin中的chunk放到tcache中,直到填满。之后再从tcache中取;因此chunk在bin中和tcache 中的顺序会反过来。
源码分析
接下来从源码的角度分析一下tcache。
_libc_malloc
第一次malloc时,会进入到MAYBE_INIT_TCACHE()
1 | void * |
_tcache_init()
其中MAYBE_INIT_TCACHE()
在tcache为空(即第一次malloc)时调用了tcache_init()
,直接查看tcache_init()
1 | tcache_init(void) |
tcache_init()
成功返回后。tcache_perthread_struct被成功建立了。
申请内存
接下来进入申请内存的步骤
1 | // 从 tcache list 中获取内存 |
在tcache->entries
不为空时,将进入tcache_get()
的流程获取chunk,否则与tcache机制前的流程类似,这里主要分析第一种tcache_get()
。这里也可以看出tcache的优先级很高,比fastbin还要高(fastbin的申请在没进入tcache的流程中)。
tcache_get()
1 | /* Caller must ensure that we know tc_idx is valid and there's |
tcache_get()
就是获得chunk的过程了。可以看出这个过程还是很简单的,从tcache->entries[tc_idx]
中获得第一个chunk,tcache->counts
减一,几乎没有任何保护。
_libc_free()
看完申请,再看看tcache时的释放
1 | void |
__libc_free()
,没有太多变化,MAYBE_INIT_TCACHE()
在tcache不为空失去了作用。
_int_free()
跟进_int_free()
1 | static void |
判断tc_idx
合法,tcache->counts[tc_idx]
在七个以内时,就进入tcache_put()
,传递的两个参数是要释放的chunk和该chunk对应的size在tcache中的下标。
tcache_put()
1 | /* Caller must ensure that we know tc_idx is valid and there's room |
tcache_puts()
完成了把释放的chunk插入到tcache->entries[tc_idx]
链表头部的操作,也几乎没有任何保护。并且 没有把p位置零。