不定长内存池之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_pool的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java全面的知识体系结构总结
- 下一篇: (计算机组成原理)第二章数据的表示和运算