從底層原理出發,瞭解Linux內核之內存管理

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文講解更加底層,基本都是從Linux內核出發,會更深入。所以當你都讀完,然後再次審視這些功能的實現和設計時,我相信你會有種豁然開朗的感覺。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1、頁","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"內核把物理頁作爲內存管理的基本單元","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"儘管處理器的最小處理單位是字(或者字節),但是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"MMU","attrs":{}},{"type":"text","text":"(內存管理單元,管理內存並把","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"虛擬地址轉換爲物理地址的硬件","attrs":{}},{"type":"text","text":")通常以頁爲單位進行處理。所以從虛擬內存看,頁也是最小單元。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"體系不同,支持的頁大小不同。大多數","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"32位","attrs":{}},{"type":"text","text":"體系結構支持","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"4KB","attrs":{}},{"type":"text","text":"的頁,而","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"64位","attrs":{}},{"type":"text","text":"體系結構一般會支持","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"8KB","attrs":{}},{"type":"text","text":"的頁。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內核用struct page結構體","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"表示系統中的每個頁","attrs":{}},{"type":"text","text":",包含很多項比如頁的狀態(有沒有髒,有沒有被鎖定)、引用計數(-1表示沒有使用)等等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"page結構和物理頁相關,和虛擬內存無關。所以","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"它的描述是短暫的,僅僅記錄當前的使用狀況,當然也不會描述其中的數據","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內核用這個結構來管理系統中所有的頁,所以","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"內核知道哪些頁是空閒的,如果在使用中擁有者又是誰","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"擁有者有四種:用戶空間進程、動態分配內存的內核數據、靜態內核代碼以及頁高速緩存","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2、區","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有些頁是有特定用途的。比如內存中有些頁是專門用於DMA的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內核使用區的概念將","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"具有相似特性的頁進行分組","attrs":{}},{"type":"text","text":"。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"區是一種邏輯上的分組的概念","attrs":{}},{"type":"text","text":",而沒有物理上的意義。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"區的實際使用和分佈是與體系結構相關的。在","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"x86體系結構中主要分爲3個區:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ZONE_DMA區中的頁用來進行DMA(直接內存訪問)時使用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ZONE_HIGHMEM是高端內存,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"其中的頁不能永久的映射到內核地址空間","attrs":{}},{"type":"text","text":",也就是說,沒有虛擬地址。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"剩餘的內存就屬於ZONE_NORMAL區,叫低端內存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不是所有體系都定義全部區,有些體系結構,比如","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"x86-64可以映射和處理64位的內存空間","attrs":{}},{"type":"text","text":",所以它","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"沒有ZONE_HIGHMEM區","attrs":{}},{"type":"text","text":",所有的物理內存都都處於ZONE_DMA和ZONE_NORMAL區。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個區都用結構體struct zone表示。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"徹底掌握Linux內核內存管理,學習知識點內容:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"第一天 剖析Linux內核內存管理(一)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.1 內存泄漏/棧溢出","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.2 虛擬地址佈局/內存映射","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.3 內存模型/頁分配器","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.4 夥伴分配器/塊分配器","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"第二天 剖析Linux內核內存管理(二)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.1 kmalloc/vmalloc系統調用","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.2 高速緩存/內存屏障","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.3 頁表緩存/頁回收機制","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.4 缺頁中斷/反碎片技術","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Linux內核內存管理直播訓練營地址:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://ke.qq.com/course/3485817?flowToken=1035914","title":"","type":null},"content":[{"type":"text","text":"Linux內核內存管理專題訓練營-學習視頻​ke.qq.com","attrs":{}}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/85/85f77ac13baa0d7545fd56eeb04de60c.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3、接口","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"獲得頁","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獲得頁使用的接口是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"alloc_pages","attrs":{}},{"type":"text","text":"函數與","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"__get_free_page","attrs":{}},{"type":"text","text":"函數。後者也是調用了前者,只不過在獲得了struct page結構體後使用page_address函數獲得了虛擬地址。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們在使用這些接口獲取頁的時候可能會面對一個問題,我們獲得的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這些頁若是給用戶態用","attrs":{}},{"type":"text","text":",雖然這些頁中的數據都是隨機產生的垃圾數據,不過,雖然概率很低,但是也有可能會包含某些敏感信息。所以,更謹慎些,我們可以","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"將獲得的頁都填充爲0","attrs":{}},{"type":"text","text":"。這會用到","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"get_zeroed_page","attrs":{}},{"type":"text","text":"函數。而這個函數又用到了__get_free_pages函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這三個函數最終都是使用了alloc_pages函數","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"釋放頁","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當我們不再需要某些頁時可以使用下面的函數釋放它們:__free_pages(struct page *page, unsigned int order)free_pages(unsigned long addr, unsigned int order)free_page(unsigned long addr)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上這些接口都是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"以頁爲單位","attrs":{}},{"type":"text","text":"進行內存分配與釋放的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"kmalloc與vmalloc","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在實際中內核需要的內存不一定是整個頁,可能只是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"以字節爲單位","attrs":{}},{"type":"text","text":"的一片區域。這兩個函數就是實現這樣的目的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不同之處在於,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"kmalloc分配的是虛擬地址連續","attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"物理地址也連續","attrs":{}},{"type":"text","text":"的一片區域,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"vmalloc分配的是虛擬地址連續,物理地址不一定連續","attrs":{}},{"type":"text","text":"的一片區域。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對應的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"釋放","attrs":{}},{"type":"text","text":"內存的函數是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"kfree與vfree","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4、slab層","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以頁爲最小單位分配內存對於內核管理系統中的物理內存來說的確比較方便,但內核自身最常使用的內存卻往往是很小的內存塊——比如存放","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"文件描述符、進程描述符、虛擬內存區域描述符","attrs":{}},{"type":"text","text":"等行爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"所需的內存都遠不及一頁,一個整頁中可以聚集多個","attrs":{}},{"type":"text","text":"這些小塊內存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了滿足內核對這種小內存塊的需要,Linux系統採用了一種被稱爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"slab分配器","attrs":{}},{"type":"text","text":"(也稱作","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"slab層","attrs":{}},{"type":"text","text":")的技術。slab分配器的實現相當複雜,但原理不難,其","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"核心思想就是“存儲池”的運用","attrs":{}},{"type":"text","text":"。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"內存片段(小塊內存)被看作對象,當被使用完後,並不直接釋放而是被緩存到“存儲池”裏,留做下次使用,這無疑避免了頻繁創建與銷燬對象所帶來的額外負載。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slab分配器扮演了通用數據結構緩存層的角色。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slab層把不同的對象劃分爲所謂高速緩存組,其中每個高速緩存組都存放不同類型的對象,每種對象對應一個高速緩存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"常見的高速緩存組有:進程描述符(task_struct結構體),索引節點對象(struct inode),目錄項對象(struct dentry),通用頁對象等等","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這些高速緩存又被劃分爲slab。slab由一個或多個物理連續的頁組成,一般僅僅由一頁組成。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"每個高速緩存可以由多個slab(頁)組成","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個高速緩存都使用","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"struct kmem_cache","attrs":{}},{"type":"text","text":"結構表示,這個結構包含三個鏈表:slabs_full、slabs_partial和slabs_empty,均放在kmem_list3結構體內。這些鏈表的每個元素爲slab描述符即struct slab結構體。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個高速緩存需要創建新的slab即新的頁,還是通過上面提到的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"__get_free_page","attrs":{}},{"type":"text","text":"()來實現的。通過最終調用","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"free_pages","attrs":{}},{"type":"text","text":"()釋放內存頁。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個高速緩存的創建和銷燬使用","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"kmem_cache_create與kmem_cache_destroy","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"高速緩存中的對象的分配和釋放使用","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"kmem_cache_alloc與kmem_cache_free","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上看出,slab層仍然是建立在頁的基礎之上,可以總結爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"slab層將 空閒頁 分解成 衆多相同長度的小塊內存 以供 同類型的數據結構 使用","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"5、進程地址空間","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上我們講述了內核如何管理內存,內核內存分配機制包括了頁分配器和slab分配器。內核除了管理本身的內存外,也必須管理用戶空間中進程的內存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們稱這個內存爲進程地址空間,也就是系統中每個用戶空間進程所看到的內存。Linux系統採用虛擬內存技術,所有進程以虛擬方式共享內存。Linux中主要採用分頁機制而不是分段機制。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"5.1 地址空間佈局","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/89/89cf8b20e85dd0125659f96ea1898aca.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 進程內存區域可以包含各種內存對象,從下往上依次爲:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(1)可執行文件","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"代碼","attrs":{}},{"type":"text","text":"的內存映射,稱爲代碼段。只讀可執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(2)可執行文件的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"已初始化全局變量","attrs":{}},{"type":"text","text":"的內存映射,稱爲數據段。後續都是可讀寫。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(3)包含","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"未初始化的全局變量","attrs":{}},{"type":"text","text":",就是bass段的零頁的內存映射。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(4)","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"堆區","attrs":{}},{"type":"text","text":",動態內存分配區域;包括任何匿名的內存映射,比如malloc分配的內存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(5)","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"棧區","attrs":{}},{"type":"text","text":",用於進程用戶空間棧的零頁內存映射,這裏不要和進程內核棧混淆,進程的內核棧獨立存在並由內核維護,因爲內核管理着所有進程。所以內核管理着內核棧,內核棧管理着進程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(6)","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"其他","attrs":{}},{"type":"text","text":"可能存在的:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"內存映射文件","attrs":{}},{"type":"text","text":";","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"共享內存段","attrs":{}},{"type":"text","text":";C庫或者動態鏈接庫等","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"共享庫","attrs":{}},{"type":"text","text":"的代碼段、數據段和bss也會被載入進程的地址空間。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這份是基於Linux內核4.0版本的內核學習路線思維導圖,下面有Linux內核相關視頻學習資料:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f1/f1397f34b4a181753d7dd9240f795266.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Linux內核相關學習視頻,清晰版導圖可以點擊:","attrs":{}},{"type":"link","attrs":{"href":"https://jq.qq.com/?_wv=1027&k=1eznLI4d","title":null,"type":null},"content":[{"type":"text","text":"學習資料","attrs":{}}]},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":" 獲取","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3c/3cf10499280f9d7b43402cfdc5715df0.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"5.2 內存描述符","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內核使用","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"內存描述符mm_struct","attrs":{}},{"type":"text","text":"結構體表示進程的地址空間,該結構體包含了","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"和進程地址空間有關的全部信息","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"1 struct mm_struct {\n 2 struct vm_area_struct *mmap; /* list of memory areas */\n 3 struct rb_root mm_rb; /* red-black tree of VMAs */\n 4 struct vm_area_struct *mmap_cache; /* last used memory area */\n 5 unsigned long free_area_cache; /* 1st address space hole */\n 6 pgd_t *pgd; /* page global directory */\n 7 atomic_t mm_users; /* address space users */\n 8 atomic_t mm_count; /* primary usage counter */\n 9 int map_count; /* number of memory areas */\n10 struct rw_semaphore mmap_sem; /* memory area semaphore */\n11 spinlock_t page_table_lock; /* page table lock */\n12 struct list_head mmlist; /* list of all mm_structs */\n13 unsigned long start_code; /* start address of code */\n14 unsigned long end_code; /* final address of code */\n15 unsigned long start_data; /* start address of data */\n16 unsigned long end_data; /* final address of data */\n17 unsigned long start_brk; /* start address of heap */\n18 unsigned long brk; /* final address of heap */\n19 unsigned long start_stack; /* start address of stack */\n20 unsigned long arg_start; /* start of arguments */\n21 unsigned long arg_end; /* end of arguments */\n22 unsigned long env_start; /* start of environment */\n23 unsigned long env_end; /* end of environment */\n24 unsigned long rss; /* pages allocated */\n25 unsigned long total_vm; /* total number of pages */\n26 unsigned long locked_vm; /* number of locked pages */\n27 unsigned long def_flags; /* default access flags */\n28 unsigned long cpu_vm_mask; /* lazy TLB switch mask */\n29 unsigned long swap_address; /* last scanned address */\n30 unsigned dumpable:1; /* can this mm core dump? */\n31 int used_hugetlb; /* used hugetlb pages? */\n32 mm_context_t context; /* arch-specific data */\n33 int core_waiters; /* thread core dump waiters */\n34 struct completion *core_startup_done; /* core start completion */\n35 struct completion core_done; /* core end completion */\n36 rwlock_t ioctx_list_lock; /* AIO I/O list lock */\n37 struct kioctx *ioctx_list; /* AIO I/O list */\n38 struct kioctx default_kioctx; /* AIO default I/O context */\n39 };","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"mmap","attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"mm_rb","attrs":{}},{"type":"text","text":"描述的對象是一樣的:該地址空間中全部內存區域(all ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"m","attrs":{}},{"type":"text","text":"emory ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"a","attrs":{}},{"type":"text","text":"reas)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"mmap是以","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"鏈表","attrs":{}},{"type":"text","text":"的形式存放,而mm_rb是以","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"紅黑樹","attrs":{}},{"type":"text","text":"存放,前者有利於","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"遍歷","attrs":{}},{"type":"text","text":"所有數據,而後者有利於","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"快速搜索定位","attrs":{}},{"type":"text","text":"到某個地址。所有的mm_struct結構體都通過自身的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"mmlist域","attrs":{}},{"type":"text","text":"連接在一個雙向鏈表中,該鏈表的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"首元素","attrs":{}},{"type":"text","text":"是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"init_mm","attrs":{}},{"type":"text","text":"內存描述符,它代表","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"init進程","attrs":{}},{"type":"text","text":"的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"地址空間","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再往下看,可以看到地址空間幾個區(堆棧)對應的變量的定義。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們再回顧下在內核進程管理中,進程描述符task_struct是在內核空間中緩存,也就是我們上面描述的slab層。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而task_struct中有個mm域指向的就是該進程使用的內存描述符,再通過current->mm便可以指向當前進程的內存描述符。fork函數利用copy_mm()函數就實現了複製父進程的內存描述符,而子進程中的mm_struct結構體實際是通過文件kernel/fork.c中的allocate_mm()宏從","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"mm_cachep slab緩存","attrs":{}},{"type":"text","text":"中","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"分配","attrs":{}},{"type":"text","text":"得到的。通常,每個進程都有唯一的mm_struct結構體。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲進程描述符和進程的內存描述符都是處於slab層,所以它們元素的分配和釋放都由slab分配器來管理。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"5.3 虛擬內存區域","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 內存區域由","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"vm_area_struct","attrs":{}},{"type":"text","text":"結構體描述,見上面的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"mmap域","attrs":{}},{"type":"text","text":",內存區域在內核中也經常被稱作","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"虛擬內存區域(Virtual Memory Area,VMA)","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它描述了指定地址空間內連續區間上的一個獨立內存範圍。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內核將每個內存區域作爲一個單獨的內存對象管理,每個內存區域都擁有一致的屬性。結構體如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"1 struct vm_area_struct {\n 2 struct mm_struct *vm_mm; /* associated mm_struct */\n 3 unsigned long vm_start; /* VMA start, inclusive */\n 4 unsigned long vm_end; /* VMA end , exclusive */\n 5 struct vm_area_struct *vm_next; /* list of VMA's */\n 6 pgprot_t vm_page_prot; /* access permissions */\n 7 unsigned long vm_flags; /* flags */\n 8 struct rb_node vm_rb; /* VMA's node in the tree */\n 9 union { /* links to address_space->i_mmap or i_mmap_nonlinear */\n10 struct {\n11 struct list_head list;\n12 void *parent;\n13 struct vm_area_struct *head;\n14 } vm_set;\n15 struct prio_tree_node prio_tree_node;\n16 } shared;\n17 struct list_head anon_vma_node; /* anon_vma entry */\n18 struct anon_vma *anon_vma; /* anonymous VMA object */\n19 struct vm_operations_struct *vm_ops; /* associated ops */\n20 unsigned long vm_pgoff; /* offset within file */\n21 struct file *vm_file; /* mapped file, if any */\n22 void *vm_private_data; /* private data */\n23 };","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個內存描述符都對應於地址進程空間中的唯一區間。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"vm_mm","attrs":{}},{"type":"text","text":"域指向和VMA相關的mm_struct結構體。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個內存區域的地址範圍是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"[vm_start, vm_end),vm_next","attrs":{}},{"type":"text","text":"指向該進程的下一個內存區域","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"兩個獨立的進程將同一個文件映射到各自的地址空間,它們分別都會有一個vm_area_struct結構體來標誌自己的內存區域;但是如果兩個線程共享一個地址空間,那麼它們也同時共享其中的所有vm_area_struct結構體。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上面的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"vm_flags域","attrs":{}},{"type":"text","text":"中存放的是VMA標誌,標誌了","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"內存區域所包含的頁面的行爲和信息","attrs":{}},{"type":"text","text":"。和物理頁訪問權限不同,VMA標誌反映了內核處理頁面所需要遵循的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"行爲準則","attrs":{}},{"type":"text","text":",而不是硬件要求。而且vm_flags同時包含了內存區域中每個頁面的消息或者內存區域的整體信息,而不是具體的獨立頁面。如下表所述:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/89/89b9c3df9da951a430f981ebecea1cbd.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"開頭三個標誌表示代碼在該內存區域的可讀、可寫和可執行權限。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第四個標誌VM_SHARD說明了該區域包含的映射是否可以在多進程間共享,如果被設置了,表示共享映射;否則未被設置,表示私有映射。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中很多狀態在實際使用中都非常有用。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"5.4 mmap()和do_mmap():創建地址空間","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內核使用do_mmap()函數創建一個新的線性地址空間。但如果創建的地址區間和一個已經存在的地址區間","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"相鄰","attrs":{}},{"type":"text","text":",並且它們具有相同的訪問權限的話,那麼兩個區間將","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"合併","attrs":{}},{"type":"text","text":"爲一個。如果不能合併,那麼就確實需要創建一個","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"新的vma","attrs":{}},{"type":"text","text":"了,但無論哪種情況,do_mmap()函數都會將一個地址區間加入到進程的地址空間中。這個函數定義在linux/mm.h中,如下","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"unsigned long do_mmap(struct file *file, unsigned long addr, unsigned long len, unsigned long prot,unsigned long flag, unsigned long offset)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個函數中由","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"file指定文件","attrs":{}},{"type":"text","text":",具體映射的是文件中從偏移offset處開始,長度爲len字節的範圍內的數據,如果","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"file參數是NULL並且offset參數也是0","attrs":{}},{"type":"text","text":",那麼就代表這次映射沒有和文件相關,該情況被稱作","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"匿名映射","attrs":{}},{"type":"text","text":"(file-backed mapping)。如果","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"指定了文件和偏移量","attrs":{}},{"type":"text","text":",那麼該映射被稱爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"文件映射","attrs":{}},{"type":"text","text":"(file-backed mapping)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中參數prot指定內存區域中頁面的訪問權限:可讀、可寫、可執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"flag參數指定了VMA標誌,這些標誌指定類型並改變映射的行爲,請見上一小節。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果系統調用do_mmap的參數中有無效參數,那麼它返回一個負值;否則,它會在虛擬內存中分配一個合適的新內存區域,如果有可能的話,將新區域和臨近區域進行","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"合併","attrs":{}},{"type":"text","text":",否則內核從","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"vm_area_cachep","attrs":{}},{"type":"text","text":"長字節","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"(slab)緩存","attrs":{}},{"type":"text","text":"中分配一個vm_area_struct結構體,並且使用vma_link()函數將","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"新分配的內存區域添加到","attrs":{}},{"type":"text","text":"地址空間的內存區域","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"鏈表和紅黑樹","attrs":{}},{"type":"text","text":"中,隨後還要更新內存描述符中的total_vm域,然後才返回新分配的地址區間的初始地址。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"用戶空間","attrs":{}},{"type":"text","text":",我們可以通過","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"mmap()系統調用獲取","attrs":{}},{"type":"text","text":"內核函數","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"do_mmap()","attrs":{}},{"type":"text","text":"的功能。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"5.5 munmap()和do_munmap():刪除地址空間","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"do_mummp()函數","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"從特定的進程地址空間中刪除指定地址空間","attrs":{}},{"type":"text","text":",該函數定義在文件linux/mm.h中,如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一個參數指定要刪除區域所在的地址空間,刪除從地址start開始,長度爲len字節的地址空間,如果成功,返回0,否則返回負的錯誤碼。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與之相對應的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"用戶空間系統調用是munmap","attrs":{}},{"type":"text","text":",它是對do_mummp()函數的一個簡單封裝。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"5.6 malloc()的實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們知道malloc()是C庫中實現的。C庫對內存分配的管理還有calloc()、realloc()、free()等函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事實上,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"malloc函數是以brk()或者mmap()系統調用實現的","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"brk和sbrk主要的工作是實現虛擬內存到內存的映射。在Linux系統上,程序被載入內存時,內核爲用戶進程地址空間建立了代碼段、數據段和堆棧段,在數據段與堆棧段之間的空閒區域用於動態內存分配。我們回到內存結構mm_struct中的成員變量start_code和end_code是進程代碼段的起始和終止地址,start_data和 end_data是進程數據段的起始和終止地址,start_stack是進程堆棧段起始地址,start_brk是進程動態內存分配起始地址(堆的起始地址),還有一個 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"brk","attrs":{}},{"type":"text","text":"(","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"堆的當前最後地址","attrs":{}},{"type":"text","text":"),","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"就是動態內存分配當前的終止地址","attrs":{}},{"type":"text","text":"。所以C庫的malloc()在Linux上的基本實現是通過內核的brk系統調用。brk()是一個非常簡單的系統調用,內核再執行sys_brk()函數進行內存分配","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":",","attrs":{}},{"type":"text","text":"只是簡單地改變mm_struct結構的成員變量brk的值。而sbrk不是系統調用,是C庫函數。系統調用通常提供一種最小功能,而庫函數通常提供比較複雜的功能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我們整理一下在進程空間堆中用brk()方式進行動態內存分配的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"流程","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"   C庫函數malloc()調用Linux系統調用函數brk(),brk()執行系統調用陷入到內核,內核執行sys_brk()函數,sys_brk()函數調用do_brk()進行內存分配","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" malloc()---------->brk()-----|----->sys_brk()----------->do_brk()------------>vma_merge()/kmem_cache_zalloc()","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 用戶空間------> | 內核空間","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"            系統調用---------->","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"mmap()系統調用也可以實現動態內存分配功能,即5.4節我們提到的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"匿名映射","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那什麼時候調用brk(),什麼時候調用mmap()呢?通過閾值","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"M_MMAP_THRESHOLD","attrs":{}},{"type":"text","text":"來決定。該值","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"默認128KB","attrs":{}},{"type":"text","text":"。可以通過mallopt()來進行修改設置。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"當需要分配的內存大於該閾值,選擇mmap();否則小於等於該閾值,選擇brk()分配。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,mmap分配的內存在調用munmap後會立即返回給系統,而brk/sbrk而受","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"M_TRIM_THRESHOLD","attrs":{}},{"type":"text","text":"的影響。該環境變量同樣通過mallopt()來設置,該值代表的意義是釋放內存的最少字節數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但brk/sbrk分配的內存是否立即歸還給系統,不僅受M_TRIM_THRESHOLD的影響,還要看高地址端(","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"brk處","attrs":{}},{"type":"text","text":")的內存是否已經釋放:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假如依次malloc了str1、str2、str3(str3在最上端,結束地址爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"brk","attrs":{}},{"type":"text","text":"),即使它們都是brk/sbrk分配的,如果沒有釋放str3,只釋放了str1和str2,就算兩者加起來超過了M_TRIM_THRESHOLD,因爲str3的存在,str1和str2也不能立即歸還可以系統,即","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這些內存都被str3給“拴住”","attrs":{}},{"type":"text","text":"了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時,str1和str2的內存只是簡單的標記爲“未使用”,如果這兩處內存是相鄰的則會進行合併,這種算法也稱爲“夥伴內存算法(buddy memory allocation scheme)”。這種算法高速簡單,但同時也會生成碎片。包括","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"內碎片","attrs":{}},{"type":"text","text":"(一次分配內存不夠整頁,最後一頁剩下的空間)和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"外碎片","attrs":{}},{"type":"text","text":"(多次或者反覆分配造成中間的空閒頁太小不夠後續的一次分配)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上可以看出,在一定條件下,假如釋放了str3的內存,堆的大小是可以緊縮的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後我們以一張圖結束今天的主題,內存分配流程圖:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8c/8c8ffd4467c580076d5b5fca04747e57.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章