天天看點

[核心記憶體] slab配置設定器2---slab系統初始化1 slab系統初始化—kmem_cache_init&&kmem_cache_init_late2 kmem_cache執行個體中cpu本地高速緩存和 kmem_cache_node執行個體中共享高速緩存間的關系3 slab系統中特殊結構體記憶體配置設定4 slab系統通用記憶體配置設定(kmalloc)

文章目錄

  • 1 slab系統初始化---kmem_cache_init&&kmem_cache_init_late
  • 2 kmem_cache執行個體中cpu本地高速緩存和 kmem_cache_node執行個體中共享高速緩存間的關系
    • 2.1 本地cpu高速緩存(cpu_cache)和cpu共享高速緩存(shared_cache)間的互動關系
    • 2.2 shared cache引入原因
  • 3 slab系統中特殊結構體記憶體配置設定
    • 3.1 建立對應資料結構的slab cache描述符
    • 3.2 配置設定slab obj
    • 3.3 slab obj的釋放
  • 4 slab系統通用記憶體配置設定(kmalloc)
    • 4.1 kmalloc的實作
    • 4.2 釋放slab系統配置設定的通用記憶體(kfree)

前面介紹的arm64架構的linux核心記憶體管理系統的初始化流程,linux os從start_kernel開始,完成核心記憶體的分頁初始化和核心内容基本資料結構的初始化,并将記憶體管理從memblock遷移到了夥伴系統。夥伴系統建立後,核心随後就完成slab系統的初始化工作。以上整個流程的調用過程如下所示:

start_kernel()
	|---setup_arch()//每次分頁初始化和核心記憶體基本資料結構的初始化
	|---build_all_zonelist()
	|---mm_init()
		|---mem_init()//夥伴系統初始化
		|---kmem_cache_init()//slab系統初始化
    |---......cpu初始化
    |---kmem_cache_init_late()
           

通過上面調用關系看出linux核心記憶體在夥伴系統初始化後通過調用函數kmem_cache_init()完成slab系統的初始化工作

1 slab系統初始化—kmem_cache_init&&kmem_cache_init_late

由于slab系統初始化前,linux核心已經完成了夥伴系統的初始化工作,核心可以很友善地通過夥伴系統提供記憶體。但是slab系統初始化時需要定義一些基本的資料結構來維護和管理slab系統(如slab cache描述符struct kmem_cache)。像stcuct kmem_cache這些資料結構需要小塊記憶體進行存儲,且這些小塊記憶體往往隻有幾十或者幾百位元組遠遠小于一個頁的大小。若通過夥伴系統配置設定記憶體來存儲着這些資料結構,則違背了slab系統設計的初衷(記憶體資源浪費,記憶體配置設定效率低下)。是以我們隻能通過slab系統給struct kmem_cache結構體配置設定slab緩存,然後從剛配置設定的slab緩存中去擷取對應的slab obj(小塊記憶體)來存儲struct kmem_cache結構中的資料。看到這裡已經懵逼了,在slab系統中slab緩存的描述符就是strcut kmem_cache.此時slab 系統還未開始初始化,也就是struct kmem_cache還沒有呢,又怎麼能從slab系統中配置設定到小塊記憶體呢?這活生生的一個是雞生蛋,蛋生雞的問題啊(slab系統隻能在slab系統初始化完成後初始化),貌似無解???

機智的linux核心使用了一個特殊的技巧來解決上面slab系統初始化時遇到的問題。

linux核心鏡像編譯時建立的靜态資料會在核心記憶體初始化時就會被加載到記憶體中去,後續核心使用該靜态資料時并不需要配置設定記憶體來存儲它。linux利用這一特性,使用了編譯時建立的靜态結構kmem_cache_boot資料來作為系統中的第一個slab 緩存,用它來為建立struct kmem_cache時配置設定小塊記憶體。

linux核心在初始化完kmem_cache_boot後,将其添加到slab系統的全局slab緩存連結清單slab_caches上,作為slab系統的第一個slab緩存。後面linux通過slab系統給其他基本資料結構A擷取小塊記憶體時,必然會建立一個stuct kmem_cache資料結構(作為A結構的slab cache),此時核心就能正常地從kmem_cache_boot指向的slab緩存中擷取slab obj來存儲剛為A建立的kmem_cache資料。

