Chinaunix首页 | 论坛 | 博客
  • 博客访问: 35212
  • 博文数量: 10
  • 博客积分: 246
  • 博客等级: 二等列兵
  • 技术积分: 155
  • 用 户 组: 普通用户
  • 注册时间: 2011-10-02 10:56
文章分类
文章存档

2012年(10)

我的朋友

分类: LINUX

2012-11-06 17:09:51

 

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类型的全局变量PoolsPools中的每一个item存放着一种MemPool的指针,结构如图1.1所示

 SEQ \* ARABIC 1.1

 

 

 

主要的MemPool相关方法有:

void memConfigure()

 

根据Config.MemPools.limit等配置值来调整MemPool的大小

 

void memInitModule(void)

 

为全局结构pools分配内存

 

void memCleanModule (void)

 

对于pools中所有的MemPool都执行一遍memPoolDestroy,然后cleanpools自身

 

static void memShrink(size_t new_limit)

 

缩小所有的MemPool的大小直到达到new_limit为止

 

MemPool * memPoolCreate(const char *label, size_t obj_size) 重要

 

增加一种MemPool的类型label是其名称,obj_size通常都传一个sizeof,并初始化这个MemPoolpstack

 

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

 

注意,每个poolpstack最开始都是空的,系统运行起来之后的一段时间内,都是使用新分配的内存,这段时间之后,通常都是使用旧的内存。

 

void memPoolFree(MemPool * pool, void *obj) 非常重要

 

obj所占的内存放到pool中。

如果将obj的大小加上所有pool的空闲块大小超过了某一限制mem_idle_limit,则直接freeobj

如果没有超过这一限制,将obj pushpool中。

 

1.2. MemPoolsquid中的使用分析

Squid中,MemPool的使用主要是在mem.c中。它自己维护着一个MemPool指针数组

static MemPool *MemPools[MEM_MAX];

 

数组中的每一个元素对应着一个mem_typemem_type的值包括MEM_2K_BUFMEM_4K_BUFMEM_REQUEST_TMEM_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_sizemem_typepool来进行内存分配。查找的方法是,逐个判断net_size是否小于等于2K4K8K…64K这段代码应该可以修改,增加一些小于2K的大小,以减少内存的浪费;但是这样做可能会造成一些其他问题,建议谨慎试验后再用。

memAllocBuf方法应用于一些读写buffer的分配,例如connState->in.bufAddVaryState->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

特定类型的MemPoolSquid中最常见的MemPool用法,MEM_2K_BUFMEM_4K_BUFMEM_REQUEST_TMEM_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的各种变化(压缩、非压缩)的objectkey。因此在刷新的时候就需要遍历这个索引文件找到所有的key,并用这个key刷掉相应的文件。如图1.2所示

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: …….

1.2

 

这种情况的遍历需要读取整个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);

 

使用这种方式分配的内存就具有了cbdatamemPool内存的一切优点,包括可管理、可统计等。

 

这两种结构的使用和其他内存结构没有什么区别:

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是记录这个cbdatadata成员究竟是哪种类型的(type虽然定义成为了int类型,但实际上存的是cbdata_type这个枚举型的数据)data是实际存放数据的地方。

另外,squid为了管理cbdata,还创建了两个全局变量cbdata_indexcbdata_types

struct

{

        MemPool *pool;

        FREE *free_func;

}     *cbdata_index = NULL;

int cbdata_types = 0;

其中cbdata_index是存放每一种cbdataMemPool和释放函数的一个动态开辟空间的数组,通过cbdata_type枚举型的值作为下标来访问。每增加一种类型的cbdata(程序中可以随时增加cbdata),这个数组就realloc一次。ctdata_typescbdata_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_FREECBCBDATA_INIT_TYPE非常类似,只是多传入了一个free_func

CREATE_CBDATA CREATE_CBDATA_FREE 则是分别对应于CBDATA_INIT_TYPECBDATA_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时所用的sizesize + OFFSET_OF(cbdata, data),这样分配出来的内存除了包括type的大小,还包括了cbdatavalidlockstype等其他成员的大小。

