__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函數。現在當你再看它的代碼時,你是否對每一句代碼都瞭然於胸了?

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