解決了slab系統初始化時的雞生蛋,蛋生雞問題,下面分8步對slab系統初始化進行介紹。

  1. 定義一個全局指針變量kmem_cache,該變量指向的實體是slab系統第一個slab cache描述符執行個體,為後面其它結構體配置設定struct kmem_cache時配置設定小塊記憶體。
    // /mm/slab.h
    extern struct kmem_cache *kmem_cache;
               
  2. 由于此時slab系統變量還未初始化,是以無法用slab系統給該全局變量kmem_cache配置設定記憶體空間。是以linux核心在編譯時就建立一個靜态資料kmem_cache_boot,它在核心加載到記憶體時就會給kmem_cache_boot結構配置設定記憶體空間,将kmem_cache_boot資料的位址複制給kmem_cache.此時kmem_cache指針記憶體空間已經配置設定(空間位于核心代碼段)。---->此處進入kmem_cache_init函數
    // /mm/slab.c
    static struct kmem_cache kmem_cache_boot = {
    	.batchcount = 1,
    	.limit = BOOT_CPUCACHE_ENTRIES,
    	.shared = 1,
    	.size = sizeof(struct kmem_cache),
    	.name = "kmem_cache",
    };
    void __init kmem_cache_init(void)
    {
        ......
    	kmem_cache = &kmem_cache_boot;
        ......
    }
               
  3. 下面需要對全局指針變量kmem_cache指向的實體結構的各個資料成員指派,成員中有兩個成員是指針變量array_cache和node。這時又需要配置設定小塊記憶體來給這兩個指針變量申請對應的小塊記憶體空間。但目前slab系統不具備記憶體配置設定能力,需要特殊處理:
    1. arry_cache為Per_CPU變量通過alloc_kmem_cache_cpus函數配置設定記憶體空間,此處隻是kmem_cache中的每cpu變量配置設定記憶體空間(每個cpu配置設定一個sizeof(arry_cache)大小的空間并将位址指派給cpu_cache成員),但并沒有使能這些本地高速緩存。需要注意這個時候隻是按照固定大小給每個cpu配置設定一個本地高速緩存(struct array_cache的avail和batchcount成員都為1),且不會給kmem_cache->node數組成員的每個節點配置設定共享cpu高速緩存,即是kmem_cache->shared=0.後面待所有cpu都初始化完全後,會調用kmem_cache_init_late函數完善cache_chain連結清單上所有struct kmem_cache執行個體的cpu本地高速緩存和其每個節點共享cpu緩存的初始化。
    2. node為指針數組,數組大小為系統節點數量,它記憶體空間的配置設定再次運用靜态記憶體配置設定技術,如下代碼所示預先定義一個全局靜态數組init_kmem_cache_node,并初始化。然後将init_kmem_cache_node指派給kmem_cache->node,這樣就為kmem_cache->node靜态配置設定了記憶體空間。(node隻占用了init_kmem_cache_node數組空間的前半部分,後半部分為後續配置設定slab系統的第二個struct kmem_cache執行個體的node成員提供空間。slab系統的第二個kmem_cache執行個體就是為kmem_cache_node配置設定空間的slab cache描述符)
      // /mm/slab.c
      #define NUM_INIT_LISTS (2 * MAX_NUMNODES)
      static struct kmem_cache_node __initdata init_kmem_cache_node[NUM_INIT_LISTS];
      
      static void kmem_cache_node_init(struct kmem_cache_node *parent)
      {
      	INIT_LIST_HEAD(&parent->slabs_full);
      	INIT_LIST_HEAD(&parent->slabs_partial);
      	INIT_LIST_HEAD(&parent->slabs_free);
      	parent->shared = NULL;
      	parent->alien = NULL;
      	parent->colour_next = 0;
      	spin_lock_init(&parent->list_lock);
      	parent->free_objects = 0;
      	parent->free_touched = 0;
      	parent->num_slabs = 0;
      }
      
      void __init kmem_cache_init(void)
      {
          ......
      	for (i = 0; i < NUM_INIT_LISTS; i++)
      		kmem_cache_node_init(&init_kmem_cache_node[i]);
          ......
      }
      
                 
  4. 步驟3的準備工作完成後,通過create_boot_cache正式給全局指針變量kmem_cache指向的實體結構進行初始化(指派),初始化完後,将kmem_cache挂到全局的struct kmem_cache連結清單slab_caches中。目前slab系統處于PARTIAL狀态,具有建立struct kmem_cache_node結構體的slab cache描述符的能力 。
    // mm/slab_common.c
    LIST_HEAD(slab_caches);
               
    // /mm/slab.c
    void __init kmem_cache_init(void)
    {
        ......
    	create_boot_cache(kmem_cache, "kmem_cache",
    			offsetof(struct kmem_cache, node) +
    					  nr_node_ids * sizeof(struct kmem_cache_node *),
    					  SLAB_HWCACHE_ALIGN);
        list_add(&kmem_cache->list, &slab_caches);
        ......
    }
               
    ps:為什麼處于PARTIAL隻有給kmem_cache_node結構體建立slab cache描述符的能力呢?因為slab系統想要為任意結構體建立slab cache描述符,必須要給該描述符的node成員配置設定空間,但是目前全局的slab_caches中還沒有一個能為kmem_cache_node結構體配置設定小塊記憶體空間的slab cache描述符執行個體。是以還需要在slab系統中建立一個kmem_cache_node結構體的slab cache執行個體,slab系統才能為所有結構體配置設定其對應的slab cache描述符。那麼建立kmem_cache_node結構體的slab cache執行個體過程中其node成員指向記憶體空間怎麼配置設定呢?步驟3中的init_kmem_cache_node數組的後半部分記憶體空間就是為它而準備的(第三次利用靜态記憶體配置設定技術)。
  5. create_kmalloc_cache函數再次利用靜态配置設定技術為struct kmem_cache_node結構體建立一個slab cache描述符,并将描述符放在全局數組kmalloc_caches對應的index處,然後也會将描述符也會挂入全局連結清單slab_caches中。此時slab系統處于PARTIAL_NODE狀态,該狀态的slab系統能夠利用kmalloc函數為struct kmem_cache_node配置設定對應大小的記憶體空間。(kmalloc_caches數組和kmalloc函數間的對應關系參考後面關于kmalloc_caches的講解).
    // include/slab.h
    #define KMALLOC_SHIFT_HIGH	PAGE_SHIFT
    /*
     * State of the slab allocator.
     *
     * This is used to describe the states of the allocator during bootup.
     * Allocators use this to gradually bootstrap themselves. Most allocators
     * have the problem that the structures used for managing slab caches are
     * allocated from slab caches themselves.
     */
    enum slab_state {
    	DOWN,			/* No slab functionality yet */
    	PARTIAL,		/* SLUB: kmem_cache_node available */
    	PARTIAL_NODE,		/* SLAB: kmalloc size for node struct available */
    	UP,			/* Slab caches usable but not all extras yet */
    	FULL			/* Everything is working */
    };
               
    // mm/slab_common.c
    struct kmem_cache *kmalloc_caches[KMALLOC_SHIFT_HIGH + 1];
    EXPORT_SYMBOL(kmalloc_caches);
               
    // /mm/slab.c
    void __init kmem_cache_init(void)
    {
        ......
    	kmalloc_caches[INDEX_NODE] = create_kmalloc_cache("kmalloc-node",
    					kmalloc_size(INDEX_NODE), ARCH_KMALLOC_FLAGS);
        slab_state = PARTIAL_NODE;
    	......
    }
               
  6. 該步驟主要完成的工作是分别将slab系統前兩個slab cache描述符中的node成員指向的靜态記憶體區域資料遷移到kmalloc動态配置設定的記憶體區域中。

    目前slab系統處于PARTIAL_NODE狀态,能為所有結構體建立slab cache描述符并配置設定記憶體空間。用slab系統的kmalloc函數動态配置設定兩段記憶體區域分别記為A和B(A,B記憶體區域的大小都為MAX_NUMNODES*sizeof(struct kmem_cache_node)),然後将init_kmem_cache_node數組中的前半部分資料拷貝到A區域中,後半部分資料拷貝到B區域中。最後将slab系統中struct kmem_cache結構的slab cache描述符的node成員指向A區域,struct kmem_cache_node結構體slab cache描述符node成員指向B區域.

    // /mm/slab.c
    void __init kmem_cache_init(void)
    {
        ......
    	{
    		int nid;
    
    		for_each_online_node(nid) {
    			init_list(kmem_cache, &init_kmem_cache_node[CACHE_CACHE + nid], nid);
    
    			init_list(kmalloc_caches[INDEX_NODE],
    					  &init_kmem_cache_node[SIZE_NODE + nid], nid);
    		}
    	}
    	......
    }
               
    ps:記憶體區域遷移原因—>init_kmem_cache_node數組定義時加了__initdata标志,這些資料在所有子產品都初始化完成以後會被核心統一釋放,是以要在釋放前完成資料遷移工作。
  7. 通過create_kmalloc_caches函數配置設定一些通用的slab cache描述符,并按一定規律将這些slab cache描述符存放在全局數組kmalloc_caches中緩存起來(kmalloc_caches數組中每個索引對應一個區間大小的slab cache描述符,可以通過全局數組kmalloc_info來檢視)。以後kmalloc配置設定指定大小記憶體塊時,就可以直接在kmalloc_caches數組中直接搜尋對應的slab cache描述符。到此slab系統進入了UP狀态.---->此處退出kmem_cache_init函數,該函數的完整源碼分析參考文章 kmem_cache_init函數源碼詳細解析
    // mm/slab_common.c
    static void __init new_kmalloc_cache(int idx, unsigned long flags)
    {
    	kmalloc_caches[idx] = create_kmalloc_cache(kmalloc_info[idx].name,
    					kmalloc_info[idx].size, flags);
    }
               
    // mm/slab_common.c
    void __init create_kmalloc_caches(unsigned long flags)
    {
    	int i;
    
    	for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {
    		if (!kmalloc_caches[i])
    			new_kmalloc_cache(i, flags);
    
    		/*
    		 * Caches that are not of the two-to-the-power-of size.
    		 * These have to be created immediately after the
    		 * earlier power of two caches
    		 */
    		if (KMALLOC_MIN_SIZE <= 32 && !kmalloc_caches[1] && i == 6)
    			new_kmalloc_cache(1, flags);
    		if (KMALLOC_MIN_SIZE <= 64 && !kmalloc_caches[2] && i == 7)
    			new_kmalloc_cache(2, flags);
    	}
    #ifdef CONFIG_ZONE_DMA
    	for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) {
    		struct kmem_cache *s = kmalloc_caches[i];
    
    		if (s) {
    			int size = kmalloc_size(i);
    			char *n = kasprintf(GFP_NOWAIT,
    				 "dma-kmalloc-%d", size);
    
    			BUG_ON(!n);
    			kmalloc_dma_caches[i] = create_kmalloc_cache(n,
    				size, SLAB_CACHE_DMA | flags);
    		}
    	}
    #endif
    }
               
    // /mm/slab.c
    void __init kmem_cache_init(void)
    {
        ......
    	create_kmalloc_caches(ARCH_KMALLOC_FLAGS);
    	......
    }
               
  8. 待所有cpu都完成初始化後,通過調用kmem_cache_init_late函數完善cache_chain上每個struct kmem_cache執行個體的cpu高速緩存機制和初始化struct kmem_cache執行個體的node成員數組中每個節點對應的struct kmem_cache_node執行個體。
    1. 完善cpu高速緩存機制包括本地高速cpu緩存的更新和共享cpu高速緩存的更新
      1. 本地cpu高速緩存(struct kmem_cache的 cpu_array成員)更新是通過slab obj的大小重新計算array_cache每個成員的 值,然後為每個cpu重新配置設定一個array_cache執行個體(每cpu變量),并替換原先固定大小的arry_cache執行個體。
      2. 若是NUMA結構,還需要給slab cache緩存在每個節點上配置設定一個cpu共享的高速緩存空間(struct kmem_cache執行個體的node成員數組中每個struct kmem_cache_node 執行個體元素的shard成員指向該共享緩存空間)
    2. 初始化struct kmem_cache執行個體的node成員數組中每個節點對應的struct kmem_cache_node執行個體中:主要是給slab cache描述符在每個節點上初始化3個slab連結清單。
    // mm/slab.c
    //完善全局連結清單slab_caches上所有struct kmem_cache執行個體的緩存機制(本地緩存和每個節點shared cache)
    void __init kmem_cache_init_late(void)
    {
    	struct kmem_cache *cachep;
    
    	slab_state = UP;
    
    	/* 6) resize the head arrays to their final sizes */
    	mutex_lock(&slab_mutex);
        周遊slab_caches上所有的slab cache描述符(struct kmem_cache結構體執行個體)
    	list_for_each_entry(cachep, &slab_caches, list)
            /*
             *完善目前slab cache描述符的cpu高速緩存機制:
             *1.對本地cpu高速緩存(cachep->cpu_array),通過slab obj的大小重新計算array_cache每個成員的
             *  值,然後為每個cpu重新配置設定一個array_cache執行個體(每cpu變量),并替換舊的arry_cache執行個體。
             *2.若是NUMA結構,完善cpu共享高速緩存機制(配置設定足夠的共享array_cache執行個體并完成位址關聯).
             *3.完成slab cache在每個節點上初始化3個slab連結清單,用于管理3種slab類型中slab obj
             */
    		if (enable_cpucache(cachep, GFP_NOWAIT))
    			BUG();
    	mutex_unlock(&slab_mutex);
    
    	/* Done! */
    	slab_state = FULL;
    
    ......
    }   
               

