__mt_alloc源码分析(2)

struct __per_type_pool_policy

 在继续深入到__common_pool_base和其他pool类里面之前,我好奇的看了下__per_type_pool_policy的定义,发现一些事情,于是决定先说说它。

 

545    /// @brief  Policy for individual __pool objects.

546    template<typename _Tp, template <bool> class _PoolTp, bool _Thread>

547      struct __per_type_pool_policy

548      : public __per_type_pool_base<_Tp, _PoolTp, _Thread>

 

__per_type_pool_policy的原型,除了名字,它与__common_pool_policy相比还少了一个模板参数_Tp,这正是它与分配对象类型相关的原因

 

549      {

550        template<typename _Tp1, template <bool> class _PoolTp1 = _PoolTp,

551            bool _Thread1 = _Thread>

552          struct _M_rebind

553          { typedef __per_type_pool_policy<_Tp1, _PoolTp1, _Thread1> other; };

 

这里就是我发现的“一些事情”:__per_type_pool_policy::_M_rebind::other是与_Tp1类型相关的,即不同的_Tp1类型会实例化出不同类型的other__per_type_pool_policy),那么这里我们看到了__common_pool_policy:: _M_rebind存在的意义——与其他的policy保持接口一致。接口一致意味着我们可以不用修改代码,就随意替换不同的policy

 

555        using  __per_type_pool_base<_Tp, _PoolTp, _Thread>::_S_get_pool;

556        using  __per_type_pool_base<_Tp, _PoolTp, _Thread>::_S_initialize_once;

557    };

 

struct __common_pool_base

__mt_alloc的类层次结构太多了,即使到了__common_pool_base这里,还只是一个概念性的“封装”类。不过有一点进步的是,它开始处理多线程情况了。

 

395    template<template <bool> class _PoolTp, bool _Thread>

396      struct __common_pool_base;

 

这是__common_pool_base的申明,第一个模板参数_PoolTp是实际的内存池类,第二个参数_Thread表示是否支持多线程。接着分别针对_Threadfalsetrue的情况,进行了偏特化实现。__common_pool_base最重要的作用就是实现_S_initialize_once,即第一次调用allocate时需要进行的初始化工作。下面是_Thread = false情况下的实现:

 

404        static void

405        _S_initialize_once()

406        {

407     static bool __init;

 

静态变量__init记录是否进行过初始化。根据C++标准,全局或局部静态变量会自动初始化为默认值,而bool的默认值为false。所以此处即使没有明确写出__init的初始值,它也会被初始化为false

 

408     if (__builtin_expect(__init == false, false))

 

除了第一次调用_S_initialize_once会发现__initfalse外,其他时候是不会有“__init == false”的。

 

409       {

410         _S_get_pool()._M_initialize_once();

411         __init = true;

 

把真正的内存池进行参数初始化,然后把__init修改为true

 

412       }

413        }

 

由于是在单线程情况下,任何代码都是按照顺序执行的,所以这里不需要任何的加锁与解锁操作。

而在多线程情况下,事情会变复杂些。

 

423        static void

424        _S_initialize()

425        { _S_get_pool()._M_initialize_once(); }

 

辅助静态函数_S_initialize的作用是包装内存池的非静态成员函数_M_initialize_once,以便在下面的_S_initialize_once函数里作为一般函数指针传递给__gthread_once

 

427        static void

428        _S_initialize_once()

429        {

430     static bool __init;

431     if (__builtin_expect(__init == false, false))

 

上面的代码和单线程下是一样的,不需解释。

 

432       {

433         if (__gthread_active_p())

434           {

435         // On some platforms, __gthread_once_t is an aggregate.

436         static __gthread_once_t __once = __GTHREAD_ONCE_INIT;

437         __gthread_once(&__once, _S_initialize);

438           }

 

这段代码里出现了几个我们极少见到的单词,__gthread_active_p__gthread_once_t__GTHREAD_ONCE_INIT__gthread_once。我觉得等到介绍完整个函数后,再回头介绍这些难点,会比较好。

 

440         // Double check initialization. May be necessary on some

441         // systems for proper construction when not compiling with

442         // thread flags.

443         _S_get_pool()._M_initialize_once();

444         __init = true;

 

无论上面平台相关的代码最后的处理结果是什么,最后还是调用一次内存池的_M_initialize_once,保证初始化工作的正确运行。

 

445       }

446        }

 

