歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> squid的內存管理機制

squid的內存管理機制

日期:2017/2/27 16:04:04   编辑:Linux教程
1. Squid MemPool機制
1.1. MemPool原理關於MemPool,最權威的說法來自於cf.data.pre
NAME: memory_pools
COMMENT: on|off
TYPE: onoff
DEFAULT: on
LOC: Config.onoff.mem_pools
DOC_START
        If set, Squid will keep pools of allocated (but unused) memory
        available for future use.  If memory is a premium on your
        system and you believe your malloc library outperforms Squid
        routines, disable this.
DOC_END

Squid會維護一個內存池,其中保留著一些已經分配但還沒有使用的內存以供未來使用。如果內存資源在你的系統中十分緊張或者你確信你的malloc 庫比squid的強,那麼可以關掉這個配置項。

整個MemPool體系中最核心的存儲結構是一個Stack類型的全局變量Pools。Pools中的每一個item存放著一種MemPool的指針,結構如圖1.1所示

主要的MemPool相關方法有:
void memConfigure()
根據Config.MemPools.limit等配置值來調整MemPool的大小

void memInitModule(void)
為全局結構pools分配內存

void memCleanModule (void)
對於pools中所有的MemPool都執行一遍memPoolDestroy,然後clean掉pools自身

static void memShrink(size_t new_limit)
縮小所有的MemPool的大小直到達到new_limit為止

MemPool * memPoolCreate(const char *label, size_t obj_size) 重要
增加一種MemPool的類型label是其名稱,obj_size通常都傳一個sizeof,並初始化這個MemPool的pstack

void memPoolDestroy(MemPool * pool) 重要
刪除一種MemPool。注意:刪除操作只是將pools.items中的某一項置為NULL。因此我們寫代碼遍歷pools.items時要注意跳過NULL的項。

void * memPoolAlloc(MemPool * pool) 非常重要
從指定的pool中分配一塊內存。
如果該pool中還有空閒內存(pool->pstack.count > 0),則直接從pool->pstack中取一塊返回。
如果該pool中已經沒有空閒內存了,則直接分配一塊內存返回。(此時並不會增加pstack的大小,而是要等到free的時候,才把這次分配的內存放到pstack中去!)
這裡有2個細節:
第一,   分配內存時,會修改該pool的已分配內存的計數。從pstack中取的時候,減少了pool->meter.idle,而直接分配的時候,增加了pool->meter.alloc。
第二,   分配內存時,會根據配置項zero_buffers來決定是用xmalloc還是xcalloc
 
注意,每個pool的pstack最開始都是空的,系統運行起來之後的一段時間內,都是使用新分配的內存,這段時間之後,通常都是使用舊的內存。

void memPoolFree(MemPool * pool, void *obj) 非常重要
將obj所占的內存放到pool中。
如果將obj的大小加上所有pool的空閒塊大小超過了某一限制mem_idle_limit,則直接free掉obj
如果沒有超過這一限制,將obj push到pool中。

1.2. MemPool在squid中的使用分析Squid中,MemPool的使用主要是在mem.c中。它自己維護著一個MemPool指針數組
static MemPool *MemPools[MEM_MAX];

數組中的每一個元素對應著一個mem_type。mem_type的值包括MEM_2K_BUF、MEM_4K_BUF、MEM_REQUEST_T、MEM_HTTP_REPLY等我們熟悉的值。
系統初始化時,會初始化每一個類型的mem_type,例如
memDataInit(MEM_2K_BUF, "2K Buffer", 2048, 10);

在代碼中經常用於分配內存的方法是memAllocate,這個方法是對memPoolAlloc的一個封裝。
void * memAllocate(mem_type type)
{
        return memPoolAlloc(MemPools[type]);
}

內存釋放的方法也是類似的。
void memFree(void *p, int type)
{
        memPoolFree(MemPools[type], p);
}

另外一種內存分配的方法是memAllocBuf。
void * memAllocBuf(size_t net_size, size_t * gross_size)