通過上面8個步驟的初始化slab系統處于了FULL狀态(slab_state = FULL),後續就可以通過用slab系統相關的函數接口為小塊記憶體配置設定記憶體空間了。

2 kmem_cache執行個體中cpu本地高速緩存和 kmem_cache_node執行個體中共享高速緩存間的關系

kmem_cache執行個體有一個Per_CPU成員變量cpu_cache,表示kmem_cache執行個體對于每個cpu都有一個array_cache,作為每cpu申請對應slab obj記憶體的本地高速緩存。同時kmem_cache執行個體對每個記憶體節點都維護了一個kmem_cache_node執行個體,kmem_cache_node執行個體的shared成員也指向一個cache_arry緩存區域(後面統一稱為shared_cache),shared_cache會為每個cpu記憶體的申請提供緩存(也是緩存對應的slab obj)。

2.1 本地cpu高速緩存(cpu_cache)和cpu共享高速緩存(shared_cache)間的互動關系

slab系統正常slab obj申請流程:

系統首先會從cpu_cache中嘗試擷取一個對象用于配置設定,若失敗,則嘗試來到所有CPU共享的shared_cache中嘗試擷取;若還是失敗就會從對應節點slab連結清單中分
配一個slab obj,若仍然失敗,kmem_cache執行個體則會從夥伴系統擷取一組連續的頁框建立一個新的SLAB,然後從新的slab中擷取一個對象
           

