1. 程式人生 > >linux記憶體原始碼分析

linux記憶體原始碼分析

  記憶體池是用於預先申請一些記憶體用於備用,當系統記憶體不足無法從夥伴系統和slab中獲取記憶體時,會從記憶體池中獲取預留的那些記憶體。記憶體池與特殊slab一樣,需要使用的裝置需要自己建立記憶體池,而不是系統會自動生成。書上形容得好,記憶體比作新鮮食物,記憶體池比作罐頭食物,人比作擁有此記憶體池的模組,當無法吃到新鮮食物時,就需要開啟罐頭吃罐頭食物。

  一般情況下,記憶體池建立在slab之上,也就是說池子裡存放的是slab物件,當某個模組建立一個屬於自己的記憶體池時,建立之前,需要設計好一個分配函式和一個釋放函式,建立函式用於建立記憶體池時自動申請對應的slab物件,而釋放函式則是用於將slab物件釋放回到slab/slub中。當然經過看程式碼,感覺記憶體池中也可以存放頁,這個可以寫個模組測試一下。

  具體先看看記憶體池主要的資料結構:

 1 /* 記憶體池,用於擁有該記憶體池的"擁有者"進行記憶體儲備,只有在常規情況下分配不到記憶體的時候才會使用自己的記憶體池 */
 2 typedef struct mempool_s {
 3     spinlock_t lock;
 4     /* 最大元素個數,也是初始個數,當記憶體池被建立時,會呼叫alloc函式申請此變數相應數量的slab放到elements指向的指標陣列中 */
 5     int min_nr;        /* nr of elements at *elements */
 6     /* 當前元素個數 
*/ 7 int curr_nr; /* Current nr of elements at *elements */ 8 /* 指向一個數組,在mempool_create中會分配記憶體,陣列中儲存指向元素指標 */ 9 void **elements; 10 11 /* 記憶體池的擁有者的私有資料結構,當元素是slab中的物件時,這裡儲存的是slab快取描述符 */ 12 void *pool_data; 13 /* 當元素是slab中的物件時,會使用方法mempool_alloc_slab()和mempool_free_slab()
*/ 14 /* 分配一個元素的方法 */ 15 mempool_alloc_t *alloc; 16 /* 釋放一個元素的方法 */ 17 mempool_free_t *free; 18 /* 當記憶體池為空時使用的等待佇列,當記憶體池中空閒記憶體物件為空時,獲取函式會將當前程序阻塞,直到超時或者有空閒記憶體物件時才會喚醒 */ 19 wait_queue_head_t wait; 20 } mempool_t;

  核心裡使用mempool_create()建立一個記憶體池,使用mempool_destroy()銷燬一個記憶體池,使用mempool_alloc()申請記憶體和mempool_free()是否記憶體。一切資訊都在程式碼當中,我們直接看程式碼就清楚知道記憶體池是怎麼實現的了。

  首先我們先看mempool_create(),mempool_create()流程很簡單,它主要做的就是分配一個mempool_t結構體,然後根據引數初始化此結構體,最後呼叫傳入的alloc()函式min_nr次,把申請到的記憶體全部存放到elements中,如下:

 1 mempool_t *mempool_create_node(int min_nr, mempool_alloc_t *alloc_fn,
 2                    mempool_free_t *free_fn, void *pool_data,
 3                    gfp_t gfp_mask, int node_id)
 4 {
 5     mempool_t *pool;
 6     /* 分配一個記憶體池結構體 */
 7     pool = kzalloc_node(sizeof(*pool), gfp_mask, node_id);
 8     if (!pool)
 9         return NULL;
10     /* 分配一個長度為min_nr的陣列用於存放申請後物件的指標 */
11     pool->elements = kmalloc_node(min_nr * sizeof(void *),
12                       gfp_mask, node_id);
13     if (!pool->elements) {
14         kfree(pool);
15         return NULL;
16     }
17     /* 初始化鎖 */
18     spin_lock_init(&pool->lock);
19     pool->min_nr = min_nr;
20     /* 私有成員 */
21     pool->pool_data = pool_data;
22     /* 初始化等待佇列 */
23     init_waitqueue_head(&pool->wait);
24     pool->alloc = alloc_fn;
25     pool->free = free_fn;
26 
27     /*
28      * First pre-allocate the guaranteed number of buffers.
29      */
30     /* pool->curr_nr初始為0,因為pool使用kzalloc_node分配的,會清0 */
31     while (pool->curr_nr < pool->min_nr) {
32         void *element;
33 
34         /* 呼叫pool->alloc函式min_nr次 */
35         element = pool->alloc(gfp_mask, pool->pool_data);
36         /* 如果申請不到element,則直接銷燬此記憶體池 */
37         if (unlikely(!element)) {
38             mempool_destroy(pool);
39             return NULL;
40         }
41         /* 新增到elements指標陣列中 */
42         add_element(pool, element);
43     }
44     /* 返回記憶體池結構體 */
45     return pool;
46 }

  再看看mempool_destroy(),此函式也很簡單,直接將elements存放的記憶體依個釋放掉,然後將該釋放的elements指標陣列和mempool_t結構都釋放掉

 1 /* 銷燬一個記憶體池 */
 2 void mempool_destroy(mempool_t *pool)
 3 {
 4     while (pool->curr_nr) {
 5         /* 銷燬elements陣列中的所有物件 */
 6         /* element = pool->elements[--pool->curr_nr] */
 7         void *element = remove_element(pool);
 8         pool->free(element, pool->pool_data);
 9     }
10     /* 銷燬elements指標陣列 */
11     kfree(pool->elements);
12     /* 銷燬記憶體池結構體 */
13     kfree(pool);
14 }

   現在我們看mempool_alloc()函式,當模組從此記憶體池中獲取記憶體物件時,會呼叫此函式,此函式優先從夥伴系統或slab緩衝區獲取需要的記憶體物件,當記憶體不足導致無法獲取記憶體物件時,才會從記憶體池elements陣列中獲取,如果elements也沒有空閒的記憶體物件,根據傳入的分配標識進行相應的處理:

 1 /* 記憶體池分配物件 */
 2 void * mempool_alloc(mempool_t *pool, gfp_t gfp_mask)
 3 {
 4     void *element;
 5     unsigned long flags;
 6     wait_queue_t wait;
 7     gfp_t gfp_temp;
 8 
 9     VM_WARN_ON_ONCE(gfp_mask & __GFP_ZERO);
10     /* 如果有__GFP_WAIT標誌,則會先阻塞,切換程序 */
11     might_sleep_if(gfp_mask & __GFP_WAIT);
12 
13     /* 不使用預留記憶體 */
14     gfp_mask |= __GFP_NOMEMALLOC;    /* don't allocate emergency reserves */
15     /* 分配頁時如果失敗則返回,不進行重試 */
16     gfp_mask |= __GFP_NORETRY;    /* don't loop in __alloc_pages */
17     /* 分配失敗不提供警告 */
18     gfp_mask |= __GFP_NOWARN;    /* failures are OK */
19 
20     /* gfp_temp等於gfp_mask去除__GFP_WAIT和__GFP_IO的其他標誌 */
21     gfp_temp = gfp_mask & ~(__GFP_WAIT|__GFP_IO);
22 
23 repeat_alloc:
24 
25     /* 使用記憶體池中的alloc函式進行分配物件,實際上就是從夥伴系統或者slab緩衝區獲取記憶體物件 */
26     element = pool->alloc(gfp_temp, pool->pool_data);
27     /* 在記憶體富足的情況下,一般是能夠獲取到記憶體的 */
28     if (likely(element != NULL))
29         return element;
30 
31     /* 在記憶體不足的情況,造成從夥伴系統或slab緩衝區獲取記憶體失敗,則會執行到這 */
32     /* 給記憶體池上鎖,獲取後此段臨界區禁止中斷和搶佔 */
33     spin_lock_irqsave(&pool->lock, flags);
34     /* 如果當前記憶體池中有空閒數量,就是初始化時獲取的記憶體數量儲存在curr_nr中 */
35     if (likely(pool->curr_nr)) {
36         /* 從記憶體池中獲取記憶體物件 */
37         element = remove_element(pool);
38         /* 解鎖 */
39         spin_unlock_irqrestore(&pool->lock, flags);
40         
41         /* 寫記憶體屏障,保證之前的寫操作已經完成 */
42         smp_wmb();
43         /* 用於debug */
44         kmemleak_update_trace(element);
45         return element;
46     }
47 
48     /* 這裡是記憶體池中也沒有空閒記憶體物件的時候進行的操作 */
49     /* gfp_temp != gfp_mask說明傳入的gfp_mask允許阻塞等待,但是之前已經阻塞等待過了,所以這裡立即重新獲取一次 */
50     if (gfp_temp != gfp_mask) {
51         spin_unlock_irqrestore(&pool->lock, flags);
52         gfp_temp = gfp_mask;
53         goto repeat_alloc;
54     }
55 
56     /* 傳入的引數gfp_mask不允許阻塞等待,分配不到記憶體則直接退出 */
57     if (!(gfp_mask & __GFP_WAIT)) {
58         spin_unlock_irqrestore(&pool->lock, flags);
59         return NULL;
60     }
61 
62     init_wait(&wait);
63     /* 加入到記憶體池的等待佇列中,並把當前程序的狀態設定為只有wake_up訊號才能喚醒的狀態 ,也就是當記憶體池中有空閒物件時,會主動喚醒等待佇列中的第一個程序,或者等待超時時,定時器自動喚醒 */
64     prepare_to_wait(&pool->wait, &wait, TASK_UNINTERRUPTIBLE);
65 
66     spin_unlock_irqrestore(&pool->lock, flags);
67 
68     /* 阻塞等待5秒 */
69     io_schedule_timeout(5*HZ);
70 
71     /* 從記憶體池的等待佇列刪除此程序 */
72     finish_wait(&pool->wait, &wait);
73     /* 跳轉到repeat_alloc,重新嘗試獲取記憶體物件 */
74     goto repeat_alloc;
75 }
76 EXPORT_SYMBOL(mempool_alloc);

  最後我們看看mempool_free()函式,此函式用於將空閒記憶體物件釋放到記憶體池中,當記憶體池中空閒物件不足時,優先將空閒記憶體物件放到elements陣列中,否則直接返回到夥伴系統或slab緩衝區中。

 1 /* 記憶體池釋放記憶體物件操作 */
 2 void mempool_free(void *element, mempool_t *pool)
 3 {
 4     unsigned long flags;
 5 
 6     /* 傳入的物件為空,則直接退出 */
 7     if (unlikely(element == NULL))
 8         return;
 9 
10     /* 讀記憶體屏障 */
11     smp_rmb();
12 
13     /* 如果當前記憶體池中空閒的記憶體物件少於記憶體池中應當儲存的記憶體物件的數量時,優先把釋放的物件加入到記憶體池空閒陣列中 */
14     if (unlikely(pool->curr_nr < pool->min_nr)) {
15         spin_lock_irqsave(&pool->lock, flags);
16         if (likely(pool->curr_nr < pool->min_nr)) {
17             /* 加入到pool->elements[pool->curr_nr++]中 */
18             add_element(pool, element);
19             spin_unlock_irqrestore(&pool->lock, flags);
20             /* 喚醒等待佇列中的第一個程序 */
21             wake_up(&pool->wait);
22             return;
23         }
24         spin_unlock_irqrestore(&pool->lock, flags);
25     }
26     /* 直接呼叫釋放函式 */
27     pool->free(element, pool->pool_data);
28 }
29 EXPORT_SYMBOL(mempool_free);

  或許看完這些還是不太清楚怎麼使用記憶體池,畢竟alloc和free函式需要我們自己去設計,如果記憶體池是使用slab緩衝區進行記憶體分配時,可將slab緩衝區描述符寫入到mempool_t中的pool_data中,alloc和free函式可以直接指定mempool_alloc_slab()和mempool_free_slab(),如下:

 1 /* 建立一個slab緩衝區 */
 2 drbd_request_cache = kmem_cache_create(
 3         "drbd_req", sizeof(struct drbd_request), 0, 0, NULL);
 4     if (drbd_request_cache == NULL)
 5         goto Enomem;
 6 
 7 /* 建立一個記憶體池,私有成員設定為drbd_request_cache這個slab緩衝區,alloc和free函式設定為mempool_alloc_slab()和mempool_free_slab() */
 8 drbd_request_mempool = mempool_create(number,mempool_alloc_slab,mempool_free_slab,drbd_request_cache);
 9     if (drbd_request_mempool == NULL)
