Linux中的内存分配和释放之slab分配器分析(完)

来源:互联网 发布:淘宝上搜到闲鱼 编辑:程序博客网 时间:2024/04/29 22:26

        我们在上篇文章分析cache_grow()函数的时候涉及两个函数,我们没有细说。一个就是kmem_getpages()和kmem_freepages()函数,这两个函数有3个参数。kmem_cahce_t:主要是把申请到的对象加到这个高速缓存内

  flags:表示你申请时候的一些控制标志。nodeid:主要是要求在那个内存节点号申请内存。我们先来说下kmem_getpages()函数吧。

static void *kmem_getpages(kmem_cache_t *cachep, int flags, int nodeid)
{
       struct page *page;
       void *addr;
       int i;

       flags |= cachep->gfpflags;//设置flags,如果此高速缓存内存位于dma时,gfpflags就不为0.反而是在标志的某一位设置为1.
       if (likely(nodeid == -1)) {//如果nodeid为-1,说明没有指定在哪个节点号申请内存。
                 addr = (void*)__get_free_pages(flags, cachep->gfporder);//申请2的gfporder页的内存空间,函数返回是内存空间起始的线性地址。
                 if (!addr)
                    return NULL;
                page = virt_to_page(addr);//由线性地址找到对应的struct page结构体。
      } else {//如果指定了内存节点号。
        page = alloc_pages_node(nodeid, flags, cachep->gfporder);//在指定的内存节点号申请内存空间。
        if (!page)
           return NULL;
       addr = page_address(page);//把返回的struct page结构体转换为对应的线性地址。
     }

     i = (1 << cachep->gfporder);//这里是统计申请到的页数。
     if (cachep->flags & SLAB_RECLAIM_ACCOUNT)
        atomic_add(i, &slab_reclaim_pages);//如果设置了可跟踪slab回收标志,我们将slab_reclaim_pages加上i。这里主要表示我们有多少页可以被回收的。
     add_page_state(nr_slab, i);//将当前处理器的页状态的slab计数器加i。这里主要是统计内存页的状态,主要是为了表明我们的系统内有多少内存页已经交给了slab管理了。
     while (i--) {
               SetPageSlab(page);//将每页对应的struct page结构体的flags成员设置属于slab管理的标志位置1.
               page++;
     }
     return addr;
}
下面我们来说说它的相对函数kmem_freepages()函数。

static void kmem_freepages(kmem_cache_t *cachep, void *addr)
{
   unsigned long i = (1<<cachep->gfporder);
   struct page *page = virt_to_page(addr);
   const unsigned long nr_freed = i;

   while (i--) {
      if (!TestClearPageSlab(page))
         BUG();//由于这里是要释放申请用做slab管理的页,所以他们对应的struct page结构体的flags标志的PG_slab标志位必须被置1了,发现不为1时则系统就崩溃了。如果为1时,我们还要把这位清0.
     page++;
  }
  sub_page_state(nr_slab, nr_freed);//这里和上面相反,把当前处理器内存页状态的slab计数器减去nr_freed,表明系统现有的由slab管理的内存页数量。
  if (current->reclaim_state)
      current->reclaim_state->reclaimed_slab += nr_freed;
  free_pages((unsigned long)addr, cachep->gfporder);//这里就是释放从addr开始的,内存页数为2的gfporder次方的内存空间。
  if (cachep->flags & SLAB_RECLAIM_ACCOUNT)
      atomic_sub(1<<cachep->gfporder, &slab_reclaim_pages);//这里又和上面相反,更新slab回收页数。

  好了,现在我们把真个slab的kmem_cache_alloc()分析了一通,知道我们高速缓存内存是怎么去一级一级得去分配内存对象的,我们现在开始就讲解slab分割器是如何释放对象的,在讲代之前,我先说下它的大体过程。刚开始我们先看看当前处理器的预留高速缓存阵列里面有没空位给释放后的对象存放,如果没有,发现现在空闲对象已经超过了最大限制时,我们就要查看我们的二级阵列,也就是共享缓存内存阵列。如果有,我们先通过批处理的batchcount变量来规定这里释放到二级内存的对象数量,往往我们是把一级缓存的最前面开始,把连续的batchcount个对象释放到二级缓存,然后接着把后面的对象往前挪,调整对象指针。如果不存在二级缓存,我们就调用free_block()从新调整该kmem_cahche_t变量的lists内的3个链表。为什么要调整,是这样的。当我们释放内存对象时,这个函数就会帮助我们找到这个对象所属的slab,然后这个slab的空闲页数将会发生变化,从而slab->list就要该挂到或者不该挂。如果slab变成了全空闲的话,就挂在slab_free链表上,如果还是原来的部分空闲,我们继续挂回原来的位置。经过上面的调整后,我们一级缓存,也就是当前处理器的预留高速缓存内存中就可以存放对象了,这是就把要释放的对象释放到这里来。好了,我们现在来看下具体的代码,让我们理解得更深。

  void kmem_cache_free (kmem_cache_t *cachep, void *objp)
{
   unsigned long flags;

   local_irq_save(flags);
   __cache_free(cachep, objp);
   local_irq_restore(flags);
}

static inline void __cache_free (kmem_cache_t *cachep, void* objp)
{
   struct array_cache *ac = ac_data(cachep);//让ac指向cachep->array_cache[smp_processor_id]这个是当期处理器的预留高速缓存阵列。ac存放的这个缓存的指针。

   check_irq_off();
   objp = cache_free_debugcheck(cachep, objp, __builtin_return_address(0));

   if (likely(ac->avail < ac->limit)) {//如果现在可用的,也就是空闲的对象不超过最大限制的话。
       STATS_INC_FREEHIT(cachep);//cachep->freehit加1.统计释放命中的变量。
       ac_entry(ac)[ac->avail++] = objp;//把这个要释放的内存对象指针存放在高速缓存阵列中。
       return;
   } else {//如果阵列已经放不下了,
      STATS_INC_FREEMISS(cachep);//释放失败变量cachep->freemiss加1.
      cache_flusharray(cachep, ac);//把阵列的最前面的部分内存对象释放到二级缓存或者是对应的slab中。
      ac_entry(ac)[ac->avail++] = objp;//然后再把该内存对象释放到阵列的最后面位置处。
 }

 

static void cache_flusharray (kmem_cache_t* cachep, struct array_cache *ac)
{
   int batchcount;

   batchcount = ac->batchcount;//获得这个阵列的批处理数量。
#if DEBUG
   BUG_ON(!batchcount || batchcount > ac->avail);//如果批处理数量比本身可用的对象大或者是为0,系统崩溃。
#endif
   check_irq_off();
   spin_lock(&cachep->spinlock);//上锁。
   if (cachep->lists.shared) {//如果二级缓存指针存在。
      struct array_cache *shared_array = cachep->lists.shared;//我们引入间接变量shared_array指向这个二级阵列。
      int max = shared_array->limit-shared_array->avail;//统计这个二级缓存还可以容纳下多少对象。
      if (max) {//如果还有空间
         if (batchcount > max)
             batchcount = max;
        memcpy(&ac_entry(shared_array)[shared_array->avail],&ac_entry(ac)[0],sizeof(void*)*batchcount);直接把一级缓存从前面开始连续batchcount个对象拷贝到二级缓存阵列的后续位置。
        shared_array->avail += batchcount;//从新更新二级缓存可用的内存对象。
        goto free_done;
      }
   }

   free_block(cachep, &ac_entry(ac)[0], batchcount);//这个函数等下会细说,主要就是完成把一级缓存的前batchcount个对象释放到对应的slab中。
free_done:
#if STATS
   {
      int i = 0;
      struct list_head *p;

      p = list3_data(cachep)->slabs_free.next;//这里是指向空闲slab链表的同个节点。
      while (p != &(list3_data(cachep)->slabs_free)) {//这里循环到这个链表为空为止。
                struct slab *slabp;

                slabp = list_entry(p, struct slab, list);//从这个list求得对应的struct slab结构体。
                BUG_ON(slabp->inuse);//如果这个可用对象个数为0的话,说明我们这个不是全部空闲的slab。系统则崩溃。

                i++;这个是统计这个slab_free全部空闲链表中包含了多少个slab。
                p = p->next;
      }
      STATS_SET_FREEABLE(cachep, i);//将cachep->max_freeable附上i(如果这里的i比以前要大的话),表示我们这个高速缓存最大试过有多少个全部空闲的slab。
   }
#endif
   spin_unlock(&cachep->spinlock);//解锁
   ac->avail -= batchcount;//跟新现在可用的对象。
   memmove(&ac_entry(ac)[0], &ac_entry(ac)[batchcount],sizeof(void*)*ac->avail);//把后面的对象向前移,向前移batchcount个单元。

}

  static void free_block(kmem_cache_t *cachep, void **objpp, int nr_objects)
{
   int i;

   check_spinlock_acquired(cachep);

 
   cachep->lists.free_objects += nr_objects;//统计现在这个高速缓存一共有多少个空闲对象。nr_objects就是上面批处理的数量batchcount。

   for (i = 0; i < nr_objects; i++) {//我们要循环批处理数量的次数。
          void *objp = objpp[i];//objpp一级缓存阵列存放内存对象指针的起始位置。
          struct slab *slabp;
          unsigned int objnr;

          slabp = GET_PAGE_SLAB(virt_to_page(objp));//由线性地址可以得到存放这个对象对应的struct page结构体。然后通过page->lur.prev可以找到对应这个page的slab。也就是说我们找到这个对象所属的slab。
          list_del(&slabp->list);//因为这个slab的空闲对象肯定会有变化的,所以我们先要从slab所属的链表删除。
         objnr = (objp - slabp->s_mem) / cachep->objsize;//这里是求得这个对象在slab中对象堆中对象号。
         check_slabp(cachep, slabp);
#if DEBUG
         if (slab_bufctl(slabp)[objnr] != BUFCTL_FREE) {
             printk(KERN_ERR "slab: double free detected in cache '%s', objp %p./n",cachep->name, objp);
             BUG();
         }
#endif
         slab_bufctl(slabp)[objnr] = slabp->free;//我们知道有与对象一一对应的kmem_bufctl_t结构的管理数据,它是存放在经跟的struct slab结构下面的,起始这些单元主要是存放下一个分配对象的索引号,可以看出这个对象会被放置在最后,对应的kmem_bufctl_t变量里存放着上次的最后对象的索引号。
         slabp->free = objnr;//这里存放着下次要分配对象的索引号,如果下次有要从这个slab分配对象的话,这个对象就会被分配出去。
         STATS_DEC_ACTIVE(cachep);//cachep->num_active是指向一级缓存的,表示里面还有几个对象还可以使用分配的。
         slabp->inuse--;//由于原来的被分配出去的对象现在释放回来了,空闲的对象多了,而使用的对象少了。
         check_slabp(cachep, slabp);
         if (slabp->inuse == 0) {//如果这个slab里面的对象全是空闲的
             if (cachep->lists.free_objects > cachep->free_limit) {//如果这个高速缓存的全部空闲对象多于高速缓存的限制话
                   cachep->lists.free_objects -= cachep->num;//我们先减去一个slab包含的对象数目。表示接下来要放弃一个slab。
                   slab_destroy(cachep, slabp);//这个函数就是讲怎么放弃一个slab得,我会在后面的函数细说的。
             } else {//如果不会超过限制值的话,我们就把它挂在全部空闲链表处。
                   list_add(&slabp->list,&list3_data_ptr(cachep, objp)->slabs_free);
             }
         } else {//如果还是部分空闲,我们就挂在slabs_particail。
             list_add_tail(&slabp->list,&list3_data_ptr(cachep, objp)->slabs_partial);
         }
    }
}

 

static void slab_destroy (kmem_cache_t *cachep, struct slab *slabp)
{
   void *addr = slabp->s_mem - slabp->colouroff;//这里是获得这个slab所有对象存放的起始地址,为什么要减去colouroff。因为这部分是色块的空间,我们也要把它一通释放掉。

#if DEBUG
   int i;
   for (i = 0; i < cachep->num; i++) {
         void *objp = slabp->s_mem + cachep->objsize * i;

         if (cachep->flags & SLAB_POISON) {
#ifdef CONFIG_DEBUG_PAGEALLOC
             if ((cachep->objsize%PAGE_SIZE)==0 && OFF_SLAB(cachep))
                 kernel_map_pages(virt_to_page(objp), cachep->objsize/PAGE_SIZE,1);
             else
                 check_poison_obj(cachep, objp);
#else
            check_poison_obj(cachep, objp);
#endif

   }
   if (cachep->flags & SLAB_RED_ZONE) {
       if (*dbg_redzone1(cachep, objp) != RED_INACTIVE)
           slab_error(cachep, "start of a freed object ""was overwritten");
       if (*dbg_redzone2(cachep, objp) != RED_INACTIVE)
           slab_error(cachep, "end of a freed object ""was overwritten");
   }
   if (cachep->dtor && !(cachep->flags & SLAB_POISON))
        (cachep->dtor)(objp+obj_dbghead(cachep), cachep, 0);
   }
#else
   if (cachep->dtor) {
       int i;
       for (i = 0; i < cachep->num; i++) {
             void* objp = slabp->s_mem+cachep->objsize*i;
             (cachep->dtor)(objp, cachep, 0);
       }
   }
#endif

   if (unlikely(cachep->flags & SLAB_DESTROY_BY_RCU)) {//前面的我不打算讲了,我们就是分析这里就可以知道slab是怎么被放弃的。如果没有设置slab的放弃不是通过rcu的话,我们就通过以下语句来完成,起始这里的核心语句和下面的else是一样的。
       struct slab_rcu *slab_rcu;

       slab_rcu = (struct slab_rcu *) slabp;
       slab_rcu->cachep = cachep;
       slab_rcu->addr = addr;
       call_rcu(&slab_rcu->head, kmem_rcu_free);
   } else {//我们通过rcu来放弃slab
      kmem_freepages(cachep, addr);//这个很熟悉吧,就是上面释放以addr地址开始的一个slab中所有对象所占页数那么大的内存空间。
      if (OFF_SLAB(cachep))//如果slab数据管理的数据空间不是和对象一起存放时,我们需要把这个slabp所在的对象释放掉,释放到专门存放slabp数据结构空间的缓存中。如果是放在一起会在上面的函数被一同释放。
          kmem_cache_free(cachep->slabp_cache, slabp);
  }
}
好了,到这里位置,我们已经把slab大致说了一遍它的释放和分配过程。slab分配器算是分析到这里了。 

原创粉丝点击