slab系統正常slab obj釋放流程:

系統先将對象釋放到cpu_cache緩存中,若cpu_cache中slab obj數量超過限制,将cpu_array中的batchcount個slab obj遷移到shared_cache中.若
shared_cache中slab obj數量超過限制,則将shared_cache中的batchcount個slab obj遷移到自己所屬的slab中(slab位于對應節點的3條連結清單中)。
若對應節點的空閑slab obj過多,kmem_cache執行個體會清理出一些完全空閑的slab,并将這些slab占用的頁釋放到夥伴系統中
           

shared cache充當了cpu_cache和slab間slab obj申請和釋放的緩存:

  1. 當cpu_cache中的slab obj用盡後,會調用cache_alloc_refill函數申請最多batchcount個slab obj到cpu_cache緩存中:
    //mm/slab.c
    cache_alloc_refill:
    	先判斷本地節點的shared_cache中是否有可用slab obj
    		 a.若有,則調用transfer_objects函數将shared_cache中的最多bactchcount個slab obj轉移到
    		   cpu_cache中.
    		 b.若無,則到本地記憶體節點的slab連結清單中去擷取最多bactchcount個slab obj,并轉移到cpu_cache中
               
  2. 當cpu_cache中的slab obj裝滿(數量達到或超過cpu_cache->limit),會調用cache_flusharray函數将最多batchcount個slab obj轉移到shared_cache或slab連結清單中:
    //mm/slab.c
    cache_flusharray:
    	先判斷本地節點的的shared_cache是否已滿:
    		a.若未滿(緩存的slab obj個數小于shared_cache->limit),則将最多batchcount個slab obj從
    		  cpu_cache中轉移到shared_cache緩存中。
    		b.若已滿,則将最多batchcount個slab obj從cpu_cache中轉移到本地記憶體節點的slab連結清單中
               
    ps:當slab三鍊的空閑slab對象超過上限,并且待釋放的slab對象對應的slab中已經沒有正在使用的slab對象時,将slab直接釋放掉(釋放到夥伴系統中)。釋放slab對應的函數是slab_destroy

2.2 shared cache引入原因

  1. shared_cache加快了slab系統申請和釋放slab obj的速度:

    slab obj釋放到本地節點的shard_cache要比将釋放的本地節點的slab連結清單更快.而且從shared_cache中申請slab

    obj的速度要快于從slab連結清單中申請slab obj的速度。

  2. shared_cache增加了硬體cache命中的幾率:

    同種類型的slab obj被linux os短時間内連續地申請和釋放時,剛被cpu釋放的slab obj會先緩存在cpu_cache中,然後經過shared_cache中轉後,最後才會将其返還給對應記憶體節點的slab連結清單。若在上述過程中cpu又需要剛被釋放的同類型的slab obj,若我們再shared_cache緩存中擷取一個slab obj,該slab obj在硬體緩存中被剔除的機率會很小,緩存命中率就會較大。但是我們從slab連結清單中去獲得slab obj,此時可能會從其他slab中去配置設定slab obj,而不是剛釋放的slab obj對應的slab,顯然新配置設定的slab obj在硬體緩存中的機率會很小,硬體緩存命中率就會很小。

3 slab系統中特殊結構體記憶體配置設定

slab系統初始化後,就能在核心态用于對任意資料結構配置設定小塊記憶體。具體方法如下:

3.1 建立對應資料結構的slab cache描述符

//mm/slab_common.c
/*參數和傳回值介紹:
 * kmem_cache_create - Create a cache.
 * @name: A string which is used in /proc/slabinfo to identify this cache.
 * @size: The size of objects to be created in this cache.
 * @align: The required alignment for the objects.
 * @flags: SLAB flags
 * @ctor: A constructor for the objects.
 *
 * Returns a ptr to the cache on success, NULL on failure.
 * Cannot be called within a interrupt, but can be interrupted.
 * The @ctor is run when new pages are allocated by the cache.
 *
 * The flags are
 *
 * %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5)
 * to catch references to uninitialised memory.
 *
 * %SLAB_RED_ZONE - Insert `Red' zones around the allocated memory to check
 * for buffer overruns.
 *
 * %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware
 * cacheline.  This can be beneficial if you're counting cycles as closely
 * as davem.
 */
struct kmem_cache *
kmem_cache_create(const char *name, size_t size, size_t align,
		  unsigned long flags, void (*ctor)(void *))
           