10         goto Enomem;
11 
12 
13 /* 若記憶體池從slab緩衝區中獲取記憶體物件,則核心提供的alloc函式 */
14 void *mempool_alloc_slab(gfp_t gfp_mask, void *pool_data)
15 {
16     struct kmem_cache *mem = pool_data;
17     return kmem_cache_alloc(mem, gfp_mask);
18 }
19 EXPORT_SYMBOL(mempool_alloc_slab);
20 
21 /* 若記憶體池從slab緩衝區中獲取記憶體物件,則核心提供的free函式 */
22 void mempool_free_slab(void *element, void *pool_data)
23 {
24     struct kmem_cache *mem = pool_data;
25     kmem_cache_free(mem, element);
26 }
27 EXPORT_SYMBOL(mempool_free_slab);

相關推薦

linux記憶體原始碼分析

  記憶體池是用於預先申請一些記憶體用於備用,當系統記憶體不足無法從夥伴系統和slab中獲取記憶體時,會從記憶體池中獲取預留的那些記憶體。記憶體池與特殊slab一樣,需要使用的裝置需要自己建立記憶體池,而不是系統會自動生成。書上形容得好,記憶體比作新鮮食物,記憶體池比作罐頭食物,人比作擁有此記憶體池的

Linux核心原始碼分析--記憶體管理(一、分頁機制)

        Linux系統中分為幾大模組:程序排程、記憶體管理、程序通訊、檔案系統、網路模組;各個模組之間都有一定的聯絡,就像蜘蛛網一樣,所以這也是為什麼Linux核心那麼難理解,因為不知道從哪裡開始著手去學習。很多人會跟著系統上電啟動 BIOS-->bootse

