這個指南主要介紹如何使用libapr(apache portable runtime)。
版權所有,Copyright (C) 2005 INOUE Seiichiro <inoue&ariel-networks.com>,翻譯:成彥
原文地址:http://dev.ariel-networks.com/apr/apr-tutorial/html/apr-tutorial.html
轉載時請保留版權信息。
大多數libapr的API都依賴于內存池,借助內存池,簡化了內存塊的管理。想像一下沒有內存池系統的情況:你申請一些內存塊就必需逐個釋放它們,例如如果你申請了10個內存塊,你必需釋放10次,否則,你將遭受內存泄露的錯誤。內存池解決了這個令人感到繁瑣的問題,在申請一個內存池之后,你可以從內存池中申請多個內存塊,釋放它們的時候,你所需要做的就是銷毀內存池,這樣你就可以釋放所有的內存塊了。這有兩個優點,第一,它可以預防內存泄露的錯誤;第二,分配內存塊的開銷相對變低了。從某種意義上說,內存池迫使你遵循面向會話編程,一個內存池就是一種會話內容,這樣,處于同一個內存池中的對象就有相同的生命周期,你可以通過控制會話內容來控制對象。在一個會話的開始,你創建了一個內存池,接著,你在內存池中創建了一些對象,你不需要去關心這些對象的生命周期,最后,在會話結束的時候,你只需要將那個內存池銷毀就可以了。
注:通常,對象生命周期控制是程序開發最困難的部分,因此,針對這個問題還存在有一些技術,例如智能指針,垃圾回收機制等等。需要注意,同時使用這些技術有一定的難度,內存池也是這其中的一項技術,所以你不得不非常小心的使用它們。
注:在將來,libapr的內存池將變得不再那么重要。參見http://mail-archives.apache.org/mod_mbox/apr-dev/200502.mbox/%3c1f1d9820502241330123f955f@mail.gmail.com%3e.
下面有三個基本的API函數:
/* 摘自 apr_pools.h */
APR_DECLARE(apr_status_t) apr_pool_create(apr_pool_t **newpool, apr_pool_t *parent);
APR_DECLARE(void *) apr_palloc(apr_pool_t *p, apr_size_t size);
APR_DECLARE(void) apr_pool_destroy(apr_pool_t *p);
我們使用apr_pool_create()函數創建一個內存池,這個內存池將一直存活,直到你調用apr_pool_destroy()函數以后被銷毀。apr_pool_create()的第一個參數是一個結果輸出參數,是一個新創建的apr_pool_t類型的內存池對象。通過調用apr_palloc()來申請指定大小的內存塊,具體使用方法見mp-sample.c。
/* 摘自 mp-sample.c */
apr_pool_t *mp;
/* 創建內存池 */
apr_pool_create(&mp, NULL);
/* 從內存池中分配內存塊 */
char *buf1;
buf1 = apr_palloc(mp, MEM_ALLOC_SIZE);
簡單地說,我們可以像使用malloc(3)這樣使用apr_palloc(),也可以調用apr_pcalloc(),正如你猜到的,apr_pcalloc類似于calloc(3),apr_pcalloc返回一個被0填充了的內存塊。假如你使用了malloc(3)/calloc(3),你需要調用free(3)來釋放分配了的內存。但是在內存池中,你必不需要釋放每個內存塊,你只需要對該內存池調用apr_poll_destroy()函數從而釋放所有的內存塊。
注:使用apr_palloc()申請內存,其內存塊的大小沒有限制,然而,在內存池中申請大內存并不是什么好主意。內存池本質上是為了更小的內存塊而設計的,實際上,初始的內存池的大小是8000字節。如果你需要申請超過幾兆字節的內存塊時,那么就不要使用內存池。
注:默認情況下,內存池管理器從不將申請到的內存歸還給系統。如果程序要運行很長時間,這將是一個問題,推薦像下面的代碼那樣指定一個上限:
/* 設置上限,讓內存池管理器釋放內存,將內存返回給系統的示例代碼 */
#define YOUR_POOL_MAX_FREE_SIZE 32 /* apr_pool max free list size */
apr_pool_t *mp;
apr_pool_create(&mp, NULL);
apr_allocator_t* pa = apr_pool_allocator_get(mp);
if (pa) {
apr_allocator_max_free_set(pa, YOUR_POOL_MAX_FREE_SIZE);
}
這兒有兩個API函數需要知道,一個是apr_pool_clear(),另一個是apr_pool_cleanup_register(),apr_pool_clear()類似于apr_pool_destroy(),不同的是內存池將一直存在。示例代碼如下:
/* 使用apr_pool_clear()的例子 */
apr_pool_t *mp;
apr_pool_create(&mp, NULL);
for (i = 0; i < n; ++i) {
do_operation(..., mp);
apr_pool_clear(mp);
}
apr_pool_destroy(mp);
do_operation()里使用了內存池,分配了一些內存塊。假如在do_operation()之外不需要這些內存塊了,可以調用apr_pool_clear()函數,這樣能縮小內存的使用大小。如果你熟悉系統的棧內存的話,你會覺得內存池與棧內存一樣,調用apr_palloc只是如同移動SP(棧指針),調用apr_pool_clear()如同重置SP,兩者都是輕量級的操作。
使用apr_pool_cleanup_register()函數,可以在內存池清空/銷毀上設定一個鉤子(回調)函數,在內存池清空或是銷毀后調用這個函數,在這個回調函數中,你可以實現任何在內存池上的結束代碼。
關于內存池的最后一個主題是子池,每個內存池都可以有一個父內存池,因此,內存池構造了樹。apr_pool_create()的第二個參數表示父內存池,當這個參數為NULL時,新創建的內存池將變為一個根內存池,可以在這個根內存池上創建子內存池。在這個樹中對一個內存池調用apr_pool_destroy()函數,則該內存池的子內存池也將被銷毀;當對該內存池調用apr_pool_clear()函數,則這個內存池存在但是它的子內存池將被銷毀,上面提及到的那些清除函數,在子內存池銷毀時被調用。
注:當將NULL值做為清除回調函數時將會產生一個bug,你必須像下面的代碼那樣傳入apr_pool_cleanup_null:
/* 關于內存池典型bug的偽代碼 */
/* apr_pool_cleanup_register(mp, ANY_CONTEXT_OF_YOUR_CODE, ANY_CALLBACK_OF_YOUR_CODE, NULL); 這將產生一個bug */
/* 修正: */
apr_pool_cleanup_register(mp, ANY_CONTEXT_OF_YOUR_CODE, ANY_CALLBACK_OF_YOUR_CODE, apr_pool_cleanup_null);