kmem_cache_create()
	|--->__kmem_cache_alias()//查找是否有現成的slab cache描述符可用
			|--->find_mergeable()//周遊slab_caches連結清單上的所有slab cache描述符,進行比對
			|--->若擷取到現成的slab描述符直接傳回該描述符,否則繼續往下執行
    |--->create_cache()//給對應的結構體配置設定一個slab cache描述符,并将該描述符添加到全局slab_caches連結清單上
    		|--->kmem_cache_zalloc()//利用struct kmem_cache的slab cache描述符,為指定結構體的slab cache描述	                                       符配置設定空間
    		|--->利用傳入的slab描述符資料初始化部分指定結構體slab描述相關成員.
    		|--->__kmem_cache_create();//完善指定結構體slab cache描述的緩存機制
					|--->初始化指定結構體slab描述相關成員(對齊後大小,freelist_size等)
                 |--->setup_cpu_cache()//完善slab cache描述符的緩存機制
                        	--->enable_cpucache()//使能指定結構體slab描述本地緩存和共享緩存
			|--->list_add(&s->list, &slab_caches)//将指定結構體slab cache描述符介入全局連結清單
           

3.2 配置設定slab obj

kmem_cache_alloc函數利用建立的slab cache描述符給對應結構體配置設定記憶體空間(slab obj),該函數在建立結構體對應slab cache描述符時已經被調用過,用它來為struct kmem_cache結構體配置設定記憶體空間(kmem_cache_zalloc()函數調用了kmem_cache_alloc函數來給stuct kmem_cache執行個體配置設定記憶體空間,兩函數的差別是kmem_cache_zalloc配置設定的記憶體空間要被0填充)

**
 * kmem_cache_alloc - Allocate an object
 * @cachep: The cache to allocate from.
 * @flags: See kmalloc().
 *
 * Allocate an object from this cache.  The flags are only relevant
 * if the cache has no available objects.
 */
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
           
//mm/slab.c
kmem_cache_alloc()
	|--->slab_alloc()
		|--->local_irq_save()
		|--->__do_cache_alloc()//在關閉本地中斷的情況下調用該函數來配置設定slab obj
				|--->____cache_alloc()
		|--->local_irq_restore()
           

slab_alloc函數中在關閉本地中斷的前提下調用____do_cache_alloc()函數從指定的結構體的slab描述符中配置設定位址空間(slab obj)

//mm/slab.c
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
	void *objp;
	struct array_cache *ac;

	check_irq_off();
	//擷取指定結構體對應的slab cache描述的本地高速緩存
	ac = cpu_cache_get(cachep);
    //若該描述符本地cpu高速緩存中有可用的slab obj直接從其entry數組中擷取slab obj的位址(entry後進先出)
	if (likely(ac->avail)) {
		ac->touched = 1;
		objp = ac->entry[--ac->avail];

		STATS_INC_ALLOCHIT(cachep);
		goto out;
	}

	STATS_INC_ALLOCMISS(cachep);
    /*
     *若對應的本地高速緩存中無slab obj,則調用cache_alloc_refill填充本地高速緩存,并配置設定slab obj:
     *(1)擷取slab cache描述符中本地節點中的cpu共享緩存,若共享緩存含有slab obj,則通過transfer_objects
     *   函數将共享緩存的部分slab obj遷移到本地緩存中去(遷移數量不超過ac->batchcount),最後取出本地高
     *   速緩存的entry數組中最後一個slab obj并配置設定出去
     *(2)若本地記憶體節點cpu共享緩存中也無slab obj,則檢查本地記憶體節點中的slabs_partial連結清單和slabs_free
     *   連結清單,若兩個連結清單不都為空,則擷取含有空閑slab obj的slab,将該slab的對象遷移部分到本地高速緩
     *   存中去(遷移對象個數不超過ac->batchcount),然後根據slab中空閑obj的數量将其插入到對應的slab鍊
     *   表中(slab_partial或slab_full),最後從本地高速緩存擷取slb obj并配置設定出去.
     *(3)若該結構體對應的整個slab cache描述符中都找不到空閑slab obj,則隻能通過夥伴系統配置設定2^gfporder
     *	 個頁的記憶體塊,并封裝成slab,然後插入到本地節點的空閑連結清單中,最後将新增加的slab中的部分slab obj遷
     *   移到cpu本地緩存中去,并将本地高速緩存的entry數組中最後一個slab obj配置設定出去
     */
	objp = cache_alloc_refill(cachep, flags);
	/*
	 * the 'ac' may be updated by cache_alloc_refill(),
	 * and kmemleak_erase() requires its correct value.
	 */
    //再次擷取cpu本地高速緩存,因為它可能被cache_alloc_refill更新過
	ac = cpu_cache_get(cachep);

out:
	/*
	 * To avoid a false negative, if an object that is in one of the
	 * per-CPU caches is leaked, we need to make sure kmemleak doesn't
	 * treat the array pointers as a reference to the object.
	 * 防止每cpu變量出現記憶體洩露的情況
	 */
 
	if (objp)
		kmemleak_erase(&ac->entry[ac->avail]);//entry容器存放的指向slab obj的虛拟位址
	return objp;
}
           

本地高速緩存的slab obj填充函數cache_alloc_refill:

