最惨的是不知道自己错在哪,list是个二级指针

2019-10-05 12:11 来源:未知

haproxy的内存管理中,通过pool_head->free_list,存储空闲内存块,free_list是个二级指针,却把空闲内存块都串了起来,没有用next,pre之类的指针。怎么实现的?着实思考了半个小时才明白。
pool_head结构:

} mempool_t;

21     //而现在,这里的判断就会是false了
struct pool_head {
    void **free_list;   /* 空闲链表 */
    struct list list;   /* 双向链表,链接每种类型的内存池 */
    unsigned int used;  /* 使用了多少内存块 */
    unsigned int allocated; /* 分配了多少内存块 */
    unsigned int limit; /* 内存块上限 */
    unsigned int minavail;  /* 最少保留几个,回收时不会全部回收 */
    unsigned int size;  /* 内存块大小 */
    unsigned int flags; /* 能否共享,类型不同,但大小相同的,能否共享一个pool_head */
    unsigned int users; /* 内存池有几个使用者 */
    char name[12];      /* 内存池名称 */
};

              }

08         if (NULL == (p = (char*)malloc(ra))) {

free_list操作

#define pool_alloc2(pool)                                     
({                                                            
        void *__p;                                            
        if ((__p = pool->free_list) == NULL)                  
                __p = pool_refill_alloc(pool);                
        else {                                                
                pool->free_list = *(void **)pool->free_list;  
                pool->used++;                                 
        }                                                     
        __p;                                                  
})

当free_list为NULL时,调用pool_refill_alloc申请内存,看到这里的时候有点懵逼,这样的话,一直申请内存,pool->free_list还是一直是NULL,就算不是NULL,pool->free_list = *(void **)pool->free_list又是什么鬼?
后面看内存回收才明了。

#define pool_free2(pool, ptr)                           
({                                                      
        *(void **)ptr = (void *)pool->free_list;        
        pool->free_list = (void *)ptr;                  
        pool->used--;                                   
        pool_gc2_ifneed(pool);                          
})

下面这句,往*ptr,即申请的内存块(取名为buff0)中写入pool->free_list的值,pool->free_list是个二级指针,所以内存块的前32位就写入了一个地址,这个地址可能是NULL,也可能指向下一个内存块。

*(void **)ptr = (void *)pool->free_list;

然后,

 pool->free_list = (void *)ptr;

pool->free_list等于了ptr,所以,现在pool->free_list指向了buff0。
所以,在申请内存中,

pool->free_list = *(void **)pool->free_list;

对pool->free_list进行*操作,因其是二级指针,所以取到的是第一块buffer的前32字节,而前32字节存的是第二块buffer的地址,所以free_list变成指向第二块buffer,嗯,第一块已经分配出去了,在if的判断语句里有

 if ((__p = pool->free_list) == NULL) 

所以此时__p指向了第一块内存。因为p是一级指针,所以在使用*p的时候,会取到整个内存块。

       /*这里存在一些不明白的地方,先将用户传递进来的gfp掩码标志去掉__GFP_WAIT 和 __GFP_IO 两个标志,试图调用用户自定义分配函数从缓存区申请一个内存对象,而不是首先从内存池从分配,如果申请不到,再从内存池中分配。*/

05     value = malloc(0);

可知,free_list是个二级指针,二级指针是指向指针的指针,对二级指针进行*操作,会得到一级指针指向的地址。

slab算法思路中最基本的一点被称为object-caching,即对象缓存。其核心做法就是保留对象初始化状态的不变部分,这样对象就用不着在每次使用时重新初始化(构造)及破坏(析构)。

08     p = strdup("hello_world");

总结

  1. 对二级指针进行*操作,会取到32位地址
  2. buffer的前32位是地址
  3. 同一个地址,都进行*操作,会因类型不同而取到不同值,这就是《CSAPP》说的,信息是位+上下文。
  4. 羡慕指针玩得6的人。

                                        NULL, NULL);