該方法是不需要傳進mem_type參數的。系統會自動找到一個匹配net_size的mem_type的pool來進行內存分配。查找的方法是,逐個判斷net_size是否小於等於2K,4K,8K…64K。這段代碼應該可以修改,增加一些小於2K的大小,以減少內存的浪費;但是這樣做可能會造成一些其他問題,建議謹慎試驗後再用。
memAllocBuf方法應用於一些讀寫buffer的分配,例如connState->in.buf、AddVaryState->buf等等。

與之相對的是memFreeBuf
void memFreeBuf(size_t size, void *buf)

使用了mem.c中提供的方法來管理內存的主要類型有:
MEM_NONE,
MEM_2K_BUF,
MEM_4K_BUF,
MEM_8K_BUF,
MEM_16K_BUF,
MEM_32K_BUF,
MEM_64K_BUF,
MEM_ACL,
MEM_ACL_DENY_INFO_LIST,
MEM_ACL_IP_DATA,
MEM_ACL_LIST,
MEM_ACL_NAME_LIST,
MEM_ACL_REQUEST_TYPE,
MEM_AUTH_USER_T,
MEM_AUTH_USER_HASH,
MEM_ACL_PROXY_AUTH_MATCH,
MEM_ACL_USER_DATA,
MEM_ACL_TIME_DATA,
MEM_CACHE_DIGEST,
MEM_CLIENT_INFO,
MEM_STORE_CLIENT_BUF,
MEM_LINK_LIST,
MEM_DLINK_NODE,
MEM_DONTFREE,
MEM_DREAD_CTRL,
MEM_DWRITE_Q,
MEM_FQDNCACHE_ENTRY,
MEM_FWD_SERVER,
MEM_HELPER_REQUEST,
MEM_HELPER_STATEFUL_REQUEST,
MEM_HTTP_HDR_CC,
MEM_HTTP_HDR_CONTENT_RANGE,
MEM_HTTP_HDR_ENTRY,
MEM_HTTP_HDR_RANGE,
MEM_HTTP_HDR_RANGE_SPEC,
MEM_HTTP_REPLY,
MEM_INTLIST,
MEM_IPCACHE_ENTRY,
MEM_MD5_DIGEST,
MEM_MEMOBJECT,
MEM_MEM_NODE,
MEM_NETDBENTRY,
MEM_NET_DB_NAME,
MEM_RELIST,
MEM_REQUEST_T,
MEM_STOREENTRY,
MEM_WORDLIST,
MEM_IDNS_QUERY,
MEM_EVENT,
MEM_TLV,
MEM_SWAP_LOG_DATA,
MEM_ACL_CERT_DATA

1.3. MemPool上手指南MemPool的使用分為以下幾種:
1.3.1 特定類型的MemPool特定類型的MemPool是Squid中最常見的MemPool用法,MEM_2K_BUF、MEM_4K_BUF、MEM_REQUEST_T、MEM_HTTP_REPLY等類型的數據都是使用這種方式管理內存的。當然,我們自己定義的類型也可以使用這種方式來管理。
要加入一種特定類型的MemPool,名為my_type,需要做以下幾項工作:
1. 修改mem_type枚舉型,在MEM_MAX前面加入我們自己的類型,如MEM_MYTYPE。
2. 修改memInit函數,在後面加上
memDataInit(MEM_MYTYPE, "my data type", sizeof(my_type), 0);

3. 分配內存時,傳入MEM_MYTYPE
my_type *mt = memAllocate(MEM_MYTYPE)

4. 釋放內存時,同樣傳入MEM_MYTYPE
memFree(mt, MEM_TYPE)