__gthread_active_p

那么现在回到432-438之间的代码,它们的主要作用是处理各平台之间对锁的实现与定义的差异。由于我也只是在一种平台下研究这份源码,所以我觉得还是专注于某种流行的平台下的代码比较好。如果想要“一口吞掉整个大象”,最终可能一无所获。

我所在的环境是Linux 2.6内核,GCC版本为4.1.2,使用POSIX多线程库,其他选项默认。那么我们开始接触第2个源代码文件“/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../../include/c++/4.1.2/i386-redhat-linux/bits/gthr-default.h”,以下简称为gthr-default.h。在gthr-default.h里,__gthread_active_p()最终变成了如下代码:

 

<gthr-default.h>

149  static inline int

150  __gthread_active_p (void)

151  {

152    static void *const __gthread_active_ptr

153      = __extension__ (void *) &__gthrw_(pthread_cancel);

 

上面定义了一个静态的const指针__gthread_active_ptr,初步猜测是指向了一个函数地址,这个函数与pthread_cancel有关系。熟悉POSIX线程的读者肯定知道pthread_cancelPOSIX线程函数中的一员,那么这里其实就是在检测这些函数是否可用。

 

154    return __gthread_active_ptr != 0;

 

这句话佐证了我们的初步猜测。

 

155  }

 

__extension__

首先我们看看__extension__关键字。以下是在《The Definitive Guide to GCC》上找到的一段解释:

当使用gcc-pedantic选项来编译C语言程序的时候,可以通过-std=version来指定代码应该符合什么版本的语言标准。但是,通过-pedantic(或它的扩展-pedantic-errors)来查看你的程序是否严格符合ISO C语言标准,是错误的做法,即使你加上-ansi选项也不行。因为这些选项只会在标准“需要警告”的地方产生警告信息——绝对严格的ISO语法分析器将产生庞大的警告信息。所以,-pedantic的目的是检测出“无理由”的非兼容代码,禁用GNU扩展,禁用未列入标准的C++或传统C语言特征。

另一个不能用-pedantic来检测是否兼容ISO标准的原因是,-pedantic会忽略那些以下划线开头和结尾的可选关键字,以及在__extension__关键字之后的语句。有一条原则是,这2种例外情况不能发生在应用程序代码里,因为可选关键字和__extension__关键字都是给系统头文件使用的,应用程序绝对不能使用它们。

既然__extension__只是为了防止编译器的警告信息,那么我们可以专注于后面的__gthrw_了。

__gthrw_

首先,在我的环境下:

 

<gthr-default.h>

68   # define __gthrw_(name) __gthrw_ ## name

 

说明一下,在C语言宏定义里,##是连接的意思,即A##B表示AB;而#则表示字符串转义,即#A表示”A”。那么__gthrw_(pthread_cancel)就会变成__gthrw_pthread_cancel。那么__gthrw_pthread_cancel又是什么呢?我找到了如下代码:

 

<gthr-default.h>

62   # ifndef __gthrw_pragma

63   #  define __gthrw_pragma(pragma)

64   # endif

65   # define __gthrw2(name,name2,type) /

