記憶體管理
1.頁
核心把物理頁作為記憶體管理的基本單位。大多數 32 位體繫結構支援 4KB 的頁,而 64 位元體繫結構一般會支援 8KB 的頁。核心用 struct page 結構表示系統中的每個物理頁。該結構位於<linux/mm.h>中。
struct page { page_flags_t flags; /* 表示頁的狀態,每一位表示一種狀態,定義在<linux/page_flags.h> */ atomic_t _count; /* 存放頁的引用計數,0代表沒有被引用 */ atomic_t _mapcount; unsigned long private; strcut address_space *mapping; pgoff_t index; struct list_head lru; void *virtual; /* 頁在虛擬記憶體中的地址,動態映射物理頁 */}
2.區
由於硬體的限制,核心並不能對所有的頁一視同仁。Linux必須處理如下兩種由於硬體存在缺陷而引起的記憶體定址問題:
1)一些硬體只能用某些特定的記憶體位址來執行DMA(直接記憶體存取)。
2)一些體繫結構其記憶體的物理定址範圍比虛擬定址範圍大得多。這樣,就有一些記憶體不能永久地映射到核心空間上。
由於存在這種限制,核心把具有相似特性的頁劃分為不同的區(ZONE):
1)ZONE_DMA——這個區包含的頁能用來執行DMA操作。
2)ZONE_NORMAL——這個區包含的都是能正常地映射網頁。
3)ZONE_HIGHMEM——這個區包含“高端記憶體”,其中的頁並能不永久地映射到核心地址空間。
Linux把系統的頁劃分為區,形成不同的記憶體池,這樣就可以根據用途進行分配。注意,區的劃分沒有任何物理意義,這隻是核心為了管理頁而採取的一種邏輯上的分組。用於DMA的記憶體必須從ZONE_DMA中進行分配,但是一般用途的記憶體卻既能從ZONE_DMA分配,也能從ZONE_NORMAL分配。
3.獲得頁
核心提供了一種請求記憶體的底層機制,並提供了對它進行訪問的幾個介面。所有這些介面都以頁為單位分配記憶體,定義於<linux/gfp.h>。最核心的函數是:
struct page *alloc_pages( unsigned int gfp_mask, unsigned int order );
該函數分配 2order 個連續的物理頁,並返回一個指向第一頁的 page 結構體指標,如果出錯就返回 NULL。
void *page_address( struct page *page );
把給定的頁轉換成它的邏輯地址。如果無須用到 struct page,可以調用:
unsigned long __get_free_pages( unsigned int gfp_mask, unsigned int order );
這個函數與 alloc_pages 作用相同,不過它直接返回所請求的第一個頁的邏輯地址。因為頁是連續的,因此其他頁也會緊隨其後。
如果只需要一頁,可以用以下兩個函數:
struct page *alloc_page( unsigned int gfp_mask );unsigned long _get_free_page( unsigned int gfp_mask );
如果需要讓返回頁的內容全為0,可以使用下面這個函數
unsigned long get_zeroed_page(unsigned int gfp_mask );
當不再需要頁時可以使用以下函數來釋放它。
void __free_pages( struct page *page, unsigned int order );void free_pages( unsigned long addr, unsigned int order );void free_page( unsigned long addr );
釋放頁時要謹慎,只能釋放屬於你的頁。傳遞了錯誤的 struct page 或地址,用了錯誤的 order 值都可能導致系統崩潰。請記住,核心是完全依賴自己的。
4.kmalloc
kmalloc 與 malloc 一族函數非常類似,只不過它多了一個 flags 參數。kmalloc在<linux/slab.h>中聲明:
void *kmalloc( size_t size, int flags );
這個函數返回一個指向記憶體塊的指標,其記憶體塊至少要有 size 大小。所分配的記憶體正在物理上是連續的。在出錯時,它返回 NULL。除非沒有足夠的記憶體可用,否則核心總能分配成功。在對 kmalloc 調用之後,你必須檢查返回的是不是 NULL,如果是,要適當地處理錯誤。
在低級頁分配函數還是 kmalloc 中,都用到了 gfp_mask(分配器標誌)。這些標誌可分為三類:行為修飾符、區修飾符及類型。
1)行為修飾符表示核心應當如何分配所需的記憶體。在某些特定情況下,只能使用某些特定的方法分配記憶體。例如,中斷處理常式就要求核心在分配記憶體的過程中不能睡眠(因為中斷處理常式不能被重新調度)。
2)區修飾符指明到底從哪一區中進行分配。
3)類型標誌組合了行為修飾符和區修飾符,將各種可能用到的組合歸納為不同類型,簡化了修飾符的使用。
行為修飾符
可以同時指定這些分配標誌。例如
ptr = kmalloc( size, __GFP_WAIT | __GFP_IO | __GFP_FS );
區修飾符
指定以上標誌中的一個就可以改變核心試圖進行分配的區。如果沒有指定任何標誌,則核心從ZONE_DMA或ZONE_NORMAL進行分配,優先從ZONE_NORMAL進行分配。不能給 _get_free_pages 或 kmalloc 指定 __GFP_HIGHMAM,因為這兩個函數返回的都是邏輯地址,而不是 page 結構,這兩個函數分配的記憶體當前有可能還沒有映射到核心的虛擬位址空間。因此,也可能根本就沒有邏輯地址。只有 alloc_pages 才能分配高端記憶體。
類型標誌(括弧裡說明該類型等價的行為修飾符組合)
核心中最常用的標誌是 GFP_KERNEL。這種分配可能會引起睡眠,它使用的是普通優先順序,因此這個標誌只用在可以重新安全高度的進程上下文中。另一個全然相反的標誌是 GFP_ATOMIC。因為這個標誌表示不能睡眠的記憶體配置,因此想要滿足調用者擷取記憶體的請求將會受到很嚴格的限制。
kmalloc 的另一端就是 kfree,kfree聲明於<linux/slab.h>中
void kfree( const void *ptr );
kfree 函數釋放由 kmalloc 分配出來的記憶體塊。調用 kfree( NULL ) 是安全的。
5.vmalloc
vmalloc 的工作方式是類似於 kmalloc,只不過前者分配的記憶體虛擬位址是連續的,而物理地址則無需連續。使用者空間分配函數工作方式的區別:
1)由 malloc 返回的頁在進程的虛擬位址空間內是連續的,但是,這並不保證它們在物理RAM中也連續。
2)kmalloc 確保頁在物理地址上是連續的(虛擬位址自然也是連續的)。
3)vmalloc 只確保頁的虛擬位址空間內是連續的。
大多數情況下,只有硬體裝置需要得到物理地址連續的記憶體。但由於通過 vmalloc 獲得的頁必須一個一個地進行映射,這就會導致比直接記憶體映射大得多的TLB抖動。因為這個原因,很多核心代碼都用 kmalloc 來獲得記憶體。