__mt_alloc源碼分析(4)

class __pool<false>

終於,我們要開始研究mt allocator裏最核心的東西了。__pool是實際上的內存池類,以前我們介紹的那麼多類,都是在它的基礎上建立起來的“上層建築”。

 

188    template<bool _Thread>

189      class __pool;

 

這是__pool的原型,模板參數_Thread表示是否支持多線程。接着分別對_Threadfalsetrue的情況,進行了特化實現。由於有一部分__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_recordbin的內部結構,下圖雖然在前面出現過,但現在仍是最好的註釋。在單線程情況下,只有_M_first[0]會真正被使用到,所以__pool<false>當然不會傻到給_M_first分配4097個指針空間。後面可以看到,__pool<false>裏所有bin_M_first數組的長度是1

_M_address是一個內存塊鏈表,每當__poolOS申請到一塊新內存,都會加入到這個鏈表中去。

 

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   是否直接使用newdelete

 

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表示是否直接使用newdelete進行內存分配和釋放。如果它爲true,那麼__pool<false>無需任何初始化,allocatedeallocate函數直接調用newdelete。這裏只把_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_binmap0,在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.h7681行可以找到它們的定義。爲什麼__bin_max__ct的類型是_Binmap_typeunsigned short int?

2.代碼裏唯一給_M_binmap數組元素賦值的語句是“*__bp++ = __bint”,而__bint作爲_Binmap_type類型變量只在“__bin_max <<= 1”的時候會加1。即使__bin_maxsize_t類型的,__bint的值也不會超過3232位機器,或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    }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章