66     extern __typeof(type) name __attribute__ ((__weakref__(#name2))); /

67     __gthrw_pragma(weak type)

 

<gthr-default.h>

81   #define __gthrw3(name) __gthrw2(__gthrw_ ## name, __ ## name, name)

82   __gthrw3(pthread_once)

83   __gthrw3(pthread_getspecific)

84   __gthrw3(pthread_setspecific)

85   __gthrw3(pthread_create)

86   __gthrw3(pthread_cancel)

87   __gthrw3(pthread_mutex_lock)

88   __gthrw3(pthread_mutex_trylock)

89   __gthrw3(pthread_mutex_unlock)

90   __gthrw3(pthread_mutex_init)

 

首先__gthrw3(pthread_cancel)会变成:

__gthrw2(__gthrw_pthread_cancel, pthread_cancel, pthread_cancel)

然后又变成了:

extern __typeof(pthread_cancel) __gthrw_pthread_cancel __attribute__ ((__weakref__("pthread_cancel")));

typeof

那么,上面的代码究竟是什么意思呢?好吧,还是老办法:逐个击破!

typeof类似于sizeof,它返回一个类型,即后面所接的表达式的类型,而__typeof是和typeof同意义的可选关键字。举几个例子:

typedef typeof (int *) IntPtr; ——IntPtr表示int指针类型;

int * x;

typeof(x) y; ——yx一样是int指针类型变量;

typeof(*x) z; ——z是一个int类型变量;

typeof(&x) t; ——tint **类型变量;

好了,现在__typeof(pthread_cancel)就很容易理解了。pthread_cancel的原型为:

int pthread_cancel(pthread_t thread);

所以__typeof(pthread_cancel)就表示这样的一个函数指针类型,后面的__gthrw_pthread_cancel则是变量名。

__attribute__

__attribute__关键字需要和别的关键字搭配,用来说明代码的属性,比如函数属性,类型属性,变量属性等,如果你想深入研究,可以看看http://gcc.gnu.org/onlinedocs/gcc/Keyword-Index.html#Keyword-Index。我这里只关系它和后面的__weakref__搭配时的意义。

__weakref__

__attribute__ ((weakref ("target")));

等价于:

__attribute__ ((weak, weakref, alias ("target ")));

第一个weak表示:申明的符号是一个弱符号(weak symbol),而非全局符号,主要用于那些能被用户代码重载的库函数申明里,当然它也能用于申明非函数符号。

第二个weakref 表示:这个符号是一个弱引用(weak reference)。弱引用就是一个别名(alias),本身不需要任何定义,弱引用需要指定目标符号(后面的alias部分)。在静态可执行文件里,弱引用符号会被转换成绝对符号(absolute symbols),并赋值为0;在动态链接的可执行文件或共享的目标文件里,弱引用是没有定义的,赋初值为0。在执行过程中,链接器会搜索目标符号,如果找到的话,把弱引用绑定到目标符号上;如果没有找到,把弱引用绑定到地址0,不产生任何运行时错误消息。

历史上,未定义的弱引用被用来测试某个函数是否存在。

 

OK,列出了这么多的介绍,最终我们得出一个结论:gthr-default.h文件第152153行申明了一个名为__gthread_active_ptr的指针,并用名为__gthrw_pthread_cancel的弱引用为其赋值。__gthrw_pthread_cancel在链接时会搜索名为pthread_cancel的函数,如果找到,那么赋值为函数地址;如果没有找到,赋值为0。于是__gthread_active_ptr最终要么等于函数pthread_cancel的地址,要么等于0,取决于链接的库里是否包含了这个函数。

如此复杂的代码只是为了得到这样简单的结果,实在让人觉得感叹!我开始明白那句20%80%的谚语了。

 

回到多线程下__common_pool_base::_S_initialize_once函数里,__gthread_once_t最终的类型是pthread_once_t__GTHREAD_ONCE_INIT最终的值是0,它们都是POSIX多线程库的一部分。__gthread_once的定义为:

 

<gthr-default.h>

516  static inline int

517  __gthread_once (__gthread_once_t *once, void (*func) (void))

518  {

519    if (__gthread_active_p ())

520      return __gthrw_(pthread_once) (once, func);

 

根据我们以前的分析,最后一句话会变成:return __gthrw_pthread_once (once, func);

如果pthread_once函数存在的话,会调用pthread_once(once,func);否则什么也不会做,也不会有运行时错误产生。pthread_once函数是POSIX多线程库的一部分。

 

521    else

522      return -1;

523  }

 

至此我们就研究完了这个“深奥”的多线程下__common_pool_base::_S_initialize_once函数。现在当你再看它的代码时,你是否对每一句代码都了然于胸了?

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