1.3.2通過cbdata使用MemPool以及內存讀寫buffer這種類型的MemPool應用主要是在一些常駐內存的結構中的讀寫buffer。例如我們自己有一種結構,這種結構是一種State,這種State需要在函數回調的過程中被反復傳遞,並且這種State結構中需要一些讀寫buffer的話,這個State就應該被定義為一種cbdata,它的buffer就需要用memAllocBuf/memFreeBuf來管理。(cbdata的具體細節會在第2章中討論)
這裡有一個實例:如果squid開啟了vary機制,對於每一個url會在磁盤上留下一個vary的索引文件,在這個索引文件中存著這個url的各種變化(壓縮、非壓縮)的object的key。因此在刷新的時候就需要遍歷這個索引文件找到所有的key,並用這個key刷掉相應的文件。如下所示
http://www.xxx.com
HTTP/1.1 200 OK
Date: Tue, 16 Jun 2009 08:58:59 GMT
Server: Apache/2.0.52 (Red Hat)
X-Powered-By: PHP/4.3.9
Cache-Control: max-age=600, max-age=120
Last-Modified: Wed, 10 Jun 2009 05:19:27 GMT
Expires: Tue, 16 Jun 2009 09:00:59 GMT
Content-Length: 7221
Connection: close
Content-Type: x-squid-internal/vary
 
Key: 1234567890123456
Vary: Accept-Encoding
……
Key: 0987654321098765
Vary: …….


這種情況的遍歷需要讀取整個Object的內容,而Object的讀取不能一次完成,而是需要異步進行的。因此我們需要創建一個適合於在回調過程中來回傳遞的State類型TraverseVaryState,其中的buf就是一個讀寫buffer。
typedef struct
{
        StoreEntry *e;
        store_client *sc;
        char *buf;
        size_t buf_size;
        size_t buf_offset;
        squid_off_t seen_offset;
        int action;
        int method;
} TraverseVaryState;
 
CBDATA_TYPE(TraverseVaryState);

TraverseVaryState和它的buf的分配過程很簡單:
CBDATA_INIT_TYPE(TraverseVaryState);
state = cbdataAlloc(TraverseVaryState);
 
state->buf = memAllocBuf(4096, &state->buf_size);

使用這種方式分配的內存就具有了cbdata或memPool內存的一切優點,包括可管理、可統計等。

這兩種結構的使用和其他內存結構沒有什麼區別:
static void storeTraverseVaryRead(void *data, char *buf, ssize_t size)
{
        TraverseVaryState *state = data;
//……使用State……
        storeClientCopy(state->sc, state->e,
                        state->seen_offset,
                        state->seen_offset,
                        state->buf_size - state->buf_offset,
                        state->buf + state->buf_offset,
                        storeTraverseVaryRead,
                        state);
}

這兩種內存的釋放過程同樣簡單:
memFreeBuf(state->buf_size, state->buf);
 
cbdataFree(state);

2. cbdata機制對於cbdata機制,Squid官方也有自己的解釋,在cbdata.c中。大概意思是說,一些方法中要使用回調數據的指針,但是如果管理不好(例如反復釋放),就很容易引起squid掛掉。對於這種情況,我們可以把這些數據的指針放到cbdata中,在注冊回調函數之前鎖定它,然後在調用回調函數之前對其進行校驗,並在完成時將它釋放掉。
這樣做的好處是,即使free發生在unlock之前,這個數據也會被標示為invalid,因此(cbdataValid)會保證回調不會被執行。在Unlock時,lock count會減少,如果減少為0,這個數據就是invalid的。
/*
 * These routines manage a set of registered callback data pointers.
 * One of the easiest ways to make Squid coredump is to issue a
 * callback to for some data structure which has previously been
 * freed.  With these routines, we register (add) callback data
 * pointers, lock them just before registering the callback function,
 * validate them before issuing the callback, and then free them
 * when finished.
 *
 * In terms of time, the sequence goes something like this:
 *
 * foo = cbdataAlloc(sizeof(foo),NULL);
 * ...
 * some_blocking_operation(..., callback_func, foo);
 *   cbdataLock(foo);
 *   ...
 *   some_blocking_operation_completes()
 *   if (cbdataValid(foo))
 *   callback_func(..., foo)
 *   cbdataUnlock(foo);
 * ...
 * cbdataFree(foo);
 *
 * The nice thing is that, we do not need to require that Unlock
 * occurs before Free.  If the Free happens first, then the
 * callback data is marked invalid and the callback will never
 * be made.  When we Unlock and the lock count reaches zero,
 * we free the memory if it is marked invalid.
 */

