操作系統內核學習之:內存管理
1. 內存地址基本概念
(1)邏輯地址
每一個邏輯地址由段和偏移量組成。
(2)線性地址(虛擬地址)
線性地址是一個32位無符號整數,可以用來表示4GB的地址,范圍為0x000000-0xfffffff
(3)物理地址
用于內存芯片級內存單元尋址,與cpu的地址引腳發送到內存總線上的電信號相對應。
(4)內存控制單元(MMU)

(5)硬件分段
(1)段選擇符:標示選擇哪個段,16bit
(2)段描述符:描述段的特征,8字節(64bit),可以描述一個段的具體信息(基地址,偏移等)
(3)段選擇符和段描述符的關系
[段選擇符和段描述符關系]
(4)邏輯地址與線性地址轉換

(6)Linux中的分段
由于段描述符的base都是從0開始,所以邏輯地址的偏移量字段的值與相應的線性地址的值總是一致的。
(1)每個CPU對應一個GDT
(2)用戶態進程共享或者會自定義LDT
(7)分頁
(1)頁框,指一組線性地址(線性地址空間);頁指包含在這組地址中的數據(對應的物理地址中的數據),每個頁框可以裝入一個頁。
(2)線性地址轉換的二級模式作用:提高效率,節省內存
(3)TLB:加速線性地址到物理地址的轉換,直接將線性地址對應的物理地址加到TLB中。,加上高速緩存,彌補了內存和CPU之間的速度差異。
2.地址映射關系
(1)內核態和用戶態
從進程的角度來看(每個進程都是一樣的),內存主要分為內核態和用戶態兩部分。

(2)地址轉換(二級模式為例)

3. 內存管理
(1)分頁機制
優點:可以靈活分配內存,不必一定要分配大塊的連續內存,但是分配時仍然傾向于分配連續內存
缺點:容易造成內存碎片,如何補救?伙伴系統。
(2)伙伴系統算法
伙伴算法主要解決的問題是外部分片問題。即頻繁地請求和釋放不同大小的一組連續頁框,必然導致在已分配頁框的塊內分散了許多小塊的空閑頁面,由此帶來的問題是,即使有足夠的空閑頁框可以滿足請求,但要分配一個大塊的連續頁框可能無法滿足請求。
伙伴算法(Buddy system)把所有的空閑頁框分為11個塊鏈表,每個塊鏈表中分別包含特定的連續頁框地址空間,比如第0個塊鏈表中每個節點包含大小為20(頁)的連續頁框,第1個塊鏈表中每個節點包含大小為21(頁)的連續地址,….,第10個塊鏈表中,每個鏈表元素代表4M的連續地址空間。每個鏈表中元素的個數在系統初始化時決定,在執行過程中,動態變化。
伙伴算法每次只能分配2的冪次頁的空間,比如一次分配1頁,2頁,4頁,8頁,…,1024頁(2^10)等等,每頁大小一般為4K,因此,伙伴算法最多一次能夠分配4M的內存空間。

申請和回收過程:
申請:比如,我要分配4(2^2)頁(16k)的內存空間,算法會先從free_area[2]中查看nr_free是否為空,如果有空閑塊,則從中分配,如果沒有空閑塊,就從它的上一級free_area[3](每塊32K)中分配出16K,并將多余的內存(16K)加入到free_area[2]中去。如果free_area[3]也沒有空閑,則從更上一級申請空間,依次遞推,直到free_area[max_order],如果頂級都沒有空間,那么就報告分配失敗。
回收:回收是申請的逆過程,當釋放一個內存塊時,先在其對于的free_area鏈表中查找是否有伙伴存在,如果沒有伙伴塊,直接將釋放的塊插入鏈表頭。如果有或板塊的存在,則將其從鏈表摘下,合并成一個大塊,然后繼續查找合并后的塊在更大一級鏈表中是否有伙伴的存在,直至不能合并或者已經合并至最大塊2^10為止。
內核試圖將大小為b的一對空閑塊(一個是現有空閑鏈表上的,一個是待回收的),合并為一個大小為2B的單獨塊,如果它成功合并所釋放的塊,它會試圖合并2b大小的塊。
(3)slab分配技術
作用:主要是解決在同一頁框如何分配小內存(遠不足一頁)問題。
思想:slab分配器的基本思想是將內核中經常使用的對象放到高速緩存中,并且由系統保持為初始的可利用狀態。比如進程描述符,內核中會頻繁對此數據進行申請和釋放。當一個新進程創建時,內核會直接從slab分配器的高速緩存中獲取一個已經初始化了的對象;當進程結束時,該結構所占的頁框并不被釋放,而是重新返回slab分配器中。如果沒有基于對象的slab分 配器,內核將花費更多的時間去分配、初始化以及釋放一個對象。
slab分配器有以下三個基本目標:
1.減少伙伴算法在分配小塊連續內存時所產生的內部碎片;
2.將頻繁使用的對象緩存起來,減少分配、初始化和釋放對象的時間開銷。
3.通過著色技術調整對象以更好的使用硬件高速緩存;

