Linux内存管理之slab分配器分析(续三)

六:kmem_cache_alloc 的实现分析:

我们在上面可以看到,创建一个cache 描述符的时候,并没有这之分配slab 数据。现在我们来看一下怎么从cache 中申请对象

void * kmem_cache_alloc (kmem_cache_t *cachep, int flags)

{

     return __cache_alloc(cachep, flags);

}

实际上会调用__cache_alloc

如下:

static inline void * __cache_alloc (kmem_cache_t *cachep, int flags)

{

     unsigned long save_flags;

     void* objp;

     struct array_cache *ac;

     // 如果定义了__GFP_WAIT 。可能会引起睡眠

     cache_alloc_debugcheck_before(cachep, flags);

 

     local_irq_save(save_flags);

     // 取得当前处理器所在的array_cache( 简称为AC ,我们下面也这样称呼它)

     ac = ac_data(cachep);

     //ac->avail:AC 中第后一个可用的对象索引

    

// 如果AC 中还有可用的对象

//在 kmem_cache_create时,ac->avail被赋值为0,所以第一次分配时,会重新填充。

if (likely(ac->avail)) {

         STATS_INC_ALLOCHIT(cachep);

         // 每次分配都会把ac->touched 置为1

         ac->touched = 1;

         objp = ac_entry(ac)[--ac->avail];

     } else {

         // 如果AC 中没有可用对象,那只能从l3 中“搬出”对象到AC

         STATS_INC_ALLOCMISS(cachep);

         objp = cache_alloc_refill(cachep, flags);

     }

     local_irq_restore(save_flags);

     objp = cache_alloc_debugcheck_after(cachep, flags, objp, __builtin_return_address(0));

     return objp;

}

首先,会从AC 中分配对象,如果AC 中无可用对象,那就从l3 链表中分配对象了,首先它会从share 链表中取对象,然后再从末满,空链表中取对象,如果都没有空闲对象的话,只能从伙伴系统中分配内存了. 接着看下面的代码:

static void* cache_alloc_refill(kmem_cache_t* cachep, int flags)

{

     int batchcount;

     struct kmem_list3 *l3;

     struct array_cache *ac;

 

     check_irq_off();

     ac = ac_data(cachep);

retry:

     //batchcount: 一次向AC 填充的对象值

     batchcount = ac->batchcount;

     if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {

         batchcount = BATCHREFILL_LIMIT;

     }

     // 取得cache 所对象的l3

     l3 = list3_data(cachep);

     // 如果Ac 中依然有可用对象,则退出

     BUG_ON(ac->avail > 0);

     spin_lock(&cachep->spinlock);

     // 首先会从shared 中取对象

     if (l3->shared) {

         struct array_cache *shared_array = l3->shared;

         if (shared_array->avail) {

              如果share 的剩余量不足batchcount 。则把它全部都移至AC

              if (batchcount > shared_array->avail)

                   batchcount = shared_array->avail;

              shared_array->avail -= batchcount;

              ac->avail = batchcount;

              // share 链中的object 拷贝到AC

              memcpy(ac_entry(ac), &ac_entry(shared_array)[shared_array->avail],

                       sizeof(void*)*batchcount);

              shared_array->touched = 1;

              //AC 中已经有数据了,那么,可以直接从AC 中分配了

              goto alloc_done;

         }

     }

     // 运行到这里的话,那说明share 链中没有对象了

     while (batchcount > 0) {

         // 先从末满的链表中获取,若末满链为空的话,从全空链表中获取

         struct list_head *entry;

         struct slab *slabp;

         /* Get slab alloc is to come from. */

         entry = l3->slabs_partial.next;

         // 判断slabs_partial 是否为空

         if (entry == &l3->slabs_partial) {

              l3->free_touched = 1;

              // 判断slabs_free 链是否为空

              entry = l3->slabs_free.next;

              if (entry == &l3->slabs_free)

                   // 若全为空的话,就从伙伴系统中分配页面了

                   goto must_grow;

         }

         // 从链表中取得slab 描述符  

         slabp = list_entry(entry, struct slab, list);

         check_slabp(cachep, slabp);

         check_spinlock_acquired(cachep);

         // 对象取尽,或者已经满尽分配要求

         // slabp->inuse: slab 中的使用对象个数

         // cachep->num :每个slab中对象的数目

         while (slabp->inuse < cachep->num && batchcount--) {

              // 从相应的slab 中分配对象

              kmem_bufctl_t next;

              STATS_INC_ALLOCED(cachep);

              STATS_INC_ACTIVE(cachep);

              STATS_SET_HIGH(cachep);

 

              // 得到空闲对象指针

              ac_entry(ac)[ac->avail++] = slabp->s_mem + slabp->free*cachep->objsize;

              // 更新计数

              slabp->inuse++;

              next = slab_bufctl(slabp)[slabp->free];

#if DEBUG

              slab_bufctl(slabp)[slabp->free] = BUFCTL_FREE;

#endif

                   // 使free 指向下一人空闲对像的索引

                  slabp->free = next;

         }

         check_slabp(cachep, slabp);

 

         /* move slabp to correct slabp list: */

         //slab 从链中脱落

         list_del(&slabp->list);

         if (slabp->free == BUFCTL_END)

              // 如果slab 中没有空闲对象了,则把它加入slabs_full

              list_add(&slabp->list, &l3->slabs_full);

         else

              // 如果slab 中没有空闲对象了,则把它加入slabs_partial

              list_add(&slabp->list, &l3->slabs_partial);

     }

 

must_grow:

     // 更新free_objects 计数.( 如果三链都为空的情况下:ac->avail 为进入函数的初始值,即为0)

     l3->free_objects -= ac->avail;

alloc_done:

     spin_unlock(&cachep->spinlock);

 

     if (unlikely(!ac->avail)) {

         int x;

         x = cache_grow(cachep, flags);

        

         // cache_grow can reenable interrupts, then ac could change.

         ac = ac_data(cachep);

         // 如果grow 失败,返回NULL

         if (!x && ac->avail == 0)   // no objects in sight? abort

              return NULL;

         // 如果grow 成功,则重复上述操作,即从三链表中取空闲对象^_^

         if (!ac->avail)        // objects refilled by interrupt?

              goto retry;

     }

     ac->touched = 1;

     return ac_entry(ac)[--ac->avail];

}

