__pool<false>的内存分配
本节研究__pool<false>在分配内存时做的事情。回忆一下__mt_alloc:: allocate函数的内容,一般情况下用户通过某个bin的_M_first[0]可以得到想要的内存,但是在_M_first[0]为0时,__mt_alloc:: allocate调用了:
701 // Null, reserve.
702 __c = __pool._M_reserve_block(__bytes, __thread_id);
那么_M_reserve_block做了些什么?我们来看一下。
<mt_allocator.cc>
117 char*
118 __pool<false>::_M_reserve_block(size_t __bytes, const size_t __thread_id)
函数_M_reserve_block的原型,参数__bytes是用户申请的内存字节数,__thread_id是当前线程分配的id。在单线程__pool<false>里,__thread_id总是为0。
119 {
120 // Round up to power of 2 and figure out which bin to use.
121 const size_t __which = _M_binmap[__bytes];
122 _Bin_record& __bin = _M_bin[__which];
123 const _Tune& __options = _M_get_options();
124 const size_t __bin_size = (__options._M_min_bin << __which)
125 + __options._M_align;
__which指出了__bytes字节数归哪个bin管理,于是“(__options._M_min_bin << __which)”得到的就是这个bin管理的字节数上限。但是每个空闲块除了数据区,还有头信息,所以需要加上__options._M_align。最终__bin_size等于每个空闲块的字节大小。
126 size_t __block_count = __options._M_chunk_size - sizeof(_Block_address);
127 __block_count /= __bin_size;
可调参数_M_chunk_size是__pool“每次从OS申请的内存块的大小”,那么在这块内存上能建立起多少个空闲内存块呢?上面的代码就是计算这个的。
注意到刚开始__block_count的值是_M_chunk_size减去sizeof(_Block_address),这说明内存的前几个字节被用于生成_Block_address结构。也许读者还记得bin结构(_Bin_record)里有一个_M_address成员变量,用于记录所有从OS得到的内存块链表,_M_address正好就是指向_Block_address的指针。
129 // Get a new block dynamically, set it up for use.
130 void* __v = ::operator new(__options._M_chunk_size);
131 _Block_address* __address = static_cast<_Block_address*>(__v);
132 __address->_M_initial = __v;
133 __address->_M_next = __bin._M_address;
134 __bin._M_address = __address;
从OS申请大小为_M_chunk_size的内存,最前面几个字节作为_Block_address,_M_initial指向自己,然后把这块内存加入到_M_address链表中。如下图所示。
136 char* __c = static_cast<char*>(__v) + sizeof(_Block_address);
137 _Block_record* __block = reinterpret_cast<_Block_record*>(__c);
138 __bin._M_first[__thread_id] = __block;
139 while (--__block_count > 0)
140 {
141 __c += __bin_size;
142 __block->_M_next = reinterpret_cast<_Block_record*>(__c);
143 __block = __block->_M_next;
144 }
145 __block->_M_next = NULL;
用剩下的内存构造出__block_count个空闲内存块,并串连起来,最后__bin._M_first[__thread_id]会指向这个链表的第一个元素。
147 __block = __bin._M_first[__thread_id];
148 __bin._M_first[__thread_id] = __block->_M_next;
别忘了我们还要给用户返回一个空闲内存块,现在就是取出这个块的时候。
150 // NB: For alignment reasons, we can't use the first _M_align
151 // bytes, even when sizeof(_Block_record) < _M_align.
152 return reinterpret_cast<char*>(__block) + __options._M_align;
给用户使用的数据区在空闲块的某个偏移位置,这在前面讨论过。
153 }
__pool<false>的内存释放
当用户使用完内存,需要调用__mt_alloc:: deallocate释放,进而会调用__pool<false>::_M_reclaim_block归还给内存池。
<mt_allocator.cc>
102 void
103 __pool<false>::_M_reclaim_block(char* __p, size_t __bytes)
函数_M_reclaim_block的原型,参数__p是用户归还的内存地址,__bytes是数据区大小。
104 {
105 // Round up to power of 2 and figure out which bin to use.
106 const size_t __which = _M_binmap[__bytes];
107 _Bin_record& __bin = _M_bin[__which];
找到管理__bytes字节数内存块的bin。几乎每个操作都会涉及到这一步,由此我们知道为什么要用_M_binmap来映射这种关系:用空间换时间。
109 char* __c = __p - _M_get_align();
110 _Block_record* __block = reinterpret_cast<_Block_record*>(__c);
通过用户给出的数据区地址,得到这个空闲块的首地址,和内存分配里给块地址加上_M_get_align()正好配对。
112 // Single threaded application - return to global pool.
113 __block->_M_next = __bin._M_first[0];
114 __bin._M_first[0] = __block;
把空闲块加入到_M_first[0]链表里。
115 }
__pool<false>的销毁
为了与_M_initialize函数对应,__pool<false>还实现了_M_destroy函数,用于销毁从OS动态申请的内存。不过这个函数实际上根本没有被调用,包括析构函数里也没有。所以__pool仍和其他许多内存池一样,让OS自动回收进程里的所有内存,而不是调用_M_destroy来归还。
<mt_allocator.cc>
81 void
82 __pool<false>::_M_destroy() throw()
83 {
84 if (_M_init && !_M_options._M_force_new)
如果没有初始化,或者_M_force_new为true,就不会进行任何辅助内存的申请,这从_M_initialize里可以看出来。
85 {
86 for (size_t __n = 0; __n < _M_bin_size; ++__n)
87 {
88 _Bin_record& __bin = _M_bin[__n];
89 while (__bin._M_address)
90 {
91 _Block_address* __tmp = __bin._M_address->_M_next;
92 ::operator delete(__bin._M_address->_M_initial);
93 __bin._M_address = __tmp;
归还每个bin里向OS动态申请的内存。也许_M_address的作用就在于此,虽然这里的代码并没有被真正运行过。
94 }
95 ::operator delete(__bin._M_first);
归还_M_first数组的内存。
96 }
97 ::operator delete(_M_bin);
98 ::operator delete(_M_binmap);
归还bin数组和_M_binmap映射数组。
99 }
100 }