1. 程式人生 > >Linux中的記憶體分配和釋放之slab分配器分析(完)

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分配器算是分析到這裡了。 

相關推薦

Linux記憶體分配釋放slab分配器分析

        我們在上篇文章分析cache_grow()函式的時候涉及兩個函式,我們沒有細說。一個就是kmem_getpages()和kmem_freepages()函式,這兩個函式有3個引數。kmem_cahce_t:主要是把申請到的物件加到這個快取記憶體內   flag

Linux夥伴系統原理-記憶體分配釋放

主要分析Linux夥伴系統演算法,記憶體的分配和釋放 1.夥伴系統簡介      Linux核心記憶體管理的一項重要工作就是如何在頻繁申請釋放記憶體的情況下,避免碎片的產生, Linux採用夥伴系統解決外部碎片的問題,採用slab解決內 部碎片的問

Linux 記憶體 buffer cache 的區別

細心的朋友會注意到,當你在Linux下頻繁存取檔案後,實體記憶體會很快被用光,當程式結束後,記憶體不會被正常釋放,而是一直作為caching.這個問題,貌似有不少人在問,不過都沒有看到有什麼很好解決的辦法.那麼我來談談這個問題。 先來說說free命令 其中: total

Lua記憶體管理釋放的理解

Lua記憶體是自動收集的, 這點跟Java類似, 不被任何物件或全域性變數引用的資料,將被首先標記為回收,不需要開發者做任何事情.但是,正如Java也會有記憶體洩露一樣, Lua也會有, 只不過,跟C++的不同,它是由於程式碼執行所裝載的資源,並沒有被徹底銷燬而導致,其中,最臭名昭著的就是不

c記憶體分配釋放malloc,realloc,calloc,free函式內容的整理

程式例2    從這個例子可以看出calloc分配完儲存空間後將元素初始化。   #include<stdio.h>   #include<stdlib.h>   int main(void)   {   int i;   int *pn=(int

Java直接記憶體分配釋放方式

一. 正常分配,回收由GC負責 新增jvm啟動引數:-verbose:gc -XX:+PrintGCDetails -XX:MaxDirectMemorySize=40M 迴圈執行以下程式碼,可以看到頻繁fullGC. ByteBuffer buffer =

Linux記憶體buffercache的區別

cache是快取記憶體,用於CPU和記憶體之間的緩衝; buffer是I/O快取,用於記憶體和硬碟的緩衝 cache最初用於cpu cache, 主要原因是cpu 與memory, 由於cpu快,memory跟不上,且有些值使用次數多,所以放入 cache中,主要目的是,重複使用, 並且一級\二級物理cach

delphi 指標的記憶體分配釋放

給字元指標(PChar、PWideChar、PAnsiChar)分配記憶體, 最佳選擇是: StrAlloc.StrAlloc 雖然最終也是呼叫了 GetMem, 但 StrAlloc 會在指標前面新增 Delphi 需要的 4 個管理位元組(記錄長度).StrAlloc

jsp的basePathpath (絕對路徑 相對路徑)重要

在JSP中的如果使用 “相對路徑” 則有可能會出現問題. 因為網頁中的 “相對路徑” , 他是相對於 “URL請求的地址” 去尋找資源.  上面這句話是什麼意思呢 ? 舉個例子: 假如我們有一個專案: MyApp 在該專案下, 有一個jsp資料夾 該資料夾下

linux驅動篇 driver_register 過程分析

linux驅動註冊過程分析--driver_register(一) 個人筆記,歡迎轉載,請註明出處,共同分享 共同進步  http://blog.csdn.net/richard_liujh/article/details/45825333 kernel版本3.10.1

Linux 4 個簡單的找出程序 IDPID的方法 | Linux 中國

每個人都知道 PID,究竟什麼是 PID?為什麼你想要 PID?你打算用 PID 做什麼?你腦子

mybatis原始碼學習執行過程分析2——config.xml配置檔案mapper.xml對映檔案解析過程

在上一篇中跟蹤了SqlSessionFactory及SqlSession的建立過程。這一篇,主要跟蹤Mapper介面和XML檔案對映及獲取。 1.xml檔案的解析 1.1Mybatis-config.xml的解析 在SqlSessionFactor

PHP類的繼承特性方法的過載覆蓋

<?php/*類的繼承特性之過載(也叫覆蓋): 1.說的意思指的是子類可以重新定義一個和父類相同名字的方法,從而覆蓋掉原來父類的方法,加上自己新新增的功能; 2.若原封不動的覆蓋容易造成程式碼的

C#的委託事件(提及Observer設計模式)轉載

原連結:http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-in-CSharp.aspx 引言 委託 和 事件在 .Net Framework中的應用非常廣泛,然而,較好地理解委託和事件對很多接

C++malloc/freenew/delete的區別---補充15《Effective C++》

1、C++中既然有了new/delete為什麼還有還存在malloc/free呢? 1)malloc/free作為C/C++語言中的庫函式,而new/delete是C++中的運算子而已,因此C++編譯器可以強制使new/delete運算子進行建構函式和解構函式

資料結構與演算法篇 複雜度分析

一直以來都想把資料結構和演算法學好,可是老是學到一半就放棄了,哈哈這次買了一個課程王爭老師學習 這次要用部落格把這個過程記錄下來 第一步先來普及一下資料結構的概念。。。。。。 資料結構是計算機儲存、組織資料的方式。資料結構是指相互之間存在一種或多種特定關係的資料元素的集

WiresharkFTP協議分析

最近專案需求,需要抓取並還原網路中通過ftp傳輸的檔案。故對ftp協議進行了簡單學習,總結如下。 1. ftp協議概述 這部分內容我參考的百度文庫的一篇文件: 裡面講的很詳細。在此對重點的部分進行總結一下。 1)ftp服務端的用到兩個埠20和21。 2)FTP使

以太坊原始碼分析---go-ethereump2p通訊分析1

這篇文章寫的非常好。裡面對以太坊原始碼的分析也非常到位,在程式碼框架上,表達非常清晰。 那麼為何我還要寫這篇原始碼分析呢,在分析原始碼的同時,也有看這篇文章,但這篇文章主要是在程式碼框架上,程式碼的細節方面,還有待補充。這就是我寫部落格的初衷。 那麼正文開始

以太坊原始碼分析---go-ethereump2p通訊分析2

本文QQ空間連結:http://user.qzone.qq.com/29185807/blog/1519899372 上一篇分析了p2p模組的初始化與start。繼續上一篇分析。 先回顧下p2p的初始化 github.com/ethereum/go-ethereu