2.1 cbdata原理cbdata的結構如下(刪掉了無用的成員)
typedef struct _cbdata
{
        int valid;
        int locks;
        int type;
        void *y;                        /* cookie used while debugging */
        union
        {
                void *pointer;
                double double_float;
                int integer;
        } data;
} cbdata;

valid是記錄這個數據是否有效的標志位,locks是鎖的個數,type是記錄這個cbdata的data成員究竟是哪種類型的(type雖然定義成為了int類型,但實際上存的是cbdata_type這個枚舉型的數據),data是實際存放數據的地方。
另外,squid為了管理cbdata,還創建了兩個全局變量cbdata_index和cbdata_types
struct
{
        MemPool *pool;
        FREE *free_func;
}     *cbdata_index = NULL;
int cbdata_types = 0;

其中cbdata_index是存放每一種cbdata的MemPool和釋放函數的一個動態開辟空間的數組,通過cbdata_type枚舉型的值作為下標來訪問。每增加一種類型的cbdata(程序中可以隨時增加cbdata),這個數組就realloc一次。ctdata_types是cbdata_index的長度。

關於cbdata的一系列重要方法如下:
CBDATA_TYPE
#define CBDATA_TYPE(type)       static cbdata_type CBDATA_##type = 0

在要使用cbdata的地方,首先需要調用CBDATA_TYPE這個宏,它的作用是聲明一個cbdata_type類型的全局靜態變量。例如CBDATA_TYPE(my_type),實際上就是聲明了一個變量
static cbdata_type CBDATA_my_type = 0;

CBDATA_INIT_TYPE
#define CBDATA_INIT_TYPE(type)  (CBDATA_##type ? 0 : (CBDATA_##type = cbdataAddType(CBDATA_##type, #type, sizeof(type), NULL)))

CBDATA_INIT_TYPE看起來稍微復雜一點,但是也不難理解,它實際上是對cbdataAddType的一個封裝,只是加上了一個判斷CBDATA_##type是否等於0。
a) 如果CBDATA_TYPE沒有執行過,那麼CBDATA_INIT_TYPE也是不可能編譯通過的。
b) 如果CBDATA_INIT_TYPE沒有執行過,那麼CBDATA_##type肯定等於0,因此會執行到cbdataAddType。
c) 否則不進行任何操作
CBDATA_INIT_TYPE_FREECB與CBDATA_INIT_TYPE非常類似,只是多傳入了一個free_func。
而CREATE_CBDATA 與CREATE_CBDATA_FREE 則是分別對應於CBDATA_INIT_TYPE和CBDATA_INIT_TYPE_FREECB的,只不過CREATE系列的函數主要是用於系統預定義的cbdata類型,而INIT系列的函數是用於程序中自定義的cbdata類型的。

cbdataAddType
cbdata_type cbdataAddType(cbdata_type type, const char *name, int size, FREE * free_func)

增加一種cbdata,實際上的操作就是擴展cbdata_index,增加cbdata_types,並初始化這種類型所對應的MemPool。注意,初始化MemPool時所用的size是size + OFFSET_OF(cbdata, data),這樣分配出來的內存除了包括type的大小,還包括了cbdata的valid、locks、type等其他成員的大小。
一個有意思的細節,傳給MemPool的label值為:cbdata %s (%d)
這也是為什麼我們用squidclient mgr:mem所看到的列表中,如果包括了我們自己聲明的cbdata類型(例如TraverseVaryState),就會出現一個”cbdata TraverseVaryState (34)”的標記。

cbdataAlloc
#define cbdataAlloc(type) ((type *)cbdataInternalAlloc(CBDATA_##type))
void * cbdataInternalAlloc(cbdata_type type)

