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

你可能感兴趣的:(__mt_alloc源码分析(4))