操作系统实验二:物理内存管理系统
操作系統實驗二:物理內存管理系統
- 一、 實驗目的
- 二、 實驗內容
- 三、 實驗準備
- 【實驗概述】
- 【關鍵數據結構】
- 【執行流程】
- 四、 實驗步驟
- (一) 練習0:填寫已有實驗
- (二) 練習1:實現 first-fit 連續物理內存分配算法
- (三) 練習2:實現尋找虛擬地址對應的頁表項
- (四) 練習3:釋放某虛地址所在的頁并取消對應二級頁表項的映射
- (五) 擴展練習1:在ucore中實現buddy system(未能完全完成)
- (六) 測試
- 五、 總結
- 六、 附錄
一、 實驗目的
二、 實驗內容
三、 實驗準備
在開始練習之前首先需要進行實驗的大致流程以及關鍵數據結構進行學習與理解;
【實驗概述】
本次實驗主要完成ucore內核對物理內存的管理工作。
參考ucore總控函數kern_init的代碼,可以清楚地看到在調用完成物理內存初始化的pmm_init函數之前和之后,是已有lab1實驗的工作。
其實lab2有些修改,ucore主要有三個方面的擴展。
- boot/bootasm.S:增加了對計算機系統中物理內存布局的探測功能;
- kern/init/entry.S:根據臨時段表重新暫時建立好新的段空間,為進行分頁做好準備。
- kern/mm/default_pmm.[ch]:提供基本的基于鏈表方法的物理內存管理(分配單位為頁,即4096字節);
首先,bootloader的工作有增加,在bootloader中,完成了對物理內存資源的探測工作,讓ucore kernel在后續執行中能夠基于bootloader探測出的物理內存情況進行物理內存管理初始化工作。
其次,bootloader不像lab1那樣,直接調用kern_init函數,而是先調用位于lab2/kern/init/entry.S中的kern_entry函數。kern_entry函數的主要任務是為執行kern_init建立一個良好的運行環境。完成這些工作后,才調用kern_init函數。
kern_init函數在完成一些輸出并對lab1實驗結果的檢查后,將進入物理內存管理初始化的工作,即調用pmm_init函數完成物理內存的管理,是lab2的內容。
【關鍵數據結構】
首先看一下Page的結構:
struct Page { int ref; // page frame's reference counter uint32_t flags; // array of flags that describe the status of the page frame unsigned int property; // the num of free block, used in first fit pm manager list_entry_t page_link; // free list link }; typedef struct list_entry list_entry_t; struct list_entry {struct list_entry *prev, *next; };這里使用的雙向鏈表借鑒了linux內核的雙向鏈表結構,鏈表結構不包含傳統數據域data,而是在具體的數據結構中包含鏈表節點。在本實驗中,使用如下結構對頁進行總體的管理。在Default.c中含鏈表free_area_t的結構:
free_area_t free_area; //global page manager #define free_list (free_area.free_list) #define nr_free (free_area.nr_free) /* free_area_t - maintains a doubly linked list to record free (unused) pages */ typedef struct {list_entry_t free_list; // the list headerunsigned int nr_free; // # of free pages in this free list } free_area_t;free_list是整個雙向鏈表的頭節點,同時nr_free表示空閑頁的數量。
下圖是整個空閑頁雙向鏈表的簡易示意圖(將鏈表和數據域隔離)
【執行流程】
內存管理相關的總體控制函數是pmm_init函數,它完成的主要工作包括:
①初始化物理內存頁管理器框架pmm_manager;
②建立空閑的page鏈表,這樣就可以分配以頁(4KB)為單位的空閑內存了;
③檢查物理內存頁分配算法;
④為確保切換到分頁機制后,代碼能夠正常執行,先建立一個臨時二級頁表;
⑤建立一一映射關系的二級頁表;
⑥使能分頁機制;
⑦從新設置全局段描述符表;
⑧取消臨時二級頁表;
⑨檢查頁表建立是否正確;
⑩通過自映射機制完成頁表的打印輸出(這部分是擴展知識)
四、 實驗步驟
(一) 練習0:填寫已有實驗
將LAB1中完成的代碼(不包含拓展練習)移植到了lab2的框架中,涉及到的文件為kern/debug/kdebug.c和kern/trap/trap.c,具體內容已在LAB1報告中進行說明,此處不再贅述;
(二) 練習1:實現 first-fit 連續物理內存分配算法
在實現first fit 內存分配算法的回收函數時,要考慮地址連續的空閑塊之間的合并操作。提示:在建立空閑頁塊鏈表時,需要按照空閑頁塊起始地址來排序,形成一個有序的鏈表。
在ucore中采用面向對象編程的思想,將物理內存管理的內容抽象成若干個特定的函數,并且使用結構體pmm_manager來將這些函數的指針封裝起來,使得具體使用到物理內存管理所提供的服務的時候,只需要調用已經初始化完成的pmm_manager的實例中的函數指針即可,這樣就實現了將物理內存管理的具體實現與ucore其他部分隔離開來的效果。
其中,上述若干個封裝在pmm_manager中的提供物理內存管理服務的函數分別如下:
init:對物理內存管理器的初始化; init_memmap:對管理的空閑頁的數據進行初始化;
alloc_pages:申請分配指定數量的物理頁; free_pages: 申請釋放若干指定物理頁;
nr_free_pages:查詢當前的空閑頁總數; check: 對物理內存管理器進行測試;
查看default_pmm.c文件可以發現,最終ucore中所使用的物理內存管理器中的函數指針分別指向了default_init, default_init_memmap等若干函數,在lab中,通過對這些函數的實現進行修改來實現first-fit 連續內存分配算法;
查看default_init中的內容,發現僅有對空閑內存塊鏈表的初始化以及將總空閑數目置零的操作,這是與具體物理內存分配算法無關的,因此直接使用默認的函數實現即可;
然后分析default_init_memmap函數,該函數的具體作用為對最初的一整塊未被占用的物理內存空間中的每一頁所對應的Page結構(用于描述這些頁的狀態)進行初始化,考慮到相鄰的物理頁對應的Page結構在內存上也是同樣相鄰的,因此可以直接通過第一個空閑物理頁對應的Page結構加上一個偏移量的方式來訪問所有的空閑的物理頁的Page結構,具體初始化方式為:
遍歷所有空閑物理頁的Page結構,將Page結構的描述空閑塊的數目的成員變量置零(因此該成員變量只有在整個空閑塊的第一個Page中才有意義),然后清空這些物理頁的引用計數,然后通過設置flags的位的方式將其標記為空閑,實現代碼如下:
接下來對空閑塊的第一個頁的Page結構進行初始化,具體實現為將其表示空閑塊大小的成員變量設置為作為參數傳入的空閑塊大小(單位為頁),然后更新存儲所有空閑頁數量,然后將這個空閑塊插入到空閑內存塊鏈表中(只需要將第一個Page的page_link插入即可);具體的代碼實現如下所示:
base->property = n; nr_free += n; list_add(&free_list, &(base->page_link));}接下來考慮實現分配空閑頁函數default_alloc_pages:該函數的具體功能為分配指定頁數的連續空閑物理空間,并且將第一頁的Page結構的指針作為結果返回;
3.1. 首先對參數進行合法性檢查,并且查詢總的空閑物理頁數目是否足夠進行分配,
如果不足夠進行分配,直接返回NULL,表示分配失敗;
3.2. 接著從頭開始遍歷保存空閑物理內存塊的鏈表(按照物理地址的從小到大順序),如果找 到某一個連續內存塊的大小不小于當前需要的連續內存塊大小,則說明可以進行成功分 配(第一個滿足條件的空閑內存塊),代碼實現如下:
3.3. 接下來考慮對獲得的滿足條件的空閑內存塊進行處理,如果該內存塊的大小大于需要的 內存大小,則將空閑內存塊分裂成兩塊,物理地址較小的一塊分配出來進行使用(大小 恰好為需要的物理內存的大小),而物理地址較大的那一塊進行以下操作
①對第一個Page的page_link中property設置為原先的空閑塊大小減掉分配的大小
②將這個分裂出來的空閑塊插入到空閑塊鏈表中
于此同時,對分配出去的物理內存的每一個的描述信息(即對應的Page結構)修改flags成員變量來將這些Page標記為非空閑,最后將原始空閑塊在空閑塊鏈表中刪除掉,并且更新表示總空閑頁數量的全局變量;最后用于表示分配到的物理內存的Page結構指針返回;
接下來考慮釋放占用的內存塊的函數default_free_pages,該函數的具體功能為釋放指定的某一物理頁開始的若干個連續物理頁,并且完成first-fit算法中需要的若干信息的維護,具體的實現如下所示:
4.1. 首先考慮遍歷需要釋放的物理頁的描述信息(即對應的Page結構),對其進行更新;
①判斷原先這些物理頁是否真的被占用了,如果釋放未被占用的物理頁,這說明出現了異常情況;
②設置flags來將這些物理頁標記為空閑;
③清空這些物理頁的引用計數;
接下來將這一新的空閑塊插入到空閑塊鏈表中:
base->property = n; // 設置空閑塊大小 list_entry_t *le = list_next(&free_list); for (; le != (&free_list) && le < (&(base->page_link)); le = list_next(le)); // 尋找新的空閑塊在空閑塊鏈表中應當處于的位置 list_add_before(le, &(base->page_link)); // 將空閑塊插入到鏈表中 nr_free += n; // 更新空閑物理頁總量最后需要對空閑塊跟其相鄰的空閑塊(如果存在的話)進行合并,分別是檢查能與前一項合并與后一項合并的操作,我的合并方式是循環遍歷鏈表,進行空閑塊相鄰的判斷,相鄰則合并;
while (le != &free_list) { // 迭代空閑鏈表中的每一個節點// 獲得節點對應的Page結構p = le2page(le, page_link);le = list_next(le);if (base + base->property == p) {// 如果尾部正好能和下一個連上則合并base->property += p->property;ClearPageProperty(p);list_del(&(p->page_link));}else if (p + p->property == base) { // 如果頭部與上一個連上則合并p->property += base->property;ClearPageProperty(base);base = p;list_del(&(p->page_link));} }在本實驗中所實現的first fit算法仍然有著相當的改進空間;
改進方向就在于時間復雜度上,每次查詢第一塊符合條件的空閑內存塊時,最壞情況需要找遍整個鏈表,這樣的話時間復雜度是O(N),N表示當前的鏈表大小,考慮針對時間效率的優化方式如下:
如果采用平衡二叉樹結構來取代簡單的鏈表結構來維護空閑塊,其中按照中序遍歷得到的空閑塊序列的物理地址恰好按照從小到大排序;每個二叉樹節點上維護該節點為根的子樹上的最大的空閑塊的大小;通過二叉樹的查詢算法可以查找到物理地址最小的能夠滿足條件的空閑地址塊,然后進行刪除以及新的分裂出來的空閑塊的插入等操作;
平衡二叉樹每次查詢符合條件的第一塊物理空閑塊的時間復雜度為O(log N),對比原先的O(N)有很大進步,但是這里實現較為復雜;
(三) 練習2:實現尋找虛擬地址對應的頁表項
本練習的要求為補全pmm.c中的get_pte函數,該函數的主要功能為根據給定的page directory以及線性地址,查詢出該linear address對應的page table entry,并且根據輸入參數要求判斷是否創建不存在的頁表;
實現的過程:
①此函數的作用是向上提供一個幾乎透明的操作,返回指定 linear address 對應的 page table entry.注意這個函數是單純的獲取其地址,得到 page dir 的 index是 PDX(la),于是對應的 pde 是 pgdir[PDX(la)];
②考察 pde 具體的結構,高 20 位是 page table 地址.因此pgdir[PSX(la)]& ~0xFFF即可得到 page table 的物理地址,在取值之前,首先要判斷存在位PTE_P, 如果存在,說明之前已經初始化過了 pd,只需計算la對應的頁表項即可,如果不存在,說明當前頁目錄項除了 PTE_P 外都是空的。此時需要新申請一塊物理內存,作為新的二級頁表,但如果 create=0 的話就直接返回 NULL 就行了。
2.1. 請描述頁目錄項(Pag Director Entry)和頁表(Page Table Entry)中每個組成部分的含義和以及對ucore而言的潛在用處。
2.1.1. PDE(頁目錄項)的具體組成如圖所示;描述每一個組成部分的含義如下:
前20位表示4K對齊的該PDE對應的頁表起始位置(物理地址,該物理地址的高20位即PDE中的高20位,低12位為0);
第9-11位未被CPU使用,可保留給OS使用;
接下來的第8位可忽略;
第7位用于設置Page大小,0表示4KB;
第6位恒為0;
第5位用于表示該頁是否被使用過;
第4位設置為1則表示不對該頁進行緩存;
第3位設置是否使用write through緩存寫策略;
第2位表示該頁的訪問需要的特權級;
第1位表示是否允許讀寫;
第0位為該PDE的存在位;
2.1.2. 接下來描述頁表項(PTE)中的每個組成部分的含義,具體組成如圖所示:
高20位與PDE相似的,用于表示該PTE指向的物理頁的物理地址;
9-11位保留給OS使用;
7-8位恒為0;
第6位表示該頁是否為dirty,即是否需要在swap out的時候寫回外存;
第5位表示是否被訪問;
3-4位恒為0;
0-2位分別表示存在位、是否允許讀寫、訪問該頁需要的特權級;
2.1.3. 潛在用處:
可以發現無論是PTE還是TDE,都具有著一些保留的位供操作系統使用,也就是說ucore可以利用這些位來完成一些其他的內存管理相關的算法;比如可以在這些位里保存最近一段時間內該頁的被訪問的次數,用于輔助近似地實現虛擬內存管理中的換出策略的LRU之類的算法;也就是說這些保留位有利于OS進行功能的拓展;
2.2. 如果ucore執行過程中訪問內存,出現了頁訪問異常,請問硬件要做哪些事情?
當ucore執行過程中出現了頁訪問異常,硬件需要完成的事情分別如下:
- 將發生錯誤的線性地址保存在cr2寄存器中;
- 在中斷棧中依次壓入EFLAGS,CS, EIP,以及頁訪問異常碼error code,如果page fault是發生在用戶態,則還需要先壓入ss和esp,并且切換到內核棧;
- 根據中斷描述符表查詢到對應page fault的ISR,跳轉到對應的ISR處執行,接下 來將由軟件進行page fault處理;
對于大多數操作系統來說,處理頁故障的方法入下:
(四) 練習3:釋放某虛地址所在的頁并取消對應二級頁表項的映射
當釋放一個包含某虛地址的物理內存頁時,需要讓對應此物理內存頁的管理數據結構Page做相關的清除處理,使得此物理內存頁成為空閑;另外還需把表示虛地址與物理地址對應關系的二級頁表項清除。
接下來具體代碼實現來說明釋放某虛地址所在的頁并且取消PTE表項的映射的具體實現;
①檢查頁表項pte的PTE_P的值,若為0,則說明物理頁不存在,此時直接返回
②首先將頁表項的低12位清零得到物理頁的起始地址,并據此得到對應的Page結構,將Page的ref字段減1,表明取消當前邏輯地址到此物理地址的映射。
③然后,判斷Page的ref的值是否為0,若為0,則說明此時沒有任何邏輯地址被映射到此物理地址,當前物理頁已沒有程序使用,因此調用free_page函數回收此物理頁;若不為0,則說明此時仍有至少一個邏輯地址被映射到此物理地址,因此不需回收此物理頁。
④將對應的頁表項pte清零,表明此邏輯地址無對應的物理地址。
⑤調用tlb_invalidate函數去使能TLB中當前邏輯地址對應的條目。
回答如下問題:
2.1. 數據結構Page的全局變量(其實是一個數組)的每一項與頁表中的頁目錄項和頁表項有無對應關系?如果有,其對應關系是啥?
存在對應關系;
由于頁表項中存放著對應的物理頁的物理地址,因此可以通過這個物理地址來獲取到對應到的Page數組的對應項,具體做法為將物理地址除以一個頁的大小,然后乘上一個Page結構的大小獲得偏移量,使用偏移量加上Page數組的基地址皆可以或得到對應Page項的地址;
可通過該函數從頁表項中得到物理page結構;
2.2. 如果希望虛擬地址與物理地址相等,則需要如何修改lab2,完成此事? 鼓勵通過編程來具體完成這個問題。
由于在完全啟動了ucore之后,虛擬地址和線性地址相等,都等于物理地址加上0xc0000000,如果需要虛擬地址和物理地址相等,可以考慮更新gdt,更新段映射,使得virtual address = linear address - 0xc0000000,這樣的話就可以實現virtual address = physical address;
通過ld工具生成的ucore的其實虛擬地址為0xC01000000開始,而bootloader把ucore放在了起始地址為0x001000000的物理內存空間,那么首先將KERNBASE從0xC000000改為0x00000000,原因在于沒有開啟頁地址轉換前,內核邏輯地址經過一次段地址轉換直接得到物理地址。
#define KERNBASE 0xC0000000
phy addr + KERNBASE = virtual addr
所以,KERNBASE = 0時,phy addr = virtual addr。
(五) 擴展練習1:在ucore中實現buddy system(未能完全完成)
Buddy System算法把系統中的可用存儲空間劃分為存儲塊(Block)來進行管理, 每個存儲塊的大小必須是2的n次冪(Pow(2, n)), 即1, 2, 4, 8, 16, 32, 64, 128…
參考伙伴分配器的一個極簡實現, 在ucore中實現buddy system分配算法;具體的代碼見附錄!
5.1. 整體思想
分配器的整體思想是,通過一個數組形式的完全二叉樹來監控管理內存,二叉樹的節點用于標記相應內存塊的使用狀態,高層節點對應大的塊,低層節點對應小的塊,在分配和釋放中我們就通過這些節點的標記屬性來進行塊的分離合并。如圖所示,假設總大小為16單位的內存,我們就建立一個深度為5的滿二叉樹,根節點從數組下標[0]開始,監控大小16的塊;它的左右孩子節點下標[12],監控大小8的塊;第三層節點下標[36]監控大小4的塊……依此類推。
5.2. 數據結構
這里的成員size表明管理內存的總單元數目(測試用例中是32),成員longest就是二叉樹的節點標記,表明所對應的內存塊的空閑單位。
5.3. 分配內存alloc
①尋找大小合適的內存塊(大于等于并且最接近2的冪,比如需要27,實際分配32)
②如果找到了,分配給應用程序。
③如果沒找到,分出合適的內存塊。
Ⅰ對半分離出高于所需大小的空閑內存塊
Ⅱ如果分到最低限度,分配這個大小。
Ⅲ回溯到步驟①(尋找合適大小的塊)
Ⅳ重復該步驟直到一個合適的塊
整個分配器的大小就是滿二叉樹節點數目,即所需管理內存單元數目的2倍。一個節點對應4個字節,longest記錄了節點所對應的的內存塊大小。
內存分配的alloc中,入參是分配器指針和需要分配的大小,返回值是內存塊索引。alloc函數首先將size調整到2的冪大小,并檢查是否超過最大限度。然后進行適配搜索,深度優先遍歷,當找到對應節點后,將其longest標記為0,即分離適配的塊出來,并轉換為內存塊索引offset返回,依據二叉樹排列序號,比如內存總體大小32,我們找到節點下標[8],內存塊對應大小是4,則offset = (8+1)*4-32 = 4,那么分配內存塊就從索引4開始往后4個單位。
在函數返回之前需要回溯,因為小塊內存被占用,大塊就不能分配了,比如下標[8]標記為0分離出來,那么其父節點下標[0]、[1]、[3]也需要相應大小的分離。將它們的longest進行折扣計算,取左右子樹較大值,下標[3]取4,下標[1]取8,下標[0]取16,表明其對應的最大空閑值。
5.4. 釋放內存free
上圖中,首先我們假設我們一個內存塊有1024K,當我們需要給A分配70K內存的時候,
①我們發現1024K的一半大于70K,然后我們就把1024K的內存分成兩半,一半512K。
②然后我們發現512K的一半仍然大于70K,于是我們再把512K的內存再分成兩半,一半是128K。
③此時,我們發現128K的一半小于70K,于是我們就分配為A分配128K的內存。
④后面的,B,C,D都這樣,而釋放內存時,則會把相鄰的塊一步一步地合并起來(合并也必需按分裂的逆操作進行合并)。
我們可以看見,這樣的算法,用二叉樹這個數據結構來實現再合適不過了。
在內存釋放的free接口,我們只要傳入之前分配的內存地址索引,并確保它是有效值。之后就跟alloc做反向回溯,從最后的節點開始一直往上找到longest為0的節點,即當初分配塊所適配的大小和位置。
將longest恢復到原來滿狀態的值。繼續向上回溯,檢查是否存在合并的塊,依據就是左右子樹longest的值相加是否等于原空閑塊滿狀態的大小,如果能夠合并,就將父節點longest標記為相加的和。
5.5. 在ucore中測試
首先,我們分析ucore的執行過程;
從練習1中可知,內存的分配函數是在default_pmm.c中實現的,default_pmm.c中各個函數的定義:
該實現manager是在pmm.c中調用的:
所以實現buddy system時,要完成buddy.c和buddy.h;
buddy.h中的內容參考了default_pmm.h實現:
并且將pmm.c中的調用改為:
即調用buddy.h中的buddy_pmm_manager;
而buddy_pmm_manager是在buddy,c中實現的:
最后通過make qemu來測試結果:
測試正確;
(六) 測試
最終完成所有練習之后,可以通過make grade的所有測試,最終實驗結果如下所示:
make grade:
make qemu:
可以看出上述補充代碼部分正確。
五、 總結
本實驗是關于物理內存管理,主要從物理內存和建立頁表兩個方面設計實驗,首先了解了如何發現物理內存,如何建立物理內存的初步管理等等內容;了解頁表的相關操作總體來說有些難,因為涉及到了很多的新數據結構,我們需要很好的理解掌握各個文件中的相關函數,才能理解實驗的內涵。
通過練習1、2、3,了解了基于段頁式內存地址的轉換機制,頁表的建立和使用方法和物理內存的管理方法。比如在練習1中直觀地看到first fit算法的實現過程,盡管在理論課上學過,而且感覺理論上比較簡單,但實際實現起來還是有點難度的。總體上來說,三個練習的工作量不小,而且也并不是很簡單就能完成的,不過好在注釋十分詳細,跟著注釋一步一步走,然后查閱一些相關的資料后還是能夠完成的。
通過這個實驗能夠深入的理解段頁式內存管理機制,對課本中的知識點有了深入的理解體會。總的來說,lab2的收獲還是很大,而且正如實驗指導書最開始說的,lab1和lab2比較困難,但通過lab1和lab2后,對計算機原理中的中斷、段頁表機制、特權級等的理解會更深入。對后面的學習有很大的幫助。
六、 附錄
主要是buddy.c中的代碼:
#include <pmm.h>
#include <list.h>
#include <string.h>
#include <default_pmm.h>
#include <buddy.h>
//來自參考資料的一些宏定義
#define LEFT_LEAF(index) ((index) * 2 + 1)
#define RIGHT_LEAF(index) ((index) * 2 + 2)
#define PARENT(index) ( ((index) + 1) / 2 - 1)
#define IS_POWER_OF_2(x) (!((x)&((x)-1)))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define UINT32_SHR_OR(a,n) ((a)|((a)>>(n)))//右移n位
#define UINT32_MASK(a) (UINT32_SHR_OR(UINT32_SHR_OR(UINT32_SHR_OR(UINT32_SHR_OR(UINT32_SHR_OR(a,1),2),4),8),16))
//大于a的一個最小的2^k
#define UINT32_REMAINDER(a) ((a)&(UINT32_MASK(a)>>1))
#define UINT32_ROUND_DOWN(a) (UINT32_REMAINDER(a)?((a)-UINT32_REMAINDER(a)):(a))//小于a的最大的2^k
static unsigned fixsize(unsigned size) {
size |= size >> 1;
size |= size >> 2;
size |= size >> 4;
size |= size >> 8;
size |= size >> 16;
return size+1;
}
struct buddy2 {
unsigned size;//表明管理內存
unsigned longest;
};
struct buddy2 root[80000];//存放二叉樹的數組,用于內存分配
free_area_t free_area;
#define free_list (free_area.free_list)
#define nr_free (free_area.nr_free)
struct allocRecord//記錄分配塊的信息
{
struct Page* base;
int offset;
size_t nr;//塊大小
};
struct allocRecord rec[80000];//存放偏移量的數組
int nr_block;//已分配的塊數
static void buddy_init()
{
list_init(&free_list);
nr_free=0;
}
//初始化二叉樹上的節點
void buddy2_new( int size ) {
unsigned node_size;
int i;
nr_block=0;
if (size < 1 || !IS_POWER_OF_2(size))
return;
root[0].size = size;
node_size = size * 2;
for (i = 0; i < 2 * size - 1; ++i) {
if (IS_POWER_OF_2(i+1))
node_size /= 2;
root[i].longest = node_size;
}
return;
}
//初始化內存映射關系
static void
buddy_init_memmap(struct Page base, size_t n)
{
assert(n>0);
struct Page p=base;
for(;p!=base + n;p++)
{
assert(PageReserved§);
p->flags = 0;
p->property = 1;
set_page_ref(p, 0);
SetPageProperty§;
list_add_before(&free_list,&(p->page_link));
}
nr_free += n;
int allocpages=UINT32_ROUND_DOWN(n);
buddy2_new(allocpages);
}
//內存分配
int buddy2_alloc(struct buddy2* self, int size) {
unsigned index = 0;//節點的標號
unsigned node_size;
unsigned offset = 0;
if (self==NULL)//無法分配
return -1;
if (size <= 0)//分配不合理
size = 1;
else if (!IS_POWER_OF_2(size))//不為2的冪時,取比size更大的2的n次冪
size = fixsize(size);
if (self[index].longest < size)//可分配內存不足
return -1;
for(node_size = self->size; node_size != size; node_size /= 2 ) {
if (self[LEFT_LEAF(index)].longest >= size)
{
if(self[RIGHT_LEAF(index)].longest>=size)
{
index=self[LEFT_LEAF(index)].longest <= self[RIGHT_LEAF(index)].longest? LEFT_LEAF(index):RIGHT_LEAF(index);
//找到兩個相符合的節點中內存較小的結點
}
else
{
index=LEFT_LEAF(index);
}
}
else
index = RIGHT_LEAF(index);
}
self[index].longest = 0;//標記節點為已使用
offset = (index + 1) * node_size - self->size;
while (index) {
index = PARENT(index);
self[index].longest =
MAX(self[LEFT_LEAF(index)].longest, self[RIGHT_LEAF(index)].longest);
}
//向上刷新,修改先祖結點的數值
return offset;
}
static struct Page*
buddy_alloc_pages(size_t n){
assert(n>0);
if(n>nr_free)
return NULL;
struct Page* page=NULL;
struct Page* p;
list_entry_t *le=&free_list,*len;
rec[nr_block].offset=buddy2_alloc(root,n);//記錄偏移量
int i;
for(i=0;i<rec[nr_block].offset+1;i++)
le=list_next(le);
page=le2page(le,page_link);
int allocpages;
if(!IS_POWER_OF_2(n))
allocpages=fixsize(n);
else
{
allocpages=n;
}
//根據需求n得到塊大小
rec[nr_block].base=page;//記錄分配塊首頁
rec[nr_block].nr=allocpages;//記錄分配的頁數
nr_block++;
for(i=0;i<allocpages;i++)
{
len=list_next(le);
p=le2page(le,page_link);
ClearPageProperty§;
le=len;
}//修改每一頁的狀態
nr_free-=allocpages;//減去已被分配的頁數
page->property=n;
return page;
}
void buddy_free_pages(struct Page* base, size_t n) {
unsigned node_size, index = 0;
unsigned left_longest, right_longest;
struct buddy2* self=root;
list_entry_t le=list_next(&free_list);
int i=0;
for(i=0;i<nr_block;i++)//找到塊
{
if(rec[i].base==base)
break;
}
int offset=rec[i].offset;
int pos=i;//暫存i
i=0;
while(i<offset)
{
le=list_next(le);
i++;
}
int allocpages;
if(!IS_POWER_OF_2(n))
allocpages=fixsize(n);
else
{
allocpages=n;
}
assert(self && offset >= 0 && offset < self->size);//是否合法
node_size = 1;
index = offset + self->size - 1;
nr_free+=allocpages;//更新空閑頁的數量
struct Page p;
self[index].longest = allocpages;
for(i=0;i<allocpages;i++)//回收已分配的頁
{
p=le2page(le,page_link);
p->flags=0;
p->property=1;
SetPageProperty§;
le=list_next(le);
}
while (index) {//向上合并,修改先祖節點的記錄值
index = PARENT(index);
node_size *= 2;
}
for(i=pos;i<nr_block-1;i++)//清除此次的分配記錄
{
rec[i]=rec[i+1];
}
nr_block–;//更新分配塊數的值
}
static size_t
buddy_nr_free_pages(void) {
return nr_free;
}
//以下是一個測試函數
static void
buddy_check(void) {
struct Page *p0, *A, *B,*C,*D;
p0 = A = B = C = D =NULL;
}
const struct pmm_manager buddy_pmm_manager = {
.name = “buddy_pmm_manager”,
.init = buddy_init,
.init_memmap = buddy_init_memmap,
.alloc_pages = buddy_alloc_pages,
.free_pages = buddy_free_pages,
.nr_free_pages = buddy_nr_free_pages,
.check = buddy_check,
};
總結
以上是生活随笔為你收集整理的操作系统实验二:物理内存管理系统的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工信部电信投诉网站入口
- 下一篇: CWM模式(卡刷)教程