13         }

过程图解

永利平台娱乐 1

mempool_t * mempool_create(int min_nr, mempool_alloc_t *alloc_fn,

20     //感谢@ColoredCotton的贡献

       if (likely(element != NULL))

09 yydebug:[./mem-test.c]:[34]:value len [135159]

              kfree(pool);

18 }

rpc_buffer_mempool = mempool_create(RPC_BUFFER_POOLSIZE,

15     printf("  &p = [%p], p's addressn", &p);

       pool->free = free_fn;

我想,根据打印信息来看,没什么需要解释的了。顺便还弄透彻了指针以及函数传参。

                     free_pool(pool);

(6)为什么经常有人说free(p);要和p = NULL;一起用,以避免“野指针”的出现?
答案:其实用上面那个例子(5)就能看出,如果free超过1次就会出错,但是从例子(3)和例子(4)可以看出,free(NULL);可以执行任意次 都不会出错。所以一般free(p);之后,马上把p指向NULL;,从而即使别人再去执行free(p);也不会出现错误。不仅如此,通过让p指向 NULL,也很好的给别人一个提示,你是否对p进行了成功的操作,让别人好判断。不妨看看如下的例子:

       return pool;

答案:这不是真的。不信?你用这些代码测试一下就知道了:

完全块:没有空闲对象。

01 void foo(char **in)//调用方式应该是传入某个指针的地址

 * @alloc_fn:       用户自定义内存分配函数

10         }

              add_element(pool, element);

22     if (NULL != p) { //这里的判断就有意义了

rpc_buffer_slabp = kmem_cache_create("rpc_buffers",

2 value addr [0x87aa5c8]

       void *element;

07 yydebug:[./mem-test.c]:[34]:value len [135157]

       /*为内存池对象分配内存*/

1 char *p = NULL;

四.内存池的使用

看看打印吧:

       pool->min_nr = min_nr;

5 p0 addr [0x97eb040]

       spinlock_t lock;

(4)假设有

 

11             return -1;

       int min_nr;             /* elements数组中的成员数量 */

09         }

 * mempool_create – 创建一个内存池对象

16     }

       if (!pool)

12         }

       mempool_t *pool;

06         value = (char*)malloc(0);

repeat_alloc:

03     char *p = NULL;

       mempool_alloc_t *alloc; /* 用户在创建一个内存池对象时提供的内存分配函数,这个函数可以用户自行编写(因为对于某个内存对象如何获取内存,其开发者完全可以自行控制),也可以采用内存池提供的分配函数 */

09     return 0;

       mb();

10         value++;

 * @free_fn:       用户自定义内存释放函数

15         free(p0);

       /*首先为内存池预先分配min_nr个element对象,这些对象就是为了存储相应类型的内存对象的。数据结构形入:

4 }

                                       rpc_buffer_slabp);

void free(void *ptr);
释 放ptr指向的内存空间,ptr必须是之前调用过malloc,calloc,realloc这三个函数返回的,否则,如果free(ptr)已经执行过 了,而又执行一次,那么会导致意外发生(undefined behavior occurs.),如果ptr指向的是NULL,则不会做任何操作。

              return NULL;

10 Segmentation fault (core dumped)

*/

09 **in = [h]

              if (unlikely(!element)) {

打印如下所示:

mempool_create函数就是内存池创建函数,负责为一类内存对象构造一个内存池,传递的参数包括,内存池大小,定制的内存分配函数,定制的内存析构函数,这个对象的缓存区指针。下面是mempool_create函数的具体实现:

11 [michael@localhost mem-test]$

 

11 [michael@localhost mem-test]$

l         slab块:slab块是内核内存分配与页面级分配的接口。每个slab块的大小都是页面大小的整数倍,有若干个对象组成。slab块共分为三类:

08             printf("p addr [%p], ra = [%d]n", p, ra);

       pool->pool_data = pool_data;

06  &in = [0xbf940130], in's address

Linux将物理内存也划分成固定大小的页面,由数据结构page管理,有多少页面就有多少page结构,它们又作为元素组成一个数组mem_map[]。

05 int main(int argc, char **argv)

 

这段代码结果如下所示:Fedora14:

三.内核缓存区和内存池的初始化

14     }

              element = pool->alloc(GFP_KERNEL, pool->pool_data);