//mm/slab.c
static void *cache_alloc_refill(struct kmem_cache *cachep, gfp_t flags)
{
	int batchcount;
	struct kmem_cache_node *n;
	struct array_cache *ac, *shared;
	int node;
	void *list = NULL;
	struct page *page;

	check_irq_off();
	node = numa_mem_id();

	ac = cpu_cache_get(cachep);
	batchcount = ac->batchcount;
	if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {
		/*
		 * If there was little recent activity on this cache, then
		 * perform only a partial refill.  Otherwise we could generate
		 * refill bouncing.
		 */
		batchcount = BATCHREFILL_LIMIT;
	}
    //擷取slab cache描述符本地節點對應的struct kmem_cache_node執行個體
	n = get_node(cachep, node);

	BUG_ON(ac->avail > 0 || !n);
    //擷取本地記憶體節點的cpu共享緩存池
	shared = READ_ONCE(n->shared);
    /*
     *若共享緩存池緩存對象為空且本地記憶體節點中slab_partial和slab_free連結清單中的slab都為空,則需要通過夥伴
     *系統配置設定新的slab(2^gfporder個連續頁)
     */
	if (!n->free_objects && (!shared || !shared->avail))
		goto direct_grow;

	spin_lock(&n->list_lock);
	shared = READ_ONCE(n->shared);

	/* See if we can refill from the shared array */
    /*
     *本地節點共享緩存池存在可用對象,通過transfer_objects函數遷移部分slab obj到本地高速緩存池,并從本
     *地高速緩存中将ac->entry[ac->avail]指向的slab obj配置設定出去
     */
	if (shared && transfer_objects(ac, shared, batchcount)) {
		shared->touched = 1;
		goto alloc_done;
	}
    /*
     *若本地節點存在包含空閑slab obj的slab,循環執行下面兩個操作,直到遷移的slab obj個數等于
     *ac->batchcount或節點2個slab連結清單中不存在包含空閑slab obj的slab時為止:
     * 		1.通過get_first_slab擷取本地節點中包含空閑slab obj的slab
     * 		2.利用alloc_block從slab中将空閑的slab obj遷移到本地高速緩存中(遷移obj數量等于batchcount或
     *		  slab中空閑obj被搬空為止)
     */
	while (batchcount > 0) {
		/* Get slab alloc is to come from. */
		page = get_first_slab(n, false);
		if (!page)
			goto must_grow;

		check_spinlock_acquired(cachep);
		/*
		 *從slab中遷移空閑slab obj到本地cpu高速緩存(當遷移obj個數等于ac->batchcount或slab中不存在空閑
		 *slab obj為止)
		 */
		batchcount = alloc_block(cachep, ac, page, batchcount);
        //根據目前slab中空閑obj的情況将該slab插入到對應的slab連結清單中
		fixup_slab_list(cachep, n, page, &list);
	}

must_grow:
    //node上空閑slab obj數量,減去放到本地CPU緩存的slab obj數量
	n->free_objects -= ac->avail;
alloc_done:
	spin_unlock(&n->list_lock);
	fixup_objfreelist_debug(cachep, &list);

direct_grow:
    //結構體對應的slab cache描述符中所有地方都找不到空閑的slab obj,就隻能通過夥伴系統配置設定新的slab
	if (unlikely(!ac->avail)) {
		/* Check if we can use obj in pfmemalloc slab */
		if (sk_memalloc_socks()) {
			void *obj = cache_alloc_pfmemalloc(cachep, n, flags);

			if (obj)
				return obj;
		}
		//從夥伴系統配置設定頁框,并初始化為slab管理對象,最後挂入本地節點的slab_partial連結清單
		page = cache_grow_begin(cachep, gfp_exact_node(flags), node);
        /*
         *頁塊配置設定的時候中斷可能被再次使能,是以再次确定本地CPU緩存是否可用,防止配置設定頁框是其他程序也做了
         *相同操作
         */
		ac = cpu_cache_get(cachep);
        /*
         *1.将新擷取的slab中部分slab obj遷移到本地高速緩存池中,然後将新slab出入到和自身狀态一緻的連結清單中
         *2.部分slab obj:若slab obj中空閑的obj大于等于batchcount,則遷移batchcount個obj,而小于
         *  batchcount,則會将遷移slab中所有空閑obj
         */
		if (!ac->avail && page)
			alloc_block(cachep, ac, page, batchcount);
        //收尾工作,頁框挂載到對應的slab連結清單,以及更新統計資訊
		cache_grow_end(cachep, page);

		if (!ac->avail)
			return NULL;
	}
	ac->touched = 1;
	//傳回将被配置設定出去的slab obj,本地高速緩存的entry容器是LIFO(後進先出),確定配置設定出去的頁是最熱的。
	return ac->entry[--ac->avail];
}
           

夥伴系統配置設定頁塊給slab系統提供slab(cache_grow_begin):

//mm/slab.c
cache_grow_begin()
    |--->kmem_getpages()//從夥伴管理系統上擷取一個空閑頁塊(頁面數為cachep->gfporder),并将該頁塊設定成slab.
    		|--->__alloc_pages_node()
    		|--->__SetPageSlab(page)
    |--->alloc_slabmgmt()//為新的slab配置設定空閑slab obj的索引數組(freelist),freelist數組可能嵌入slab,或與slab分離(通過OFF_SLAB(cachep)判斷)
    |--->cache_init_objs()//初始化slab上的空閑對象,包括空閑對象索引數組freelist
           

3.3 slab obj的釋放

slab系統通過kmem_cache_free函數來釋放其為指定結構體配置設定的slab obj(位址空間).

/**
 * kmem_cache_free - Deallocate an object
 * @cachep: The cache the allocation was from.
 * @objp: The previously allocated object.
 *
 * Free an object which was previously allocated from this
 * cache.
 */
void kmem_cache_free(struct kmem_cache *cachep, void *objp)
{
	unsigned long flags;
    /*
     *擷取objp對應的slab cache描述符
     *1.obj虛拟位址通過virt_to_pfn和pfn_to_page兩個函數找到obj對應空間頁描述符struct page
     *2.page->slab_slab_cache擷取到obj的slab cache描述符
     */
	cachep = cache_from_obj(cachep, objp);
	......
	//關閉本地CPU中斷
	local_irq_save(flags);
	......
    //objp釋放的核心函數
	__cache_free(cachep, objp, _RET_IP_);
	local_irq_restore(flags);
	......
}
           
/*
 * Release an obj back to its cache. If the obj has a constructed state, it must
 * be in this state _before_ it is released.  Called with disabled ints.
 */
static inline void __cache_free(struct kmem_cache *cachep, void *objp,
				unsigned long caller)
{
	/* Put the object into the quarantine, don't touch it for now. */
	if (kasan_slab_free(cachep, objp))
		return;

	___cache_free(cachep, objp, caller);
}
//函數速省略了debug調試處理函數
void ___cache_free(struct kmem_cache *cachep, void *objp,
		unsigned long caller)
{
	//擷取本地高速緩存
    struct array_cache *ac = cpu_cache_get(cachep);

	//檢查本地cpu中斷是否關閉
    check_irq_off();
	......
	/*
	 *slab cache描述符本地cpu高速緩存池緩存的空閑obj數量超過限制(ac->avail>=ac->limit),調用cache_flusharray
	 *函數對本地緩存池的空閑obj進行回收嘗試
	 */
	if (ac->avail < ac->limit) {//limit,avail和batchcount值在slab建立該slab cache描述時,enable_cpucache()								  函數根據slab obj大小計算而來
		STATS_INC_FREEHIT(cachep);
	} else {
		STATS_INC_FREEMISS(cachep);
		cache_flusharray(cachep, ac);
	}
	......
	//将釋放的放在本地cpu高速緩存池目前的最熱位置(ac->entry的末尾,它是LIFO容器)
	ac->entry[ac->avail++] = objp;
}
           