記憶體管理(Linux核心原始碼分析

背景 本篇部落格試圖通過linux核心原始碼分析linux的記憶體管理機制,並且對比核心提供的幾個分配記憶體的介面函式。然後聊下slab層的用法以及介面函式。 核心分配記憶體與使用者態分配記憶體 核心分配記憶體與使用者態分配記憶體顯然是不同的,核心不可

Linux核心原始碼分析--記憶體管理(二、函式實現技巧)

        仔細的分析了一下各個記憶體管理函式的實現,發現裡面涉及到了幾個技巧,如果知道了這幾個技巧,那麼閱讀記憶體管理原始碼將會事半功倍(主要是這幾個技巧在幾個函式中都出現過),當然也會選擇性的分析幾個比較重要的函式實現; 函式實現技巧         1、向上取整

linux核心原始碼分析-夥伴系統

之前的文章已經介紹了夥伴系統,這篇我們主要看看原始碼中是如何初始化夥伴系統、從夥伴系統中分配頁框,返回頁框於夥伴系統中的。   我們知道,每個管理區都有自己的夥伴系統管理屬於這個管理區的頁框,這也說明了,在夥伴系統初始化時,管理區必須要已經存在(初始化完成)

Linux核心原始碼分析--zImage出生實錄(Linux-3.0 ARMv7)

此文為兩年前為好友劉慶敏的書《嵌入式Linux開發詳解--基於AT91RM9200和Linux 2.6》中幫忙寫的章節的重新整理。如有雷同,純屬必然。經作者同意,將我寫的部分重新整理後放入blog中。 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

JVM與Linux記憶體關係分析

在一些實體記憶體為8g的伺服器上,主要執行一個Java服務,系統記憶體分配如下:Java服務的JVM堆大小設定為6g,一個監控程序佔用大約 600m,Linux自身使用大約800m。從表面上,實體記憶體應該是足夠使用的;但實際執行的情況是,會發生大量使用SWAP(說明實體記憶體不夠使用 了),如下圖所示。

linux核心原始碼分析

本文基於3.18.3核心的分析,nvme裝置為pcie介面的ssd,其驅動名稱為nvme.ko,驅動程式碼在drivers/block/nvme-core.c. 驅動的載入   驅動載入實際就是module的載入,而module載入時會對整個module進行初始化,nvme驅動的module初始化函式

linux中斷原始碼分析

本篇文章主要講述原始碼中是如何對中斷進行一系列的初始化的。 回顧   在上一篇概述中,介紹了幾個對於中斷來說非常重要的資料結構,分別是:中斷描述符表,中斷描述符陣列,中斷描述符,中斷控制器描述符,中斷服務例程。可以說這幾個結構組成了整個核心中斷框架主體,所以核心對整個中斷的初始化工作大多集中在

s3c2410 RTC驅動框架linux核心原始碼分析

實在無聊中就將原來的一些東西整理了一下,自己是個記性不好的人,隔斷時間整理自己,同時也希望可以方便他人。 -------------------------------------------------------------------------------------

Linux核心原始碼分析——Linux核心的入口

Jack:hi,淫龍,在Linux核心的原始碼裡,有幾段彙編程式碼,那幾段程式碼是負責Linux核心引導的。 我:是的。早期的Linux核心引導程式碼只有bootsect.s、setup.s、head.s這3個檔案,這三個檔案都是Linus在1991年左右親手寫的。後來的程

Linux核心原始碼分析--檔案系統(五、Inode.c)

_bmap()         1、_bmap()函式用於把一個檔案資料塊對映到盤塊的處理操作                  因為一個i節點對應一個檔案,所以上面的i節點對映的邏輯塊號就是檔案資料存放的邏輯塊號;i_zone[0]到i_zone[6]是直接邏輯塊號,i

塊IO層(Linux核心原始碼分析

背景 本篇部落格重點分析塊IO層,試圖瞭解在linux原始碼中是如何實現塊IO的。 基本知識 塊裝置與字元裝置 塊裝置與字元裝置都是物理外設。簡單來說,塊裝置與字元裝置的最大區別在於塊裝置都隨機對資料片段進行讀寫的,而字元裝置都以順序對資料片段進

ARMv8 Linux核心原始碼分析:__flush_dcache_all()

1.1   /* *  Flush the wholeD-cache.  * Corrupted registers: x0-x7, x9-x11  */ ENTRY(__flush_dcache_all) //保證之前的訪存指令的順序     dsb sy      

Linux核心原始碼分析--系統時間初始化(kernel_mktime()函式)

        從boot檔案中的幾個彙編程式執行後跳轉到init檔案中的main.c程式開始繼續執行,該main.c函式式為系統執行的環境進行初始化的。首先來看系統時間的初始化(因為系統時間的初始化開始程式就在init檔案中),其中主要還是由kernel中的mktime.

Linux核心原始碼分析方法

一、核心原始碼之我見 Linux核心程式碼的龐大令不少人“望而生畏”,也正因為如此,使得人們對Linux的瞭解僅處於泛泛的層次。如果想透析Linux,深入作業系統的本質,閱讀核心原始碼是最有效的途徑。我們都知道,想成為優秀的程式設計師,需要大量的實踐和程式碼的編寫。程式設計

Linux核心原始碼分析(六)--start_kernel之lockdep_init

這個函式比較短,這裡直接貼出來。 void lockdep_init(void) {         int i;         /*          * Some architectures have their own start_kernel()         

Linux核心原始碼分析之set_arch (一)

### 1. 概述 之前已經寫了幾篇Linux核心啟動相關的文章,比如:《[解壓核心映象](http://mp.weixin.qq.com/s?__biz=MzUzNjU2OTkyOA==&mid=2247484463&idx=1&sn=1dc7706fccd141ecbdb2704d

Linux核心原始碼分析之setup_arch (二)

### 1. 概述 接著上一篇《Linux核心原始碼分析之setup_arch (一)》繼續分析,本文首先分析arm_memblock_init函式,然後分析核心啟動階段的是如何進行記憶體管理的。 ### 2. arm_memblock_init 該函式的功能比較簡單,主要就是把meminfo中記錄的記憶體

Linux核心原始碼分析之setup_arch (四)

### 前言 Linux核心原始碼分析之setup_arch (三) 基本上把setup_arch主要的函式都分析了,由於距離上一篇時間比較久了,所以這裡重新貼一下大致的流程圖,本文主要分析的是bootmem_init函式。 ### 程式碼分析 bootmem_init函式的結構如下: