關於Linux的內存管理,本文分別從內核空間和用戶空間兩個視角來闡述
頁(page)是內核的內存管理基本單位。
==> linux/mm_types.h
struct page {
page_flags_t flags; 頁標志符
atomic_t _count; 頁引用計數
atomic_t _mapcount; 頁映射計數
unsigned long private; 私有數據指針
struct address_space *mapping; 該頁所在地址空間描述結構指針,用於內容為文件的頁幀
pgoff_t index; 該頁描述結構在地址空間radix樹page_tree中的對象索引號即頁號
struct list_head lru; 最近最久未使用struct slab結構指針鏈表頭變量
void *virtual; 頁虛擬地址
};
盡管處理器的最小可尋址單位通常為字或字節,但內存管理單元(MMU,把虛擬地址轉換為物理地址的硬件設備)通常以頁為單位處理。內核用struct page結構體表示每個物理頁,struct page結構體占40個字節,假定系統物理頁大小為4KB,對於4GB物理內存,1M個頁面,故所有的頁面page結構體共占有內存大小為40MB,相對系統4G,這個代價並不高。
內核把頁劃分在不同的區(zone)
總共3個區,具體如下:
下面列舉所有的頁為單位進行連續物理內存分配,也稱為低級頁分配器:
kmalloc,vmalloc分配都是以字節為單位
(1) kmalloc
void * kmalloc(size_t size, gfp_t flags)
該函數返回的是一個指向內存塊的指針,其內存塊大小至少為size,所分配的內存在物理內存中連續且保持原有的數據(不清零)
其中部分flags取值說明:
kmalloc內存分配最終總是調用__get_free_pages 來進行實際的分配,故前綴都是GFP_開頭。 kmalloc分最多只能分配32個page大小的內存,每個page=4k,也就是128K大小,其中16個字節用來記錄頁描述結構。kmalloc分配的是常駐內存,不會被交換到文件中。最小分配單位是32或64字節。
kzalloc
kzalloc()
等價於先用 kmalloc()
申請空間, 再用memset()
來初始化,所有申請的元素都被初始化為0。
static inline void *kzalloc(size_t size, gfp_t flags)
{
return kmalloc(size, flags | __GFP_ZERO); //通過或標志位__GFP_ZERO,初始化元素為0
}
(2) vmalloc
void * vmalloc(unsigned long size)
該函數返回的是一個指向內存塊的指針,其內存塊大小至少為size,所分配的內存是邏輯上連續的。
kmalloc不同,該函數乜有flags,默認是可以休眠的。
小結:
slab分配器的作用:
slab層把不同的對象劃分為高速緩存組,每個高速緩存組都存放不同類型的對象,每個對象類型對應一個高速緩存。kmalloc接口監理在slab層只是,使用一組通用高速緩存。
每個高速緩存都是用kmem_cache結構來表示
實例分析: 內核初始化期間,/kernel/fork.c的fork_init()中會創建一個名叫task_struct的高速緩存; 每當進程調用fork()時,會通過dup_task_struct()創建一個新的進程描述符,並調用do_fork(),完成從高速緩存中獲取對象。
當設置單頁內核棧,那麼每個進程的內核棧只有一頁大小,這取決於編譯時配置選項。 好處:
任意函數必須盡量節省棧資源, 方法就是所有函數讓局部變量所占空間之和不要超過幾百字節。
高端內存中的頁不能永久地映射到內核地址空間。
使用每個CPU數據好處:
分配函數選擇:
用戶空間中進程的內存,往往稱為進程地址空間。Linux采用虛擬內存技術
每個進程都有一個32位或64位的地址空間,取決於體系結構。 一個進程的地址空間與另一個進程的地址空間即使有相同的內存地址,也彼此互不相干,對於這種共享地址空間的進程稱之為線程。一個進程可尋址4GB的虛擬內存(32位地址空間中),但不是所有虛擬地址都有權訪問。對於進程可訪問的地址空間稱為內存區域。每個內存區域都具有對相關進程的可讀、可寫、可執行屬性等相關權限設置。
內存區域可包含的對象:
這些內存區域不能相互覆蓋,每一個進程都有不同的內存片段。
內存描述符由mm_struct
結構體表示,
==> linux/sched.h
struct mm_struct
{
struct vm_area_struct *mmap;
rb_root_t mm_rb;
...
atomic_t mm_users;
atomic_t mm_count;
struct list_head mmlist;
...
};
在進程的進程描述符(<linux/sched.h>中定義的task_struct結構體)中,mm域記錄該進程使用的內存描述符。故current->mm代表當前進程的內存描述符。
fork()函數 利用copy_mm函數復制父進程的內存描述符,子進程中的mm_struct結構體通過allcote_mm()從高速緩存中分配得到。通常,每個進程都有唯一的mm_struct結構體,即唯一的進程地址空間。
當子進程與父進程是共享地址空間,可調用clone(),那麼不再調用allcote_mm(),而是僅僅是將mm域指向父進程的mm,即 tsk->mm = current->mm。
相反地,撤銷內存是exit_mm()函數,該函數會進行常規的撤銷工作,更新一些統計量。
內核線程