天天看點

十天學Linux核心之第三天---記憶體管理方式

  昨天分析的程序的代碼讓自己還在頭昏目眩,腦子中這幾天都是關于Linux核心的,對于自己出現的一些問題我會繼續改正,希望和大家好好分享,共同進步。今天将會講訴Linux如何追蹤和管理使用者空間程序的可用記憶體和核心的可用記憶體,還會講到核心對記憶體分類的方式以及如何決定配置設定和釋放記憶體,記憶體管理是應用程式通過軟硬體協助來通路記憶體的一種方式,這裡我們主要是介紹作業系統正常運作對記憶體的管理。插個話題,剛才和姐姐聊天,她快結婚了,說起了自己的初戀,可能是一句很搞笑的話,防火防盜防初戀,,嘎嘎,這個好像是的吧,盡管大三了,有了新的女友,也特别喜歡她,把她當作未來的伴侶,但是那個時候确實很美好,難怪哦姐姐聊起這些,這裡祝福姐姐,心情好相信接下來的部落格講解一定可以狀态大好,和大家一起好好分享。

  在深入了解記憶體管理的實作之前一些有關記憶體管理的進階概念我們有必要了解一下,先說虛拟記憶體,怎麼産生的呢?現在作業系統要求能夠使多個程式共享作業系統資源,并且還要求記憶體對程式的開發透明,有了虛拟記憶體之後,依靠透明的使用磁盤空間,就可以使系統實體記憶體大得多,而且使得多個程式共享更加容易友善。然後再說說虛拟位址,當一個程式從記憶體中存取資料時,會使用位址來指出需要通路的記憶體位址,這就是虛拟位址,它組成了程序虛拟位址空間,其大小取決于體系結構的字寬。記憶體管理在作業系統中負責維護虛拟位址和實體位址之間的關系并且實作分頁機制(将頁從記憶體到磁盤之間調入調出的機制), 核心把實體頁作為記憶體管理的基本機關;記憶體管理單元(MMU)把虛拟位址轉換為實體位址,通常以頁為機關進行處理。如:

       32位系統:頁大小4KB

       64位系統:頁大小8KB  

  上述這些資料都會在頁面載入記憶體時候得以更新,下面來看看核心是如何利用頁來實作記憶體管理的。

  作為記憶體管理的基本單元,頁有許多屬性需要維護,下面的結構體描述了頁描述符的各種域以及記憶體管理是如何使用它們的,在include/linux/mm.h中可以檢視到定義。

  要了解的一點是page結構與實體頁相關,而并非與虛拟頁相關。是以,該結構對頁的描述是短暫的。核心僅僅用這個結構來描述目前時刻在相關的實體頁中存放的東西。這種資料結構的目的在于描述實體記憶體本身,而不是描述包含在其中的資料。

   在linux中,核心也不是對所有的也都一視同仁,核心而是把頁分為不同的區,使用區來對具有相似特性的頁進行分組。Linux必須處理如下兩種硬體存在缺陷而引起的記憶體尋址問題:

一些硬體隻能用某些特定的記憶體位址來執行DMA

一些體系結構其記憶體的實體尋址範圍比虛拟尋址範圍大的多。這樣,就有一些記憶體不能永久地映射在核心空間上。

  為了解決這些制約條件,Linux系統使用了三種區:

ZONE_DMA:這個區包含的頁用來執行DMA操作。

ZONE_NOMAL:這個區包含的都是能正常映射的頁(用于映射非DMA)

ZONE_HIGHEM:這個區包"高端記憶體",其中的頁能不永久地映射到核心位址空間。

  每個記憶體區都有一個對應的描述符号zone,zone結構被定義在/linux/mmzone.h中,接下來浏覽一下該結構的一些域:

  核心提供了一種請求内層的底層機制,并提供了對它進行通路的幾個接口。所有這些接口都是以頁為機關進行操作的頁面是實體記憶體存儲頁的基本單元,隻要有程序申請記憶體,核心便會請求一個頁面給它,同理,如果頁面不再使用,那麼核心将其釋放,以便其他程序可以使用,下面介紹一下這些函數。

  alloc_page() 用于請求單頁,不需要描述請求記憶體大小的order參數

  alloc_pages() 可以請求頁面組

  __get_free_page() 請求單頁面操作的簡化版本

  __get_dma_pages() 用于從ZONE_DMA區請求頁面

   當你不再需要頁時可以用下列函數釋放它們,隻是提醒:僅能釋放屬于你的頁,否則可能導緻系統崩潰。核心是完全信任自己的,如果有非法操作,核心會開心的把自己挂起來,停止運作。

  上面提到都是以頁為機關的配置設定方式,那麼對于常用的以位元組為機關的配置設定來說,核心通供的函數是kmalloc(),和mallloc很像吧,其實還真是這樣,隻不過多了一個flags參數。用它可以獲得以位元組為機關的一塊核心記憶體。

   kmalloc

kmalloc()函數與使用者空間malloc一組函數類似,獲得以位元組為機關的一塊核心記憶體。

void *kmalloc(size_t size, gfp_t flags)

void kfree(const void *objp)

配置設定記憶體實體上連續。

gfp_t标志:表明配置設定記憶體的方式。如:

GFP_ATOMIC:配置設定記憶體優先級高,不會睡眠