cbdataInternalAlloc就是分配cbdata內存的函數了。但是裡面有一點不太容易理解的地方:MemPool分配出來的內存是type的大小加上cbdata其他成員的大小,返回的值是memPoolAlloc返回的結果看作cbdata的指針所對應的data成員。
這樣實際上是一個較小的cbdata的指針指向了一片較大的內存,cbdata的data成員指向了程序中真正使用的內存的首地址。data成員實際上只是一個內存地址標識的作用,並沒有被真正當做union來使用!(這一段歡迎高手批判)
另外,還有一個細節,在cbdataAlloc中將cbdata->y 設置成為了 CBDATA_COOKIE(cbdata->data),並在後面的代碼中會經常斷言二者相等。這樣做是為了保護cbdata的內存不會被意想不到的代碼篡改。之前FC5出現過cbdata斷言錯的bug,就是這個機制幫助我們提早發現了代碼中的錯誤。

cbdataValid
int cbdataValid(const void *p)

判斷p是否還是一個有效的cbdata。前面提到過,valid的定義是:p為NULL或者p沒有被free過。這個函數通常被用來終止回調過程,例如:一個cbdata->data為valid,才繼續進行回調過程,否則退出。

cbdataLock
void cbdataLock(const void *p)

為cbdata加鎖,實際上就是c->locks++

cbdataUnlock
void cbdataUnlock(const void *p)

為cbdata解鎖,實際上就是c->locks--,當locks減到0,並且valid==0,會調用free_func,並調用MemPoolFree來釋放p指向的內存。

cbdataFree
void * cbdataInternalFree(void *p)

釋放cbdata,但是如果locks>0,就不會真正釋放,而是將valid置為0,然後return掉,等待下次有人調用cbdataUnlock時釋放。當然,valid置為0並不影響data的使用,data仍然是一個合法的指針,只要程序中不去判斷cbdataValid就可以繼續使用data,取決於程序的具體邏輯。

簡單總結一下,由於cbdata可能是被多個回調過程共享的,cbdataLock實際上表示“我要用這個cbdata,你們不要真正把它釋放掉了!”通常是一個過程進入異步過程(例如epoll)之前調用一下。
cbdataUnlock實際上表示“我用完了,有沒有人想把它釋放掉?”並且檢查大家是否都用完了(locks)以及是否有人想釋放掉它(valid),如果都符合就釋放掉。
cbdataFree表示“這個東西也該釋放掉了,你們要是都用完了,就釋放掉它吧!”,如果locks為0就自己釋放,否則就等待cbdataUnlock來釋放。

2.2 cbdata在squid中的使用在squid中使用cbdata機制的內存類型主要有:
CBDATA_UNKNOWN = 0,
CBDATA_UNDEF = 0,
CBDATA_acl_access,
CBDATA_aclCheck_t,
CBDATA_clientHttpRequest,
CBDATA_ConnStateData,
CBDATA_ErrorState,
CBDATA_FwdState,
CBDATA_generic_cbdata,
CBDATA_helper,
CBDATA_helper_server,
CBDATA_statefulhelper,
CBDATA_helper_stateful_server,
CBDATA_HttpStateData,
CBDATA_peer,
CBDATA_ps_state,
CBDATA_RemovalPolicy,
CBDATA_RemovalPolicyWalker,
CBDATA_RemovalPurgeWalker,
CBDATA_store_client,
CBDATA_FIRST_CUSTOM_TYPE = 1000


另外我們也可以在程序代碼中自定義cbdata類型,方法見1.3.2章。
2.3 cbdata上手指南cbdata的使用在1.3.2章中已經描述了一部分,包括cbdata的聲明類型、分配與釋放等。這裡換一個場景來講解cbdataLock、cbdataUnlock等方法的應用。
選取大家比較熟悉的aclCheck函數為例:
static void aclCheck(aclCheck_t * checklist)
{
while ((A = checklist->access_list) != NULL)
{
…….
    if (match)
    {
        aclCheckCallback(checklist, allow);
        return;
    }
…….
    checklist->access_list = A->next;
    if (A->next)
        cbdataLock(A->next);
    cbdataUnlock(A);
}
aclCheckCallback(checklist, allow);
}


這個函數在循環中會拿出checklist的access_list中的每一個acl_access的acl_list與checklist去做匹配,如果匹配上則執行回調。
在每一次循環的末尾,會調用一次cbdataLock鎖住access_list中的下一個acl_access,即宣布將要使用那個acl_access,請別人不要free掉它。然後調用cbdataUnlock來放棄對本次的acl_access的使用權。