这段代码涉及到slab_bufctl (),等我们看完分配,释放的全过程后。再来详细分析它涉及到的各项操作,cache_grow() 用来做slab 分配器与slab 的交互。它的代码如下示:

static int cache_grow (kmem_cache_t * cachep, int flags)

{

     struct slab   *slabp;

     void     *objp;

     size_t        offset;

     int      local_flags;

     unsigned long ctor_flags;

     if (flags & ~(SLAB_DMA|SLAB_LEVEL_MASK|SLAB_NO_GROW))

         BUG();

     if (flags & SLAB_NO_GROW)

         return 0;

 

     ctor_flags = SLAB_CTOR_CONSTRUCTOR;

     local_flags = (flags & SLAB_LEVEL_MASK);

     if (!(local_flags & __GFP_WAIT))

         /*

          * Not allowed to sleep.  Need to tell a constructor about

          * this - it might need to know...

          */

         ctor_flags |= SLAB_CTOR_ATOMIC;

 

     /* About to mess with non-constant members - lock. */

     check_irq_off();

     spin_lock(&cachep->spinlock);

     // 取得下一个偏移索引(着色机制在前面已经详细分析了)

     offset = cachep->colour_next;

     cachep->colour_next++;

     // 如果大于允许的最大颜色,那就把计数归位,即为0

     if (cachep->colour_next >= cachep->colour)

         cachep->colour_next = 0;

     // 计算偏移量

     offset *= cachep->colour_off;

 

     spin_unlock(&cachep->spinlock);

 

     if (local_flags & __GFP_WAIT)

         local_irq_enable();

     kmem_flagcheck(cachep, flags);

     // 向伙伴系统申请内存

     if (!(objp = kmem_getpages(cachep, flags, -1)))

         goto failed;

     // 分配slab 描述符,这里有两种情况,一种是slab 在缓存外部,另一种是内部

     if (!(slabp = alloc_slabmgmt(cachep, objp, offset, local_flags)))

         goto opps1;

 

     set_slab_attr(cachep, slabp, objp);

     // 初始化slab 的对像

     cache_init_objs(cachep, slabp, ctor_flags);

 

     if (local_flags & __GFP_WAIT)

         local_irq_disable();

     check_irq_off();

     spin_lock(&cachep->spinlock);

 

     // 将新构建的slab 加至slabs_free

     list_add_tail(&slabp->list, &(list3_data(cachep)->slabs_free));

     STATS_INC_GROWN(cachep);

     // 更新计数

     list3_data(cachep)->free_objects += cachep->num;

     spin_unlock(&cachep->spinlock);

     return 1;

opps1:

     // 发生了错误,把内存归还伙伴系统

     kmem_freepages(cachep, objp);

failed:

     if (local_flags & __GFP_WAIT)

         local_irq_disable();

     return 0;

}

我们看到了cache_grow 的概貌,接着分析它里面调用的子函数。

kmem_getpages ()用于slab 分配器向伙伴系统分配内存,代码如下:

 