一个有意思的细节,传给MemPoollabel值为: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的指针指向了一片较大的内存,cbdatadata成员指向了程序中真正使用的内存的首地址。data成员实际上只是一个内存地址标识的作用,并没有被真正当做union来使用!(这一段欢迎高手批判)

另外,还有一个细节,在cbdataAlloc中将cbdata->y 设置成为了 CBDATA_COOKIE(cbdata->data),并在后面的代码中会经常断言二者相等。这样做是为了保护cbdata的内存不会被意想不到的代码篡改。之前FC5出现过cbdata断言错的bug,就是这个机制帮助我们提早发现了代码中的错误。

 

cbdataValid

int cbdataValid(const void *p)

 

判断p是否还是一个有效的cbdata。前面提到过,valid的定义是:pNULL或者p没有被free过。这个函数通常被用来终止回调过程,例如:一个cbdata->datavalid,才继续进行回调过程,否则退出。

 

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表示“这个东西也该释放掉了,你们要是都用完了,就释放掉它吧!”,如果locks0就自己释放,否则就等待cbdataUnlock来释放。

 

2.2 cbdatasquid中的使用

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的声明类型、分配与释放等。这里换一个场景来讲解cbdataLockcbdataUnlock等方法的应用。

选取大家比较熟悉的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);

}

这个函数在循环中会拿出checklistaccess_list中的每一个acl_accessacl_listchecklist去做匹配,如果匹配上则执行回调。

在每一次循环的末尾,会调用一次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返回的还只是“一块”内存,只是这块内存的大小是向36128512“取整”了。这块内存可以被当作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中是返回了栈上的一个structcopy看起来如果结构体比较小的话,为了程序的简单性,偶尔整体copy一下也无妨。

       我们在处理字符串的时候,如果拿不准是否会有内存泄露问题的话,就应该使用Squid自身提供的内存机制。不推荐直接使用memAllocStringmem.c中的函数,而是应该使用String.c中的函数!

       String结构在嵌入其他结构体中的时候,推荐直接嵌入一个String类型的变量,而非String指针,这样就省了free String的过程,但是一定要记得调用stringClean

4. 总结

squid的内存管理机制是围绕着MemPool展开的。而MemPool仅是提供了一种与具体业务无关的,内存分配与释放的管理机制。

而建立在MemPool之上的,更加方便使用的机制有两个:一个是mem.c提供的MemPool封装,另一个就是cbdata。这两种机制都有各自的适用范围。

一般来说,程序对内存使用方法分为3类,

a)       全局变量,包括挂在全局变量下面的成员变量,例如StoreEntryfd等。

b)      局部变量,即在一个函数调用过程中声明的变量,它可能会被传递给子函数等。

c)       回调过程中用到的变量,它介于全局变量和局部变量之间,在一个函数中产生,但这个函数因为某种原因(如等待epoll等外部条件)而半途中止运行,并将自己或其他函数注册到外部条件的回调中去,而且还需要用到之前声明的变量。例如LocateVaryState这类的状态机,还包括clientHttpRequest等类型。

 

不同的变量存在着不同的问题,解决方法入表4.1所示

类型

问题

解决方法

全局变量

数量难以统计,一旦出现bug导致泄露,难以追查

使用mem.c提供的memAllocate等方法,或者直接用MemPool

局部变量

可以直接使用xmalloc,xcalloc

回调变量

数量难以统计,可能出现多处代码释放同一变量的情况

使用cbdata机制

4.1

 

       使用squid自身带来的各种机制,肯定能够获得事半功倍的效果,但是也可能会引入一些问题。我个人认为,

第一,   不能想当然,看别人用了自己也用,一定要在充分了解其原理的前提下使用!

第二,   并且不要为了使用而使用,而是要充分了解自己想要什么,以及squid机制能提供什么的前提下使用!

阅读(2132) | 评论(0) | 转发(1) |
0

上一篇:反转一句话的单词顺序

下一篇:没有了

给主人留下些什么吧!~~