6.內存管理

內存管理

在內核中分配內存不像在其他地方分配內存那么容易。造成這種局面的因素很多,根本原因是內核本身不能像用戶空間那樣奢侈地使用內存。

1.頁

內核把物理頁作為內存管理的基本單位。內存管理單元(MMU,管理內存并把虛擬地址轉換為物理地址的硬件)通常以頁為單位。體系結構不同,支持的頁大小也不同。內核用struct page結構表示每個物理頁:

struct page {
         unsigned long flags;                                                      
         atomic_t count;                
         unsigned int mapcount;          
         unsigned long private;          
         struct address_space *mapping;  
         pgoff_t index;                  
         struct list_head lru;  
         void *virtual;                  
};

對上面重要變量說明:

  • flag的每一位單獨表示一個狀態,標志定義在<linux/page-flags.h>
  • count存放頁的引用次數,為0則是空閑頁
  • virtual是頁的虛擬地址

2.區

由于硬件限制,內核對頁不能一視同仁。有些頁位于內存特定的物理地址上,不能用于一些特定的任務,因此內核把頁劃分為不同的區(zone)。Linux必須處理如下兩種由于硬件缺陷而引起的內存尋址問題:

  • 一些硬件只能用某些特定內存來執行DMA(直接內存訪問)
  • 一些體系結構的內存物理尋址范圍比虛擬尋址范圍大的多,因此部分內存永遠無法映射到內核空間

因此Linux主要存在四種區:

  • ZONE_DMA,包含的頁可以執行DMA
  • ZONE_DMA32,和ZONE_DMA不同在于,這些頁面只能被32位設備訪問,某些體系下該區比ZONE_DMA更大
  • ZONE_NORMAL,能夠正常映射的頁
  • ZONE_HIGHMEM,不能永久被映射到內核空間地址的區

每個區都用struct zone表示,定義在<linux/mmzone.h>

struct zone {
         spinlock_t              lock;
         unsigned long           free_pages;
         unsigned long           pages_min, pages_low, pages_high;
         unsigned long           protection[MAX_NR_ZONES];
         spinlock_t              lru_lock;       
         struct list_head        active_list;
         struct list_head        inactive_list;
         unsigned long           nr_scan_active;
         unsigned long           nr_scan_inactive;
         unsigned long           nr_active;
         unsigned long           nr_inactive;
         int                     all_unreclaimable; 
         unsigned long           pages_scanned;    
         struct free_area        free_area[MAX_ORDER];
         wait_queue_head_t       * wait_table;
         unsigned long           wait_table_size;
         unsigned long           wait_table_bits;
         struct per_cpu_pageset  pageset[NR_CPUS];
         struct pglist_data      *zone_pgdat;
         struct page             *zone_mem_map;
         unsigned long           zone_start_pfn;
 
         char                    *name;
         unsigned long           spanned_pages;  
         unsigned long           present_pages;  
};

其中,lock是自旋鎖防止該結構被并發訪問;watermark數組持有該區的最小值、最低和最高水位值;name是以NULL結尾的區名字,三個區名字為DMANormalHighMem

3.獲得頁

前面了解了頁和區的概念,下面講述如何請求和釋放頁。

請求頁

標志 描述
alloc_page(gfp_mask) 只分配一頁,返回指向頁結構的指針
alloc_pages(gfp_mask, order) 分配2^order頁,返回指向第一頁頁結構的指針
__get_free_page(gfp_mask) 只分配一頁,返回指向其邏輯地址的指針
__get_free_pages(gfp_mask, order) 分配2^order頁,返回指向第一頁邏輯地址的指針
get_zeroed_page(gfp_mask) 只分配一頁,讓其內容填充0,返回指向邏輯地址的指針

釋放頁

釋放頁需要謹慎,只能釋放屬于你的頁。傳遞了錯誤的struct page或地址,,用了錯誤的order值都可能導致系統崩潰。

例如釋放8個頁:

free_pages(page, 3)

可以看到釋放過程與C語言的釋放內存很相似的。

4.kmalloc()

上述的方法是對以頁為單位的連續物理頁,而以字節為單位的分配,內核提供的函數是kmalloc()。使用方法和malloc()類似,只是多了一個flags參數,其在<linux/slab.h>中聲明:

void * kmalloc(size_t size, gfp_t flags)

kmalloc()對應的函數就是kfree()kfree()聲明于<linux/slab.h>中:

void kfree(const void *ptr)

5.vmalloc()

vmalloc()kmalloc()工作方式類似,但是kmalloc()使用的連續的物理地址。vmalloc()使用非連續的物理地址,該函數為了把物理上不連續的頁轉換為虛擬地址空間上連續的頁,必須專門建立頁表項。

大多數情況下,一般硬件設備需要使用連續的物理地址,而軟件可以使用非連續的物理地址,但是大多數情況,為了性能提升,內核往往用kmalloc()更多。

vmalloc()函數聲明在<linux/vmalloc.h>中,定義在<mm/vmalloc.c>中。用法和用戶空間的malloc()相同:

void * vmalloc(unsigned long size)

釋放通過vmalloc()所獲得的內存,使用下面函數:

void vfree(const void *addr)

6.slab層

分配和釋放數據結構是所有內核中最常用操作之一。為了便于數據的頻繁分配和回收,編程人員常常會用到空閑鏈表空閑鏈表包含可供使用的、已經分配好的數據結構塊。當代名需要一個新的數據結構實例時,就可以從空閑鏈表中抓取一個,而不需要分配內存,再把數據存放進去。不需要這個數據結構的實例時,就放回空閑鏈表,而不是釋放它。空閑鏈表相對于對象的高速緩存——快速存儲頻繁使用的對象類型(這個策略簡直是awesome!)。

沒有免費的蛋糕,對于空閑鏈表存在的主要問題是無法全局控制。當內存緊缺時,內核無法通知每個空閑鏈表,讓其收縮緩存的大小,以便釋放部分內存。實際上,內核根本就不知道任何空閑鏈表。因此未來彌補這個缺陷,Linux內核提供了slab層(也就是所謂的slab分配器)。slab分配器扮演了通用數據結構緩存層的角色。對于slab分配器設計需要考慮一下幾個原則:

  • 頻繁使用的數據結構也會頻繁分配和釋放,因此應當緩存它們。
  • 頻繁分配和回收必然會導致內存碎片。為了避免這種情況,空閑鏈表的緩存會連續地存放。因為已釋放的數據結構又會放回空閑鏈表,不會導致碎片。
  • 回收的對象可以立即投入下一次分配,因此,對于頻繁的分配和釋放,空閑鏈表能夠提高其性能。
  • 如果讓部分緩存專屬于單個處理器,那么,分配和釋放就可以在不加SMP鎖的情況下進行。
  • 對存放的對象進行著色,以防止多個對象映射到相同的高速緩存行。

slab層把不同的對象劃分為所謂的高速緩存組,其中每個高速緩存都存放不同類型的對象,每種對象類型對應一個高速緩存,例如一個高速緩存用于task_struct,一個用于struct inode。kmalloc()接口建立在slab層上,使用了一組通用高速緩存。這些緩存又被分為slabs,slab由一個或多個物理上連續的頁組成,一般情況下,slab也就僅僅由一頁組成。每個高速緩存可以由多個slab組成。每個slab都包含一些對象成員,這里的對象指的是被緩存的數據結構,每個slab處于三種狀態之一:滿,部分滿,空。當內核的某一部分需要一個新的對象時,先從部分滿的slab中進行分配。如果沒有部分滿的slab,就從空的slab中進行分配。如果沒有空的slab,就要創建一個slab了。下圖給出高速緩存,slab及對象之間的關系:

高速緩存、slab和對象關系

每個緩存都使用kmem_catche結構表示,結構中包含3個鏈表。這些鏈表包含高速緩存所有的slab。slab描述符struct slab用來描述每個slab:

struct slab {
        struct list_head  list;       /*滿,部分滿或空鏈表*/
        unsigned long     colouroff;  /*slab著色的偏移量*/
        void              *s_mem;     /*在slab中的第一個對象*/
        unsigned int      inuse;      /*已分配的對象數*/
        kmem_bufctl_t     free;       /*第一個空閑對象*/
};

slab層負責內存緊缺情況下所有底層的對齊、著色、分配、釋放和回收等。

7.棧上的靜態分配

在前面討論的分配例子,不少可以分配到棧上。用戶空間可以奢侈地負擔很大的棧,而且棧空間還可以動態增長,相反內核空間不能——棧小而固定。給每個進程分配一個固定小棧,可以減小內存消耗和棧管理任務負擔。

進程的內核棧大小既依賴體系結構,也和編譯時的選項有關。在任何一個函數中,都必須盡量節省棧資源。讓函數所有局部變量之后不要超過幾百字節(棧上分配大量的靜態分配是不理智的),棧溢出就會覆蓋掉臨近堆棧末端的數據。首先就是前面講的thread_info

8.每個CPU使用數據

支持SMP的操作系統使用每個CPU上的數據,對于給定的處理器其數據是唯一的。一般而言,每個CPU的數據存放在一個數組內,數組中的每一項對應著系統上一個存在的處理器,安裝當前處理器號就能確定這個數組的當前元素。

在Linux中引入了新的操作接口稱為percpu,頭文件<linux/percpu.h>聲明了所有接口操作例程,可以在文件mm/slab.c<asm/percpu.h>找到定義。

使用每個CPU數據的好處是:

  • 減少了數據鎖定
  • 大大減少了緩存失效,一個CPU操作另一個CPU的數據時,必須清理另一個CPU的緩存并刷新,存在不斷的緩存失效。持續不斷的緩存失效稱為緩存抖動

這種方式的唯一安全要求就是禁止內核搶占,同時注意進程在訪問每個CPU數據過程中不能睡眠——否則,喚醒之后可能已經到其他處理器上了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容