GFP_KERNEL:常用的方式,可能會阻塞。

   vmalloc    

void *vmalloc(unsigned long size)

void vfree(const void *addr)

vmalloc()與kmalloc方式類似,vmalloc配置設定的記憶體虛拟位址是連續的,而實體位址則無需連續,與使用者空間配置設定函數一緻。

vmalloc通過配置設定非連續的實體記憶體塊,在修正頁表,把記憶體映射到邏輯位址空間的連續區域中,虛拟位址是連續的。 是否必須要連續的實體位址和具體使用場景有關。在不了解虛拟位址的硬體裝置中,記憶體區都必須是連續的。通過建立頁表轉換成虛拟位址空間上連續,肯定存在一些消耗,帶來性能上影響。是以通常核心使用kmalloc來申請記憶體,在需要大塊記憶體時使用vmalloc來配置設定。

  程序往往會以位元組為機關請求小塊記憶體,為了滿足這種小記憶體的請求,核心特别實作了Slab配置設定器,Slab配置設定器使用三個主要結構維護對象資訊,分别如下:

kmem_cache的緩存描述符

cache_sizes的通用緩存描述符

slab的slab描述符

十天學Linux核心之第三天---記憶體管理方式

  在最高層是 cache_chain,這是一個 slab 緩存的連結清單。可以用來查找最适合所需要的配置設定大小的緩存。cache_chain 的每個元素都是一個 kmem_cache 結構的引用。一個kmem_cache中的所有object大小都相同。這裡我們首先看看緩存描述符中各個域以及他們的含義。

  如我們所講,作為通用目的的緩存大小都是被定義好的,且成對出現,一個為從DMA記憶體配置設定對象,另一個從普通記憶體中配置設定,結構cache_sizes包含了有關通用緩存大小的所有資訊。代碼解釋如下:

  最後介紹一下Slab狀态和描述符域的值,如下表(N=slab中的對象數目,X=某一變量的正數)

Free

Partial

Full

Slab->inuse

X

N

Slab->free

  現在我們再核心運作的整個生命周期範圍内觀察緩存和slab配置設定器第如何互動的,核心需要某些特殊結構以支援程序的記憶體請求和動态可加載子產品來建立特定緩存,核心函數 kmem_cache_create 用來建立一個新緩存。這通常是在核心初始化時執行的,或者在首次加載核心子產品時執行.

  當緩存被建立之後,其中的slab都是空的,事實上slab在請求對象前都不會配置設定,當我們在建立slab時,不僅僅配置設定和初始化其描述符,而且還需要和夥伴系統互動請求頁面。從一個命名的緩存中配置設定一個對象,可以使用 kmem_cache_alloc 函數,這個函數從緩存中傳回一個對象。注意如果緩存目前為空,那麼這個函數就會調用 cache_alloc_refill 向緩存中增加記憶體。

  緩存和slab都可被銷毀,其步驟與建立相逆,但是對齊問題在銷毀緩存時候不需要關心,隻需要删除緩存描述符和釋放記憶體即可,其步驟有三如下:

從緩存連結清單中删除緩存

删除slab描述符

删除緩存描述符

  目前為止,我們讨論完了slab配置設定器,那麼實際的記憶體請求是怎麼樣的呢,slab配置設定器是如何被調用的呢?這裡我粗略講解一下。當核心必須獲得位元組大小的記憶體塊時,就需要使用函數kmalloc(),它實際上會調用函數kmem_getpages完成實際配置設定,調用路徑如下:kmalloc()->__cache_alloc()->kmem_cache_grow()->kmem_getpages().kmalloc和get_free_page申請的記憶體位于實體記憶體映射區域,而且在實體上也是連續的,它們與真實的實體位址隻有一個固定的偏移,是以存在較簡單的轉換關系,virt_to_phys()可以實作核心虛拟位址轉化為實體位址:

  那麼核心是如何管理它們使用記憶體的呢,使用者程序一旦建立便要配置設定一個虛拟位址空間,其位址範圍可以通過增加或者删除線性位址間隔得以擴大或者縮減,在核心中程序位址空間的所有資訊都被儲存在mm_struct結構中,mm_struct和vm_area_struct結構之間的關系如下圖:

十天學Linux核心之第三天---記憶體管理方式

  最後簡單講一下程序映象分布于線性位址空間的相關重點,當使用者程式被載入記憶體之後,便被賦予 了自己的線性空間,并且被映射到程序位址空間,下面需要注意。

永久映射:可能會阻塞

  映射一個給定的page結構到核心位址空間:

  void *kmap(struct page *page)

  解除映射:

  void kunmap(struct page *page) 

臨時映射:不會阻塞     

void *kmap_atomic(struct page *page)

  小結

  這次講了記憶體管理的大部分内容,介紹了頁是如何在核心中被跟蹤,然後讨論了記憶體區,之後讨論了小于一頁的小塊記憶體配置設定,即slab配置設定器管理。在核心管理結構和衆多代碼分析完了之後,繼續讨論了使用者空間程序管理特殊方式,最後簡單介紹了程序映象分布于線性位址空間的相關重點。裡面肯定有些内容比較散亂,代碼有補全的狀況,希望大家能夠多家批評改正,一起讨論,今天發生了很多事情,到現在才更新完,晚上還有些時間,還需要好好了解體會,共勉。

繼續閱讀