3. Squid String機制字符串(char數組)機制是一種比較特殊的內存管理方式,由於其長度的不定性,為它的內存管理引入了一些復雜度。而我們的代碼中之所以要用到malloc/calloc等內存分配方式,很大一部分也是處理字符串用的。mem.c中對String也提供了一種特殊的方式來管理字符串內存。
3.1 mem.c中對字符串內存的管理 mem.c對字符串類型使用了一種與眾不同的管理方式:
首先,字符串類型所對應的MemPool是特殊定義的。
#define mem_str_pool_count 3
static const struct
{
        const char *name;
        size_t obj_size;
} StrPoolsAttrs[mem_str_pool_count] =
{
        {      
                "Short Strings", 36,
        },                              /* to fit rfc1123 and similar */
        {      
                "Medium Strings", 128,
        },                              /* to fit most urls */
        {      
                "Long Strings", 512
        }                               /* other */
};

然後,提供了對字符串的分配與釋放的專門方法
void * memAllocString(size_t net_size, size_t * gross_size)
void memFreeString(size_t size, void *buf)

注意:memAllocString返回的還只是“一塊”內存,只是這塊內存的大小是向36、128和512“取整”了。這塊內存可以被當作char *來使用,這樣就成了字符串。
3.2 String.c中對字符串的封裝 當然,只是提供“一塊”內存的分配與釋放方法,對於應用來說還是不夠方便的,因此Squid中還提供了String.c來封裝字符串。其中的主要定以及方法包括:
struct _String {
    /* never reference these directly! */
    unsigned short int size;    /* buffer size; 64K limit */
    unsigned short int len;     /* current length  */
    char *buf;
};
void stringInit(String * s, const char *str)
void stringClean(String * s)
void stringAppend(String * s, const char *str, int len)
String stringDup(const String * s)

注意一個細節,stringDup中是返回了棧上的一個struct的copy。看起來如果結構體比較小的話,為了程序的簡單性,偶爾整體copy一下也無妨。
我們在處理字符串的時候,如果拿不准是否會有內存洩露問題的話,就應該使用Squid自身提供的內存機制。不推薦直接使用memAllocString等mem.c中的函數,而是應該使用String.c中的函數!
String結構在嵌入其他結構體中的時候,推薦直接嵌入一個String類型的變量,而非String指針,這樣就省了free 掉String的過程,但是一定要記得調用stringClean!
4. 總結squid的內存管理機制是圍繞著MemPool展開的。而MemPool僅是提供了一種與具體業務無關的,內存分配與釋放的管理機制。
而建立在MemPool之上的,更加方便使用的機制有兩個:一個是mem.c提供的MemPool封裝,另一個就是cbdata。這兩種機制都有各自的適用范圍。
一般來說,程序對內存使用方法分為3類,
a) 全局變量,包括掛在全局變量下面的成員變量,例如StoreEntry、fd等。
b) 局部變量,即在一個函數調用過程中聲明的變量,它可能會被傳遞給子函數等。
c) 回調過程中用到的變量,它介於全局變量和局部變量之間,在一個函數中產生,但這個函數因為某種原因(如等待epoll等外部條件)而半途中止運行,並將自己或其他函數注冊到外部條件的回調中去,而且還需要用到之前聲明的變量。例如LocateVaryState這類的狀態機,還包括clientHttpRequest等類型。

不同的變量存在著不同的問題,解決方法入表4.1所示
類型 問題 解決方法 全局變量 數量難以統計,一旦出現bug導致洩露,難以追查 使用mem.c提供的memAllocate等方法,或者直接用MemPool 局部變量 無 可以直接使用xmalloc,xcalloc等 回調變量 數量難以統計,可能出現多處代碼釋放同一變量的情況 使用cbdata機制 表 4.1 使用squid自身帶來的各種機制,肯定能夠獲得事半功倍的效果,但是也可能會引入一些問題。
Copyright © Linux教程網 All Rights Reserved