__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 }