當slab cache描述符的本地cpu高速緩存池中的空閑slab obj數量超過ac->limit值,則slab系統會調用ache_flusharray函數将本地緩存池的部分空閑obj slab進行回收處理:将本地高速緩存cpu_cache的enrty容器中前batchcount個空閑slab obj遷移到本節點cpu共享緩存shared_cache的entry容器的末尾。

static void cache_flusharray(struct kmem_cache *cachep, struct array_cache *ac)
{
	int batchcount;
	struct kmem_cache_node *n;
	int node = numa_mem_id();
	LIST_HEAD(list);
	//擷取ac,在回收收縮,遷出的空閑obj速率
	batchcount = ac->batchcount;
	//驗證本地cpu中斷是否關閉
    check_irq_off();
    //擷取本地記憶體節點對應的struct kmem_cache_node描述符
	n = get_node(cachep, node);
    //加鎖
	spin_lock(&n->list_lock);
    /*
     *本地節點存在cpu共享緩存池,則将本地高速緩存池entry容器中前面N個元素拷貝到cpu共享緩存池的entry容器的尾端.
     * n = min(ac->batchcount,shared_array->limit - shared_array->avail).
     */
	if (n->shared) {
		struct array_cache *shared_array = n->shared;
		int max = shared_array->limit - shared_array->avail;
		if (max) {
            //本地緩存遷移slab obj的個數min(ac->batchcount,shared_array->limit - shared_array->avail)
			if (batchcount > max)
				batchcount = max;
            /*
             *ac為本地高速緩存,shared_array為共享cpu緩存。此處是将ac->entry容器頭部的batchcount個元素遷移逐個
             *到shared_array->enrty容器的尾部.
             */
			memcpy(&(shared_array->entry[shared_array->avail]),
			       ac->entry, sizeof(void *) * batchcount);
			shared_array->avail += batchcount;
			goto free_done;
		}
	}

	free_block(cachep, ac->entry, batchcount, node, &list);
free_done:
#if STATS
	{
		int i = 0;
		struct page *page;

		list_for_each_entry(page, &n->slabs_free, lru) {
			BUG_ON(page->active);

			i++;
		}
		STATS_SET_FREEABLE(cachep, i);
	}
#endif
	spin_unlock(&n->list_lock);
	slabs_destroy(cachep, &list);
	ac->avail -= batchcount;
    //遷移後,将本地cpu緩存池的entry容器中後面剩餘的空閑的obj對象逐個移動到容器的頭部
	memmove(ac->entry, &(ac->entry[batchcount]), sizeof(void *)*ac->avail);
}
           

4 slab系統通用記憶體配置設定(kmalloc)

/**
 * kmalloc - allocate memory
 * @size: how many bytes of memory are required.
 * @flags: the type of memory to allocate.
 *
 * kmalloc is the normal method of allocating memory
 * for objects smaller than page size in the kernel.
 *
 * The @flags argument may be one of:
 *
 * %GFP_USER - Allocate memory on behalf of user.  May sleep.
 *
 * %GFP_KERNEL - Allocate normal kernel ram.  May sleep.
 *
 * %GFP_ATOMIC - Allocation will not sleep.  May use emergency pools.
 *   For example, use this inside interrupt handlers.
 *
 * %GFP_HIGHUSER - Allocate pages from high memory.
 *
 * %GFP_NOIO - Do not do any I/O at all while trying to get memory.
 *
 * %GFP_NOFS - Do not make any fs calls while trying to get memory.
 *
 * %GFP_NOWAIT - Allocation will not sleep.
 *
 * %__GFP_THISNODE - Allocate node-local memory only.
 *
 * %GFP_DMA - Allocation suitable for DMA.
 *   Should only be used for kmalloc() caches. Otherwise, use a
 *   slab created with SLAB_DMA.
 *
 * Also it is possible to set different flags by OR'ing
 * in one or more of the following additional @flags:
 *
 * %__GFP_COLD - Request cache-cold pages instead of
 *   trying to return cache-warm pages.
 *
 * %__GFP_HIGH - This allocation has high priority and may use emergency pools.
 *
 * %__GFP_NOFAIL - Indicate that this allocation is in no way allowed to fail
 *   (think twice before using).
 *
 * %__GFP_NORETRY - If memory is not immediately available,
 *   then give up at once.
 *
 * %__GFP_NOWARN - If allocation fails, don't issue any warnings.
 *
 * %__GFP_REPEAT - If allocation fails initially, try once more before failing.
 *
 * There are other flags available as well, but these are not intended
 * for general use, and so are not documented here. For a full list of
 * potential flags, always refer to linux/gfp.h.
 */
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
    ......
}
           

kmalloc函數是slab系統中傳統意義上的記憶體配置設定函數,通過傳入要申請記憶體空間的大小Size,就可以傳回一個按位元組對齊的記憶體空間給調用者使用,不會涉及到slab cache描述符的建立等操作。這有點類似于使用者空間的malloc函數。例如向slab系統配置設定一個大小為15位元組的記憶體空間,則可以用kmalloc(15,GFP_KERNEL)來實作,它會傳回一個16位元組大小的記憶體空間(slab obj)給調用者。

4.1 kmalloc的實作

在slab系統初始化時,它對一個全局數組進行了初始化

// mm/slab_common.c
struct kmem_cache *kmalloc_caches[KMALLOC_SHIFT_HIGH + 1];
           

初始化後,該數組中存儲的每個slab cache描述符描述的都是大小不同的記憶體空間(記憶體空間大小都是按8位元組對齊的)。可以通過kmalloc_info數組來了解全局素組kmalloc_caches中每個slab cache描述的名字和對應slab obj大小.

