class __pool<false>
終於,我們要開始研究mt allocator裏最核心的東西了。__pool是實際上的內存池類,以前我們介紹的那麼多類,都是在它的基礎上建立起來的“上層建築”。
188 template<bool _Thread>
189 class __pool;
這是__pool的原型,模板參數_Thread表示是否支持多線程。接着分別對_Thread爲false和true的情況,進行了特化實現。由於有一部分__pool的實現代碼在GCC源碼中的“libstdc++-v3/src/ mt_allocator.cc”(以下簡稱mt_allocator.cc)裏,所以讀者請注意代碼所屬的文件,沒有標明的情況下默認爲mt_allocator.h。
191 /// Specialization for single thread.
192 template<>
193 class __pool<false> : public __pool_base
單線程下的__pool<false>,它的基類是__pool_base。__pool_base同時也是多線程下__pool<true>的基類,它的作用就是提煉出線程無關的部分。
194 {
195 public:
196 union _Block_record
197 {
198 // Points to the block_record of the next free block.
199 _Block_record* volatile _M_next;
200 };
_Block_record是每個塊的頭信息,在單線程下只有一個成員,即指向下一個塊的指針。對於分配給用戶使用的塊,_M_next是沒有用的。但是爲了與多線程下的頭信息結構兼容,單線程的_M_next也不會被用戶數據佔用。
202 struct _Bin_record
203 {
204 // An "array" of pointers to the first free block.
205 _Block_record** volatile _M_first;
206
207 // A list of the initial addresses of all allocated blocks.
208 _Block_address* _M_address;
209 };
_Bin_record是bin的內部結構,下圖雖然在前面出現過,但現在仍是最好的註釋。在單線程情況下,只有_M_first[0]會真正被使用到,所以__pool<false>當然不會傻到給_M_first分配4097個指針空間。後面可以看到,__pool<false>裏所有bin的_M_first數組的長度是1。
_M_address是一個內存塊鏈表,每當__pool從OS申請到一塊新內存,都會加入到這個鏈表中去。
127 struct _Block_address
128 {
129 void* _M_initial;
130 _Block_address* _M_next;
131 };
成員變量_M_initial指向當前申請到的新內存,_M_next指向下一個。
__pool<false>成員變量
__pool<false>的成員變量包括2部分:自己的,和從__pool_base繼承來的。
我們先介紹它自己的。
245 // An "array" of bin_records each of which represents a specific
246 // power of 2 size. Memory to this "array" is allocated in
247 // _M_initialize().
248 _Bin_record* volatile _M_bin;
顯然,這就是上圖中的bin數組。
250 // Actual value calculated in _M_initialize().
251 size_t _M_bin_size;
這是bin數組的長度。在__pool<false>對象構造的時候,它並不知道bin數組會有多長,因爲這與其他參數有關,實際上_M_bin_size的值是在初始化函數_M_initialize裏計算出來的,當然只計算一次。
__pool_base成員變量
從__pool_base繼承來的成員變量有:
172 // Configuration options.
173 _Tune _M_options;
我們以前介紹過的可調參數,包括7個:
l 字節對齊:空閒塊裏數據區的偏移
l 多少字節以上的內存直接用new分配
l 可分配的最小的數據區大小
l 每次從OS申請的內存塊的大小
l 可支持的最多線程數目
l 單個線程能保存的空閒塊的百分比(超過的空閒塊會歸還給全局空閒鏈表)
l 是否直接使用new和delete
175 _Binmap_type* _M_binmap;
字節數到bin索引的映射數組。_M_binmap的作用就是最快的找到用戶申請的內存大小應該歸哪個bin管理。
177 // Configuration of the pool object via _M_options can happen
178 // after construction but before initialization. After
179 // initialization is complete, this variable is set to true.
180 bool _M_init;
記錄__pool是否已經初始化過了。註釋的意思是,對於_M_options的更改可能發生在__pool構造完,但是還沒有初始化的時候,所以用_M_init標記。當初始化工作做完了,_M_init會設置成true。
需要補充的一點是,_Binmap_type的類型:
50 // Using short int as type for the binmap implies we are never
51 // caching blocks larger than 32768 with this allocator.
52 typedef unsigned short int _Binmap_type;
註釋裏說,由於_M_binmap數組元素的類型是short int,所以分配的塊最大不能超過32768。
說實話對這個結論我不太理解。因爲據我所知,_M_binmap數組元素的意義是bin數組的索引,所以short int最大值爲32768只是表示bin數組最大長度爲32768,而232768已經是很大的一個內存塊了。不過暫時我保留自己的看法,等研究到後面的代碼再說。
初始化
__pool<false>的初始化工作包括2部分,構造函數和初始化函數。
238 explicit __pool()
239 : _M_bin(NULL), _M_bin_size(1) { }
240
241 explicit __pool(const __pool_base::_Tune& __tune)
242 : __pool_base(__tune), _M_bin(NULL), _M_bin_size(1) { }
除了默認構造函數,__pool<false>還可以用調節參數__tune來構造。有一點注意的是,_M_bin_size被初始化爲1。
函數_M_initialize是__pool<false>用來初始化內部參數的,它的實現代碼位於mt_allocator.cc裏面。
<mt_allocator.cc>
155 void
156 __pool<false>::_M_initialize()
157 {
158 // _M_force_new must not change after the first allocate(), which
159 // in turn calls this method, so if it's false, it's false forever
160 // and we don't need to return here ever again.
161 if (_M_options._M_force_new)
162 {
163 _M_init = true;
164 return;
165 }
可調參數_M_force_new表示是否直接使用new和delete進行內存分配和釋放。如果它爲true,那麼__pool<false>無需任何初始化,allocate和deallocate函數直接調用new和delete。這裏只把_M_init設置爲true,然後返回。
167 // Create the bins.
168 // Calculate the number of bins required based on _M_max_bytes.
169 // _M_bin_size is statically-initialized to one.
170 size_t __bin_size = _M_options._M_min_bin;
可調參數_M_min_bin是可分配的最小的數據區大小,也是bin管理的最小字節數。
171 while (_M_options._M_max_bytes > __bin_size)
可調參數_M_max_bytes表示“多少字節以上的內存直接用new分配”,那麼就是bin管理的最大字節數。
172 {
173 __bin_size <<= 1;
174 ++_M_bin_size;
這裏計算出了從_M_min_bin到_M_max_bytes之間需要多少個bin,每個bin管理的字節數是指數遞增的。注意_M_bin_size初始值爲1。
175 }
176
177 // Setup the bin map for quick lookup of the relevant bin.
178 const size_t __j = (_M_options._M_max_bytes + 1) * sizeof(_Binmap_type);
計算_M_binmap數組的字節數。_M_binmap數組的長度是“_M_max_bytes + 1”,每個元素的類型是_Binmap_type。
179 _M_binmap = static_cast<_Binmap_type*>(::operator new(__j));
分配足夠的空間給_M_binmap。
180 _Binmap_type* __bp = _M_binmap;
181 _Binmap_type __bin_max = _M_options._M_min_bin;
182 _Binmap_type __bint = 0;
183 for (_Binmap_type __ct = 0; __ct <= _M_options._M_max_bytes; ++__ct)
184 {
185 if (__ct > __bin_max)
186 {
187 __bin_max <<= 1;
188 ++__bint;
189 }
190 *__bp++ = __bint;
191 }
這段代碼設置_M_binmap每個元素的值,使其等於對應的bin索引。首先注意到__bp和__ct,初始值分別是_M_binmap和0,在for循環裏是同步增長的。然後看到__bint,它表示bin索引,初始值爲0,每當“__ct > __bin_max”的時候加1。而__bin_max則表示每個bin管理的字節數,從_M_min_bin開始,在設置完前面的所有_M_binmap元素的時候,“__bin_max <<= 1”變成下一個bin管理的字節數。
如果讀者看懂了這段代碼,我就提出幾個問題:
1._M_min_bin和_M_max_bytes的類型都是size_t,在mt_allocator.h的76和81行可以找到它們的定義。爲什麼__bin_max和__ct的類型是_Binmap_type(unsigned short int)?
2.代碼裏唯一給_M_binmap數組元素賦值的語句是“*__bp++ = __bint”,而__bint作爲_Binmap_type類型變量只在“__bin_max <<= 1”的時候會加1。即使__bin_max是size_t類型的,__bint的值也不會超過32(32位機器,或64,在64位機器上),也就是說_M_binmap數組元素的最大值不會超過64,那麼何必要用unsigned short int類型表示?
193 // Initialize _M_bin and its members.
194 void* __v = ::operator new(sizeof(_Bin_record) * _M_bin_size);
195 _M_bin = static_cast<_Bin_record*>(__v);
分配bin數組的內存,_M_bin_size在前面已經計算出來了。
196 for (size_t __n = 0; __n < _M_bin_size; ++__n)
197 {
198 _Bin_record& __bin = _M_bin[__n];
199 __v = ::operator new(sizeof(_Block_record*));
200 __bin._M_first = static_cast<_Block_record**>(__v);
從這裏可以看出來,__pool<false>裏每個bin的_M_first數組只有一個元素。
201 __bin._M_first[0] = NULL;
202 __bin._M_address = NULL;
203 }
204 _M_init = true;
把_M_init設置爲true,表示初始化工作完成了。
205 }