免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 817 | 回复: 0
打印 上一主题 下一主题

Linux内存管理之slab分配器分析(续五) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-02-01 16:46 |只看该作者 |倒序浏览

九:几点补充:
1: Slab中使用的页面都会加上“PG_slab”标志,以跟一般的页面区别。另外,在释放内存的时候,经常需要用到从页面到slab的对应转换关系。那是怎样标识的呢?
关于标志:
注意有以下代码:
static void *kmem_getpages(kmem_cache_t *cachep, int flags, int nodeid)
{
     ……
     while (i--) {
         //为分得的每一个页面设置PG_slab标志
         SetPageSlab(page);
         page++;
     }
……
}
关于从页面到slab的转换:
  向伙伴系统请求内存
static int cache_grow (kmem_cache_t * cachep, int flags)
{
     ……
     //请求内存过后,设置内存属性
set_slab_attr(cachep, slabp, objp);
……
}
static void set_slab_attr(kmem_cache_t *cachep, struct slab *slabp, void *objp)
{
     int i;
     struct page *page;
     //计算页面总数
     i = 1 gfporder;
     //虚拟地址转换成相应页面
     page = virt_to_page(objp);
     do {
         // #define    SET_PAGE_CACHE(pg,x)  ((pg)->lru.next = (struct list_head *)(x))
         SET_PAGE_CACHE(page, cachep);
         #define  SET_PAGE_SLAB(pg,x)   ((pg)->lru.prev = (struct list_head *)(x))
         SET_PAGE_SLAB(page, slabp);
         page++;
     } while (--i);
}
从上面的函数可以看出,pg->lru.next指向它所在的cache pg->lru.prev指向它所在slab
在代码中,用slabp = GET_PAGE_SLAB(virt_to_page(objp))来计算对象所在的slab
GET_PAGE_SLAB定义如下:
#define  GET_PAGE_SLAB(pg)     ((struct slab *)(pg)->lru.prev)
所以,只要直接取pg的lru.prev即可。
Slab的这部份设计很高效,很巧妙
2:在上述代码中,经常用到slab_bufctl的操作,下面就来详细分析一下:
先来看下cache中的slab大小的计算。即cache的slab_size字段:
kmem_cache_t * kmem_cache_create (const char *name, size_t size, size_t align,
     unsigned long flags, void (*ctor)(void*, kmem_cache_t *, unsigned long),
     void (*dtor)(void*, kmem_cache_t *, unsigned long))
{
     ……
     slab_size = ALIGN(cachep->num*sizeof(kmem_bufctl_t) + sizeof(struct slab), align);
     ……
}
Cachep->num:slab中的object个数
从上面可以看到,slab_size已经包括了num个kmem_bufctl_t大小,也可以理解成有num个元素的kmem_bufctl_t数组。实际上kmem_bufctl_t又被定义为:unsigned short
typedef unsigned short kmem_bufctl_t;
slab_bufctl()用来计算kmem_bufctl_t数组的首地址。代码如下:
static inline kmem_bufctl_t *slab_bufctl(struct slab *slabp)
{
     return (kmem_bufctl_t *)(slabp+1);
}
我们接着看一下,kmem_bufctl_t数组如何被初始化。初始化是在新增一个slab 的时候,看下相应的代码:
static int cache_grow (kmem_cache_t * cachep, int flags)
{
     …
     cache_init_objs(cachep, slabp, ctor_flags);
….
}
在对每个对象初始化的时候:
static void cache_init_objs (kmem_cache_t * cachep,
              struct slab * slabp, unsigned long ctor_flags)
{
     int i;
     //i为页面在slab中的序号
     for (i = 0; i num; i++) {
         void* objp = slabp->s_mem+cachep->objsize*i;
……
……
         slab_bufctl(slabp) = i+1;
     }
     slab_bufctl(slabp)[i-1] = BUFCTL_END;
     slabp->free = 0;
}
初始化之后,kmem_bufctl_t数组中的值如下图所示:

从上面的分析可以看到,slab中的free字段与kmem_bufctl_t数组中的值会错开一个值。
再来看从slab中分配对象的时候:
static void* cache_alloc_refill(kmem_cache_t* cachep, int flags)
{
     while (slabp->inuse num && batchcount--) {
         ……
         //取当前free值
         ac_entry(ac)[ac->avail++] = slabp->s_mem + slabp->free*cachep->objsize;
         slabp->inuse++;
              //使free指向下一项.下一项的值即为数组中的free项
              next = slab_bufctl(slabp)[slabp->free];
              slabp->free = next;
     }
     ……

}
释放对像的时候:
static void free_block(kmem_cache_t *cachep, void **objpp, int nr_objects)
{
     ……
     slab_bufctl(slabp)[objnr] = slabp->free;
     slabp->free = objnr;
     ……

}
结合上面的初始化可以看到,有这样的一个规律:
kmem_bufctl_t数组中的slabp->free项的值就是下一个空闲对像的序号。结合这一样就能很好的理解这一部份代码了
十:kmalloc()/kfree()的实现
Kmalloc/kfree的实现其实跟上面是所讨论的是一样的,所不同的是。上面讨论的是属于专用cache,这里所讨论的普通cache.也可以这样说:专用cache是从数据结构方面来管理内存的。普通cache是以大小来管理的。
我们在前面曾讨论过,slab分配器按大32*(2^0),32*(2^1),32*(2^2) ….32*(2^12)大小,共划分了13个区域。Kmalloc()根据所申请的数据大小,选择合适的cache分配内存
代码如下示:
void * __kmalloc (size_t size, int flags)
{
     struct cache_sizes *csizep = malloc_sizes;
     //取得相应大小的普通cache
     for (; csizep->cs_size; csizep++) {
         if (size > csizep->cs_size)
              continue;
#if DEBUG
         /* This happens if someone tries to call
          * kmem_cache_create(), or kmalloc(), before
          * the generic caches are initialized.
          */
         BUG_ON(csizep->cs_cachep == NULL);
#endif
         //按请求内类型从不同的cache中分配内存
         //__cache_alloc这函数我们在上面已经详细的分析过了
         return __cache_alloc(flags & GFP_DMA ?
               csizep->cs_dmacachep : csizep->cs_cachep, flags);
     }
     return NULL;
}
Kfree的实现代码:
void kfree (const void *objp)
{
     kmem_cache_t *c;
     unsigned long flags;

     if (!objp)
         return;
     local_irq_save(flags);
     kfree_debugcheck(objp);
     //取得对象所对应的CACHE
     c = GET_PAGE_CACHE(virt_to_page(objp));
     //从cache中释放object
     //__cache_free这函数我们在前面已经分析过了
     __cache_free(c, (void*)objp);
     local_irq_restore(flags);
}
十一:总结:
Slab分配器中有很多值得仔细研读的代码。与2.4相比,2。6内核新增了两个缓冲结构,一个是AC,另一个是share。有效的缓减了对伙伴系统的压力


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/51562/showart_474694.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP