天天看點

Linux中的記憶體配置設定和釋放之slab配置設定器分析(完)

        我們在上篇文章分析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配置設定器算是分析到這裡了。 

繼續閱讀