06 {

/**

30 }

调用kmem_cache_create函数从系统缓存区cache_cache中获取长度为RPC_BUFFER_MAXSIZE的缓存区大小的内存,作为rpc_buffer使用的缓存区。而以后对rpc操作的所有数据结构内存都是从这块缓存区申请,这是linux的slab技术的要点,而内存池也是基于这段缓存区进行的操作。

那么稍后p需要用free(p)来释放,以避免内存泄漏吗?
答:不妨用这段代码来测试:

上面提到,内存池的使用是与特定类型的内存对象缓存区相关联的。例如,在系统rpc服务中,系统初始化时,会为rpc_buffers预先分配缓存区,调用如下语句:

02 {

       mempool_free_t *free;   /* 内存释放函数,其它同上 */

04    p = [0x9964008], p's value

                     return NULL;

3 value addr [0x87aa5d8]

                                       mempool_alloc_slab,

11         printf("value len [%d]n", strlen(ori));

       spin_lock_init(&pool->lock);

3 free(p);

面向对象的slab分配中有如下几个术语:

09     printf("p = [%s], len = [%d]n", p, strlen(p));

       unsigned long flags;

那么会导致进程退出吗?
答案:不会,free(NULL)相当于啥事儿不干。

       element = pool->alloc(gfp_nowait|__GFP_NOWARN, pool->pool_data);

2 while(1) {

 

11 {

       if (!pool->elements) {

(8)malloc(0)返回的真的入man手册所说:要么是NULL,要么是一个unique的pointer?
答案:不妨看下这段代码:

Linux采用“按需调页”算法,支持三层页式存储管理策略。将每个用户进程4GB长度的虚拟内存划分成固定大小的页面。其中0至3GB是用户态空间,由各进程独占;3GB到4GB是内核态空间,由所有进程共享,但只有内核态进程才能访问。

4 p addr [0x8bd8038], ra = [46]

在申请新的对象空间时,如果缓冲区中存在部分块,那么首先查看部分块寻找空闲对象空间,若未成功再查看空闲块,如果还未成功则为这个对象分配一块新的slab块。

16         p = NULL; //他习惯很好,free之后指向NULL

                                       mempool_free_slab,

从打印看来,虽然是调用malloc(0);,但是每次指向的地址都不同,并且逐渐增大,偏移是0x10,也就是16个字节。

如果需要使用已经创建的内存池,则需要调用mempool_alloc从内存池中申请内存以及调用mempool_free将用完的内存还给内存池。

04     char *p0 = NULL;

 

07         printf("value addr [%p]n", value);

       DEFINE_WAIT(wait);

07         ra = rand()%100+1;

       void **elements;    /* 用来存放内存成员的二维数组,其长度为min_nr,宽度是上述各个内存对象的长度,因为对于不同的对象类型,我们会创建相应的内存池对象,所以每个内存池对象实例的element宽度都是跟其内存对象相关的 */

17     return 0;

       int gfp_nowait = gfp_mask & ~(__GFP_WAIT | __GFP_永利平台娱乐,IO);

3 p addr [0x8bd8008], ra = [41]

       }

10         else {

       /*根据内存池的最小长度为elements数组分配内存*/

4 p0 addr [0x97eb040]

l         对象:将被申请的空间视为对象,使用构造函数初始化对象,然后由用户使用对象。

07     free(*in); //*in是实参的指针变量p的指向的被分配的内存

一.Linux系统内核内存管理简介

13 }
TAG标签:
版权声明:本文由永利平台娱乐发布于新闻动态,转载请注明出处:最惨的是不知道自己错在哪,list是个二级指针