//nodeid: 分配内面的cpu 结点。如果从当前CPU 分存,nodeid 置为-1

static void *kmem_getpages(kmem_cache_t *cachep, int flags, int nodeid)

{

     struct page *page;

     void *addr;

     int i;

 

     flags |= cachep->gfpflags;

//__get_free_pages alloc_pages_node 在《linux 内存管理之伙伴系统分析》一文中已有详

// 细分析,请参考

     if (likely(nodeid == -1)) {

         // 从当前cpu 结点分配内存

         addr = (void*)__get_free_pages(flags, cachep->gfporder);

         if (!addr)

              return NULL;

         // 将地址转换为页描述符

         page = virt_to_page(addr);

     } else {

         // 从指定结点分配内存

         page = alloc_pages_node(nodeid, flags, cachep->gfporder);

         if (!page)

              return NULL;

         addr = page_address(page);

     }

 

     // 计算页面个数。即为2^ cachep->gfporder

     i = (1 << cachep->gfporder);

     if (cachep->flags & SLAB_RECLAIM_ACCOUNT)

         atomic_add(i, &slab_reclaim_pages);

     // 更新cpu nr_slab 状态计数

     add_page_state(nr_slab, i);

     while (i--) {

         // 将页面标识为PG_slab, 表示该页面已被slab 使用

         SetPageSlab(page);

         page++;

     }

     return addr;

}

alloc_slabmgmt() 是一个slab 描述符分配器接口,代码如下:

static struct slab* alloc_slabmgmt (kmem_cache_t *cachep,

              void *objp, int colour_off, int local_flags)

{

     struct slab *slabp;

     // 如果slab 描述符是外置的

     if (OFF_SLAB(cachep)) {

         // 从对应的cache 中分配slab 描述符

         slabp = kmem_cache_alloc(cachep->slabp_cache, local_flags);

         if (!slabp)

              return NULL;

     } else {

         // 从偏移量后开始安置slab

         slabp = objp+colour_off;

         // 更新偏移量,即加上slab 的大小,这位置也是有效数据的起始偏移位置

         colour_off += cachep->slab_size;

     }

     slabp->inuse = 0;

     slabp->colouroff = colour_off;

     slabp->s_mem = objp+colour_off;

 

     return slabp;

}

cache_init_objs ()初始化分配得到的每一个对象,代码如下:

static void cache_init_objs (kmem_cache_t * cachep,

              struct slab * slabp, unsigned long ctor_flags)

{

     int i;

 

     for (i = 0; i < cachep->num; i++) {

         // slab 中的每一个对像

         void* objp = slabp->s_mem+cachep->objsize*i;

#if DEBUG

         // 忽略掉debug 信息

         /* need to poison the objs? */

         if (cachep->flags & SLAB_POISON)

              poison_obj(cachep, objp, POISON_FREE);

         if (cachep->flags & SLAB_STORE_USER)

              *dbg_userword(cachep, objp) = NULL;

 

         if (cachep->flags & SLAB_RED_ZONE) {

              *dbg_redzone1(cachep, objp) = RED_INACTIVE;

              *dbg_redzone2(cachep, objp) = RED_INACTIVE;

         }

         /*

          * Constructors are not allowed to allocate memory from

          * the same cache which they are a constructor for.

          * Otherwise, deadlock. They must also be threaded.

          */

         if (cachep->ctor && !(cachep->flags & SLAB_POISON))

              cachep->ctor(objp+obj_dbghead(cachep), cachep, ctor_flags);

 

         if (cachep->flags & SLAB_RED_ZONE) {

              if (*dbg_redzone2(cachep, objp) != RED_INACTIVE)

                   slab_error(cachep, "constructor overwrote the"

                                 " end of an object");

              if (*dbg_redzone1(cachep, objp) != RED_INACTIVE)

                   slab_error(cachep, "constructor overwrote the"

                                 " start of an object");

         }

         if ((cachep->objsize % PAGE_SIZE) == 0 && OFF_SLAB(cachep) && cachep->flags & SLAB_POISON)

                   kernel_map_pages(virt_to_page(objp), cachep->objsize/PAGE_SIZE, 0);

#else

         // 如果有初始化函数,则调用之

         if (cachep->ctor)

              cachep->ctor(objp, cachep, ctor_flags);

#endif

         // 更新bufctl 数组

         slab_bufctl(slabp)[i] = i+1;

     }

     // 置末尾描述符

     slab_bufctl(slabp)[i-1] = BUFCTL_END;

     slabp->free = 0;

}

同样,slab_bufctl 的分析,等讲完释放对像的时候再继续

到此,我们已经看完到分配对象的全过程,接着来看怎么释放一个对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值