**注意:伙伴系統和slab技術的目的都是為了防止內存碎片。內存碎片又分為外部碎片和內部碎片之說,所謂內部碎片(頁內)是說系統為了滿足一小段內存區(連續)的需要,不得不分配了一大區域連續內存給它,從而造成了空間浪費;外部碎片(頁之間)是指系統雖有足夠的內存,但卻是分散的碎片,無法滿足對大塊“連續內存”的需求。slab分配器使得一個頁面內包含的眾多小塊內存可獨立被分配使用,避免了內部分片,節約了空閑內存。伙伴關系把內存塊按大小分組管理,一定程度上減輕了外部分片的危害,因為頁框分配不在盲目,而是按照大小依次有序進行,不過伙伴關系只是減輕了外部分片,但并未徹底消除
(4)內核虛擬內存(vmalloc)
作用:將多個不連續的物理頁通過虛擬轉換形成在邏輯上連續的頁,可以解決外部碎片問題
應用場景:用伙伴系統分配10 x 4KB的數據時,會去16 x 4KB的空閑列表里面去找(這樣得到的物理內存是連續的),但很有可能系統里面有內存,但是伙伴系統分配不出來,因為他們被分割成小的片段。那么,vmalloc就是要用這些碎片來拼湊出一個大內存,相當于收集一些“邊角料”,組裝成一個成品后“出售”。

4. 缺頁異常
通過寫實復制(cow)技術,實現將物理頁真正的掛載在頁框中。只有當進程真正的需要物理頁時才會產生缺頁異常,從而分配物理頁。在實際需要某個虛擬內存區域的數據之前,和物理內存之間的映射關系不會建立。如果進程訪問的虛擬地址空間部分尚未與頁幀關聯,處理器自動引發一個缺頁異常。
盡管每個進程獨立擁有3GB的可訪問地址空間,但是這些資源都是內核開出的空頭支票,也就是說進程手握著和自己相關的一個個虛擬內存區域(vma),但是這些虛擬內存區域并不會在創建的時候就和物理頁框掛鉤,由于程序的局部性原理,程序在一定時間內所訪問的內存往往是有限的,因此內核只會在進程確確實實需要訪問物理內存時才會將相應的虛擬內存區域與物理內存進行關聯(為相應的地址分配頁表項,并將頁表項映射到物理內存),也就是說這種缺頁異常是正常的,而第一種缺頁異常是不正常的,內核要采取各種可行的手段將這種異常帶來的破壞減到最小。
缺頁異常的處理函數為do_page_fault(),該函數是和體系結構相關的一個函數,缺頁異常的來源可分為兩種,一種是內核空間(訪問了線性地址空間的第4個GB),一種是用戶空間(訪問了線性地址空間的0~3GB),以X86架構為例,先來看內核空間異常的處理。

解釋:
兩種情況:
- 觸發異常的線性地址處于用戶空間的vma中,但還未分配物理頁,如果訪問權限OK的話內核就給進程分配相應的物理頁了
2、觸發異常的線性地址不處于用戶空間的vma中,這種情況得判斷是不是因為用戶進程的棧空間消耗完而觸發的缺頁異常,如果是的話則在用戶空間對棧區域進行擴展,并且分配相應的物理頁,如果不是則作為一次非法地址訪問來處理,內核將終結進程
說明:本文部分圖片引用自以下參考資料,如有侵權,請告知。
參考資料:
[1]http://www.sohu.com/a/216719739_236714
[2]http://www.rzrgm.cn/cherishui/p/4246133.html
[3]https://my.oschina.net/fileoptions/blog/968320
[4]https://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/
[5]http://soft.chinabyte.com/os/18/12408018.shtml
[6]https://cloud.tencent.com/developer/article/1432751
[7]深入理解Linux內核,第2/8章

浙公網安備 33010602011771號