//mm/slab_common.c
/*
 * kmalloc_info[] is to make slub_debug=,kmalloc-xx option work at boot time.
 * kmalloc_index() supports up to 2^26=64MB, so the final entry of the table is
 * kmalloc-67108864.
 */
static struct {
	const char *name;
	unsigned long size;
} const kmalloc_info[] __initconst = {
	{NULL,                      0},		{"kmalloc-96",             96},
	{"kmalloc-192",           192},		{"kmalloc-8",               8},
	{"kmalloc-16",             16},		{"kmalloc-32",             32},
	{"kmalloc-64",             64},		{"kmalloc-128",           128},
	{"kmalloc-256",           256},		{"kmalloc-512",           512},
	{"kmalloc-1024",         1024},		{"kmalloc-2048",         2048},
	{"kmalloc-4096",         4096},		{"kmalloc-8192",         8192},
	{"kmalloc-16384",       16384},		{"kmalloc-32768",       32768},
	{"kmalloc-65536",       65536},		{"kmalloc-131072",     131072},
	{"kmalloc-262144",     262144},		{"kmalloc-524288",     524288},
	{"kmalloc-1048576",   1048576},		{"kmalloc-2097152",   2097152},
	{"kmalloc-4194304",   4194304},		{"kmalloc-8388608",   8388608},
	{"kmalloc-16777216", 16777216},		{"kmalloc-33554432", 33554432},
	{"kmalloc-67108864", 67108864}
};
           

slab系統可以通過記憶體空間的大小Size獲得其比對的slab cache描述符在全局素組slab caches中的索引,通過如下kmalloc_index函數來實作。

/*
 * Figure out which kmalloc slab an allocation of a certain size
 * belongs to.
 * 0 = zero alloc
 * 1 =  65 .. 96 bytes
 * 2 = 129 .. 192 bytes
 * n = 2^(n-1)+1 .. 2^n
 */
static __always_inline int kmalloc_index(size_t size)
{
	if (!size)
		return 0;

	if (size <= KMALLOC_MIN_SIZE)
		return KMALLOC_SHIFT_LOW;

	if (KMALLOC_MIN_SIZE <= 32 && size > 64 && size <= 96)
		return 1;
	if (KMALLOC_MIN_SIZE <= 64 && size > 128 && size <= 192)
		return 2;
	if (size <=          8) return 3;
	if (size <=         16) return 4;
	if (size <=         32) return 5;
	if (size <=         64) return 6;
	if (size <=        128) return 7;
	if (size <=        256) return 8;
	if (size <=        512) return 9;
	if (size <=       1024) return 10;
	if (size <=   2 * 1024) return 11;
	if (size <=   4 * 1024) return 12;
	if (size <=   8 * 1024) return 13;
	if (size <=  16 * 1024) return 14;
	if (size <=  32 * 1024) return 15;
	if (size <=  64 * 1024) return 16;
	if (size <= 128 * 1024) return 17;
	if (size <= 256 * 1024) return 18;
	if (size <= 512 * 1024) return 19;
	if (size <= 1024 * 1024) return 20;
	if (size <=  2 * 1024 * 1024) return 21;
	if (size <=  4 * 1024 * 1024) return 22;
	if (size <=  8 * 1024 * 1024) return 23;
	if (size <=  16 * 1024 * 1024) return 24;
	if (size <=  32 * 1024 * 1024) return 25;
	if (size <=  64 * 1024 * 1024) return 26;
	BUG();

	/* Will never be reached. Needed because the compiler may complain */
	return -1;
}
           

到此kmalloc函數的實作方式已經很明了了:

  1. 先将傳入的記憶體空間大小Size,通過kmalloc_index函數處理,獲得其比對的slab cache描述符在全局數組kmalloc_caches中的索引Index_Size
  2. 通過索引Index_Size,在全局素組kmalloc_caches中擷取到配置設定Index_Size記憶體空間的slab cache描述 Kmem_Cache_Size
  3. 在獲得Kmem_Cache_Size描述符後,通過kmem_cache_alloc函數就能配置設定出需要的記憶體空間。但是這裡需要注意Kmem_Cache_Size配置設定出的slab obj指向的記憶體空間大小是Size按8位元組對齊後的值.

4.2 釋放slab系統配置設定的通用記憶體(kfree)

/**
 * kfree - free previously allocated memory
 * @objp: pointer returned by kmalloc.
 *
 * If @objp is NULL, no operation is performed.
 *
 * Don't free memory not originally allocated by kmalloc()
 * or you will run into trouble.
 */
void kfree(const void *objp)
{
	struct kmem_cache *c;
	unsigned long flags;
	//記錄kfree軌迹
	trace_kfree(_RET_IP_, objp);
	//對位址做非零判斷
	if (unlikely(ZERO_OR_NULL_PTR(objp)))
		return;
	local_irq_save(flags);
	kfree_debugcheck(objp);
    //将虛拟位址轉化為頁面描述符,然後用page的slab_cache成員獲得對應的slab cache描述符
	c = virt_to_cache(objp);
	debug_check_no_locks_freed(objp, c->object_size);

	debug_check_no_obj_freed(objp, c->object_size);
    //将objp釋放到對應slab cache描述符的本地緩存中去cpu_cache
	__cache_free(c, (void *)objp, _RET_IP_);
	local_irq_restore(flags);
}
EXPORT_SYMBOL(kfree);
           

kfree函數傳入的參數是kmalloc函數執行成功的傳回值,就是slab系統配置設定的一段記憶體空間的首位址(在slab系統中也可将其稱為slab obj),kfreee函數就是要将該obj釋放到其對應的slab cache描述符的本地高速緩存中去(緩存滿了會涉及到緩存的收縮操作)

  1. 通過virt_to_cache擷取到函數參數objp對應的slab cache描述符Kmem_cache_tmp
  2. 通過__cache_free函數将objp釋放到Kmem_cache_tmp的本地高速緩存緩存池中(前面3.3.3小節已經對__cache_free函數有過詳細介紹)

繼續閱讀