Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基說明:node
在以前的系列文章中,分析到了Buddy System
的頁框分配,Slub分配器
的小塊內存對象分配,這些分配的地址都是物理內存連續的。當內存碎片後,連續物理內存的分配就會變得困難,可使用vmap
機制,將不連續的物理內存頁框映射到連續的虛擬地址空間中。vmalloc
的分配就是基於這個機制來實現的。算法
還記得下邊這張圖嗎?
數組
vmap/vmalloc
的區域就是在VMALLOC_START ~ VMALLOC_END
之間。緩存
開啓探索之旅吧。數據結構
這兩個數據結構比較簡單,直接上代碼:less
struct vm_struct { struct vm_struct *next; void *addr; unsigned long size; unsigned long flags; struct page **pages; unsigned int nr_pages; phys_addr_t phys_addr; const void *caller; }; struct vmap_area { unsigned long va_start; unsigned long va_end; unsigned long flags; struct rb_node rb_node; /* address sorted rbtree */ struct list_head list; /* address sorted list */ struct llist_node purge_list; /* "lazy purge" list */ struct vm_struct *vm; struct rcu_head rcu_head; };
struct vmap_area
用於描述一段虛擬地址的區域,從結構體中va_start/va_end
也能看出來。同時該結構體會經過rb_node
掛在紅黑樹上,經過list
掛在鏈表上。
struct vmap_area
中vm
字段是struct vm_struct
結構,用於管理虛擬地址和物理頁之間的映射關係,能夠將struct vm_struct
構成一個鏈表,維護多段映射。函數
關係以下圖:
工具
紅黑樹,本質上是一種二叉查找樹,它在二叉查找樹的基礎上增長了着色相關的性質,提高了紅黑樹在查找,插入,刪除時的效率。在紅黑樹中,節點已經進行排序,對於每一個節點,左側的的元素都在節點以前,右側的元素都在節點以後。
紅黑樹必須知足如下四條規則:性能
定義以下:3d
struct rb_node { unsigned long __rb_parent_color; struct rb_node *rb_right; struct rb_node *rb_left; } __attribute__((aligned(sizeof(long)))); /* The alignment might seem pointless, but allegedly CRIS needs it */
因爲內核會頻繁的進行vmap_area
的查找,紅黑樹的引入就是爲了解決當查找數量很是多時效率低下的問題,在紅黑樹中,搜索元素,插入,刪除等操做,都會變得很是高效。至於紅黑樹的算法操做,本文就再也不深刻分析,知道它的用途便可。
vmap
函數,完成的工做是,在vmalloc
虛擬地址空間中找到一個空閒區域,而後將page頁面數組
對應的物理內存映射到該區域,最終返回映射的虛擬起始地址。
總體流程以下:
操做流程比較簡單,來一個樣例分析,就清晰明瞭了:
vmap
調用中,關鍵函數爲alloc_vmap_area
,它先經過vmap_area_root
二叉樹來查找第一個區域first vm_area
,而後根據這個first vm_area
去查找vmap_area_list
鏈表中知足大小的空間區域。
在alloc_vmap_area
函數中,有幾個全局的變量:
static struct rb_node *free_vmap_cache; static unsigned long cached_hole_size; static unsigned long cached_vstart; static unsigned long cached_align;
用於緩存上一次分配成功的vmap_area
,其中cached_hole_size
用於記錄緩存vmap_area
對應區域以前的空洞的大小。緩存機制固然也是爲了提升分配的效率。
vunmap
執行的是跟vmap
相反的過程:從vmap_area_root/vmap_area_list
中查找vmap_area
區域,取消頁表映射,再從vmap_area_root/vmap_area_list
中刪除掉vmap_area
,頁面返還給夥伴系統等。因爲映射關係有改動,所以還須要進行TLB的刷新,頻繁的TLB刷新會下降性能,所以將其延遲進行處理,所以稱爲lazy tlb
。
來看看逆過程的流程:
vmalloc
用於分配一個大的連續虛擬地址空間,該空間在物理上不連續的,所以也就不能用做DMA緩衝區。vmalloc
分配的線性地址區域,在文章開頭的圖片中也描述了:VMALLOC_START ~ VMALLOC_END
。
直接分析調用流程:
從過程當中能夠看出,vmalloc
和vmap
的操做,大部分的邏輯操做是同樣的,好比從VMALLOC_START ~ VMALLOC_END
區域之間查找並分配vmap_area
, 好比對虛擬地址和物理頁框進行映射關係的創建。不一樣之處,在於vmap
創建映射時,page
是函數傳入進來的,而vmalloc
是經過調用alloc_page
接口向Buddy System申請分配的。
vmalloc VS kmalloc
vmalloc
和kmalloc
的差別了吧,kmalloc
會根據申請的大小來選擇基於slub分配器
或者基於Buddy System
來申請連續的物理內存。而vmalloc
則是經過alloc_page
申請order = 0
的頁面,再映射到連續的虛擬空間中,物理地址不連續,此外vmalloc
能夠休眠,不該在中斷處理程序中使用。vmalloc
相比,kmalloc
使用ZONE_DMA和ZONE_NORMAL
空間,性能更快,缺點是連續物理內存空間的分配容易帶來碎片問題,讓碎片的管理變得困難。直接上代碼:
void vfree(const void *addr) { BUG_ON(in_nmi()); kmemleak_free(addr); if (!addr) return; if (unlikely(in_interrupt())) __vfree_deferred(addr); else __vunmap(addr, 1); }
若是在中斷上下文中,則推遲釋放,不然直接調用__vunmap
,因此它的邏輯基本和vunmap
一致,再也不贅述了。