不定長內存池之apr_pool
內存池可有效降低動態申請內存的次數,減少與內核態的交互,提升系統性能,減少內存碎片,增加內存空間使用率,避免內存泄漏的可能性,這么多的優點,沒有理由不在系統中使用該技術。
內存池分類:
1、 不定長內存池。典型的實現有apr_pool、obstack。優點是不需要為不同的數據類型創建不同的內存池,缺點是造成分配出的內存不能回收到池中。這是由于這種方案以session為粒度,以業務處理的層次性為設計基礎。
2、 定長內存池。典型的實現有LOKI、BOOST。特點是為不同類型的數據結構分別創建內存池,需要內存的時候從相應的內存池中申請內存,優點是可以在使用完畢立即把內存歸還池中,可以更為細粒度的控制內存塊。
與變長的相比,這種類型的內存池更加通用,另一方面對于大量不同的數據類型環境中,會浪費不少內存。但一般系統主要的數據結構都不會很多,并且都是重復申請釋放使用,這種情況下,定長內存池的這點小缺點可以忽略了。
一、apr
apr 是 apache 使用的底層庫,apache 是跨平臺的,其跨平臺正是基于 apr。個人覺得,使用apr有兩個好處,一是不用擔心跨平臺(事實上,我從來就不擔心,因為我寫的程序,從來都不跨平臺)。二是 apr 的 pool 很好用。pool 有兩個好處,一是可以理解成內存池,在 pool上 分配的內存,是不用釋放的,pool 銷毀的時候自然會釋放這些內存(所以銷毀(清理)pool變得異常重要,千萬不能忘了)。二是可以理解成資源管理器,分配資源后,然后在pool上注冊一個釋放資源的函數,pool 銷毀(清理)的時候,會調用這個函數,釋放資源。例如打開了一個文件,可以在 pool 上注冊一個關閉文件的函數,讓 pool 替你關閉文件。也可以在不銷毀(清理)pool 時,手動的釋放。具體可以看參考apr手冊。
APR的核心就是Apache的資源管理(池),我們將在本章的后面部分進行更加詳細的介紹。表3-1列出了APR中的所有模塊。
表3-1 APR模塊
名稱
目的
apr_allocator
內存分配,內部使用
apr_atomic
原子操作
apr_dso
動態加載代碼(.so/.dll)
apr_env
讀取/設定環境變量
apr_errno
定義錯誤條件和宏
apr_file_info
文件系統對象和路徑的屬性
apr_file_io
文件系統輸入/輸出
apr_fnmatch
文件系統模式匹配
apr_general
初始化/終結,有用的宏
名稱
目的
apr_getopt
命令參數
apr_global_mutex
全局鎖
apr_hash
哈希表
apr_inherit
文件句柄繼承助手
apr_lib
奇數和末端
apr_mmap
內存映射
apr_network_io
網絡輸入/輸出(套接字)
apr_poll
投票
apr_pools
資源管理
apr_portable
APR到本地映射轉換
apr_proc_mutex
進程鎖
apr_random
隨機數
apr_ring
環數據結構和宏
apr_shm
共享內存
apr_signal
信號處理
apr_strings
字符串操作
apr_support
內部支持函數
apr_tables
表格和數組函數
apr_thread_cond
線程條件
apr_thread_mutex
線程鎖
apr_thread_proc
線程和進程函數
apr_thread_rwlock
讀寫鎖
apr_time
時間/日期函數
apr_user
用戶和組ID服務
apr_version
APR版本
apr_want
標準頭文件支持
表3-2 APU模塊
名稱
目的
apr_anylock
透明的、任何鎖的封裝
apr_base64
Base-64編碼
apr_buckets
Buckets/Bucket brigade
apr_date
時間字符串解析
apr_dbd
針對SQL數據庫的常用API
apr_dbm
針對DBM數據庫的常用API
apr_hooks
鉤子實現宏
apr_ldap
LDAP授權API
apr_ldap_init
LDAP初始化API,主要應用在和LDAP服務器的初始安全連接
apr_ldap_option
設置LDAP選項的API
apr_ldap_url
解析和處理LDAP URL的API
apr_md4
MD4編碼
apr_md5
MD5編碼
apr_optional
可選函數
apr_optional_hooks
可選鉤子
apr_queue
線程安全的FIFO隊列
apr_reslist
資源池
apr_rmm
可再定位地址的內存
名稱
目的
apr_sdbm
SDBM庫
apr_sha1
SHA1編碼
apr_strmatch
字符串模式匹配
apr_uri
URI解析/構造
apr_uuid
用戶標識
apr_xlate
字符集轉換(I18N)
apr_xml
XML解析
基本的約定
APR和APR-UTIL采用了一些約定,使得它們的API具有同質性,并且易于使用。
3.3.1 參考手冊:API文檔和Doxygen
APR和APU在代碼層都有非常好的文檔。每一個公開函數和數據類型都在定義它們的頭文件中進行了注釋,使用了doxygen友好的格式。那些頭文件,或者doxygen生成的文檔,為程序員提供了完整的API參考手冊。如果你安裝了doxygen,那么就可以通過make dox命令從源代碼中生成你自己版本的APR參考手冊。
3.3.2 命名空間
所有的APR和APU的公開接口都使用了字符串前綴“apr_”(數據類型和函數)和“APR_”(宏),這就為APR定義了一個“保留”的命名空間。
在APR命名空間中,絕大部分的APR和APU模塊使用了二級命名空間。這個約定通常基于正在討論的那個模塊的名字。例如,模塊apr_dbd中的所有函數使用字符串“apr_dbd_”前綴。有時候使用一個明顯的描述性的二級命名空間。例如,在模塊apr_network_io中套接字操作使用“apr_socket_”前綴。
3.3.3 聲明的宏
APR和APU的公開函數使用類似于APR_DECLARE、APU_DECLARE和APR_ DECLARE_NONSTD的宏進行聲明。例如:
APR_DECLARE(apr_status_t) apr_initialize(void);
在很多的平臺上,這是一個空聲明,并且擴展為
apr_status_t apr_initialize(void);
例如在Windows的Visual C++平臺上,需要使用它們自己的、非標準的關鍵字,例如“_dllexport”來允許其他的模塊使用一個函數,這些宏就需要擴展以適應這些需要的關鍵字。
3.3.4 apr_status_t和返回值
在APR和APU中廣泛采用的一個約定是:函數返回一個狀態值,用來為調用者指示成功或者是返回一個錯誤代碼。這個類型是apr_status_t,在apr_errno.h中定義,并賦予整數值。因此一個APR函數的常見原型就是:
APR_DECLARE(apr_status_t) apr_do_something(…function args…);
返回值應當在邏輯上進行判斷,并且實現一個錯誤處理函數(進行回復或者對錯誤進行進一步的描述)。返回值APR_SUCCESS意味著成功,我們通常可以用如下的方式進行錯誤處理結構:
apr_status_t rv;
...
rv = apr_do_something(... args ...);
if (rv != APR_SUCCESS) {
/* 記錄一個錯誤 */
return rv;
}
有時候我們可能需要做得更多。例如,如果do_something是一個非閉塞的I/O操作并且返回APR_EAGAIN,我們可能需要重試這個操作。
有些函數返回一個字符串(char *或者const char *)、一個void *或者void。這些函數就被認為在沒有失敗條件或者在錯誤發生時返回一個空指針。
3.3.5 條件編譯
本質上說,APR的一些特色可能并不是每個平臺都支持的。例如,FreeBSD在5.x版本之前并沒有適合Apache的本地線程實現,因此線程在APR中就不被支持(除非編譯時手動設置相應的操作)。
為了在這種情況下應用程序依然能夠工作,APR為這些情況提供了APR_HAS_*宏。如果一個應用處于這種情況,它應當使用這些宏進行條件編譯。例如,一個模塊執行了一個操作,這個操作可能導致在多線程環境下的競爭條件,那么它就可能使用以下的方式。
#if APR_HAS_THREADS
rv = apr_thread_mutex_lock(mutex);
if (rv != APR_SUCCESS) {
/* 記錄一個錯誤 */
/* 放棄關鍵的操作*/
}
#endif
/* ... 在這里執行關鍵代碼... */
#if APR_HAS_THREAD
apr_thread_mutex_unlock(mutex);
#endif
二、apr_pool內存池。
pool本身并不直接從物理內存中分配或釋放,而是通過allocator(內存分配器)來統一管理,可以為新池創建新的allocator(內存分配器),但通常使用默認的全局allocator(內存分配器),這樣更有助于統一的內存管理。pool采用的是樹形的結構,在初始化內存池(apr_pool_initialize)時,建立根池,和全局allocator(內存分配器),以后建立的都是根結點的子孫結點可以從pool中分配任何大小的內存塊,但釋放的單位為pool,就是說pool釋放之前,從pool分配出的內存不能單獨釋放,看起來好像有點浪費。這里要注意的是,有些分配的內存塊,清除時有特別操作,這樣就需要要帶清除函數,在分配之后用apr_pool_cleanup_register注冊清除時用的函數。特殊的,如果內存塊里是線程對象,也不能用一般的清除函數,應該用apr_pool_note_subprocess注冊清除操作。
apr_pool中主要有3個對象,allocator、pool、block。pool從allocator申請內存,pool銷毀的時候把內存歸還allocator,allocator銷毀的時候把內存歸還給系統,allocator有一個owner成員,是一個pool對象,allocator的owner銷毀的時候,allocator被銷毀。在apr_pool中并無block這個單詞出現,這里大家可以把從pool從申請的內存稱為block,使用apr_palloc申請block,block只能被申請,沒有釋放函數,只能等pool銷毀的時候才能把內存歸還給allocator,用于allocator以后的pool再次申請。
常見函數:
對系統內存池初始化,全局的,一個進程只要初始化一次
apr_status_t apr_pool_initialize (void)
銷毀內存池對象,及內部的結構和子內存池
void apr_pool_terminate (void)
創建一個新的內存池
apr_status_t apr_pool_create_ex (apr_pool_t **newpool, apr_pool_t *parent, apr_abortfunc_t abort_fn, apr_allocator_t *allocator)
創建一個新的內存池,apr_pool_create_ex的使用默認參數簡化版
apr_status_t apr_pool_create (apr_pool_t **newpool, apr_pool_t *parent)
獲取內存池使用的內存分配器
apr_allocator_t * apr_pool_allocator_get (apr_pool_t *pool)
清除一個內存池的內容,清除后內容為空,但可以再使用
void apr_pool_clear (apr_pool_t *p)
釋構一個內存池
void apr_pool_destroy (apr_pool_t *p)
從池中分配內存
void * apr_palloc (apr_pool_t *p, apr_size_t size)
從池中分配內存,并將分配出來的內存置0
void * apr_pcalloc (apr_pool_t *p, apr_size_t size)
設置內存分配出錯時的調用函數
void apr_pool_abort_set (apr_abortfunc_t abortfunc, apr_pool_t *pool)
獲取內存分配出錯時的調用函數
apr_abortfunc_t apr_pool_abort_get (apr_pool_t *pool)
獲取池的父池
apr_pool_t * apr_pool_parent_get (apr_pool_t *pool)
判斷a是否是b的祖先
int apr_pool_is_ancestor (apr_pool_t *a, apr_pool_t *b)
為內存池做標簽
void apr_pool_tag (apr_pool_t *pool, const char *tag)
設置與當前池關聯的數據
apr_status_t apr_pool_userdata_set (const void *data, const char *key, apr_status_t(*cleanup)(void *), apr_pool_t *pool)
設置與當前池關聯的數據,與apr_pool_userdata_set類似,但內部不拷貝數據的備份,如常量字符串時就有用
apr_status_t apr_pool_userdata_setn (const void *data, const char *key, apr_status_t(*cleanup)(void *), apr_pool_t *pool)
獲取與當前池關聯的數據
apr_status_t apr_pool_userdata_get (void **data, const char *key, apr_pool_t *pool)
注冊內存塊的清除函數,每塊銷毀時要特別處理的都要注冊下,在cleanups里加入一個項
void apr_pool_cleanup_register (apr_pool_t *p, const void *data, apr_status_t(*plain_cleanup)(void *), apr_status_t(*child_cleanup)(void *))
刪除內存塊的清除函數,從cleanups里移除一個項,放入free_cleanups中
void apr_pool_cleanup_kill (apr_pool_t *p, const void *data, apr_status_t(*cleanup)(void *))
用新的child_cleanup,替換原來老的child_cleanup
void apr_pool_child_cleanup_set (apr_pool_t *p, const void *data, apr_status_t(*plain_cleanup)(void *), apr_status_t(*child_cleanup)(void *))
執行內存塊的清除函數,進從清除函數的隊列cleanups中刪除
apr_status_t apr_pool_cleanup_run (apr_pool_t *p, void *data, apr_status_t(*cleanup)(void *))
一個空的內存塊清除函數
apr_status_t apr_pool_cleanup_null (void *data)
執行所有的子清除函數child_cleanup
void apr_pool_cleanup_for_exec (void)
帶調試信息內存池函數,功能跟上面的一樣,只是多了調試信息
apr_status_t apr_pool_create_ex_debug (apr_pool_t **newpool, apr_pool_t *parent, apr_abortfunc_t abort_fn, apr_allocator_t *allocator, const char *file_line)
void apr_pool_clear_debug (apr_pool_t *p, const char *file_line)
void apr_pool_destroy_debug (apr_pool_t *p, const char *file_line)
void * apr_palloc_debug (apr_pool_t *p, apr_size_t size, const char *file_line)
void * apr_pcalloc_debug (apr_pool_t *p, apr_size_t size, const char *file_line)
一般可以不調用創建allocator的函數,而是使用的默認全局allocator。但是apr_pool提供了一系列函數操作allocator,可以自己調用這些函數:
apr_allocator_create
apr_allocator_destroy
apr_allocator_alloc
apr_allocator_free 創建銷毀allocator
apr_allocator_owner_set
apr_allocator_owner_get 設置獲取owner
apr_allocator_max_free_set 設置pool銷毀的時候內存是否直接歸還到操作系統的閾值
apr_allocator_mutex_set
apr_allocator_mutex_get 設置獲取mutex,用于多線程
apr_pool的一個大缺點就是從池中申請的內存不能歸還給內存池,只能等pool銷毀的時候才能歸還。為了彌補這個缺點,apr_pool的實際使用中,可以申請擁有不同生命周期的內存池。
三、實例
#include "stdafx.h"
#include "apr_pools.h"
#include <new>
#pragma comment(lib,"libapr-1.lib")
int main(int argc, char* argv[])
{
apr_pool_t *root;
apr_pool_initialize();//初始化全局分配子(allocator),并為它設置mutext,以用于多線程環境,初始化全局池,指定全局分配子的owner是全局池
apr_pool_create(&root,NULL);//創建根池(默認父池是全局池),根池生命期為進程生存期。分配子默認為全局分配子
{
apr_pool_t *child;
apr_pool_create(&child,root);//創建子池,指定父池為root。分配子默認為父池分配子
void *pBuff=apr_palloc(child,sizeof(int));//從子池分配內存
int *pInt=new (pBuff) int(5);//隨便舉例下基于已分配內存后,面向對象構造函數的調用。
printf("pInt=%d/n",*pInt);
{
typedef struct StudentInfo{
char szName[20];
bool nSex;
};
apr_pool_t *grandson;
apr_pool_create(&grandson,root);
void *pBuff2=apr_palloc(grandson,sizeof(StudentInfo));
StudentInfo *pSI=new (pBuff2) StudentInfo();
strcpy(pSI->szName,"zhangsan");
pSI->nSex = 1;
printf("Name=%s,sex=%d/n",pSI->szName,pSI->nSex);
apr_pool_destroy(grandson);
}
apr_pool_destroy(child);//釋放子池,將內存歸還給分配子
}
apr_pool_destroy(root);//釋放父池,
apr_pool_terminate();//釋放全局池,釋放全局allocator,將內存歸還給系統
return 0;
}
參考資料:
apr官方網站:http://apr.apache.org/
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成