自製作業系統 (5)記憶體

2021-06-26 03:34:40 字數 4369 閱讀 1320

來到了lab2,記憶體管理,該實驗分為兩部分,第一部分為物理記憶體管理,第二部分為虛擬記憶體管理,本篇先描述lab1。

做本章實驗一定頭腦中要時刻清晰的記住兩個記憶體分布圖:物理記憶體分布圖以及虛擬記憶體分布圖。

物理記憶體的分布在前面的筆記中有介紹,這裡拷貝過來:

+------------------+  

| 32-bit |

| devices |

| |

/\/\/\/\/\/\/\/\/\/\

/\/\/\/\/\/\/\/\/\/\

| |

| unused |

| |

+------------------+

| |

| |

| extended memory |

|------------------|

| kernnel |

+------------------+

| bios rom |

+------------------+

| 16-bit devices, |

| expansion roms |

+------------------+

| vga display |

+------------------+

| |

| low memory |

|------------------|

|------------------|| bootmain stack |

+------------------+

虛擬記憶體分布參見memlayout.h檔案,這裡也拷貝過來:

好,記住這兩個圖然後開始做實驗。

1、實驗內容

實驗內容很簡單,只是讓你完成下列幾個函式

boot_alloc()

mem_init()

page_init()

page_alloc()

page_free()

mem_init()會呼叫你寫的函式,然後再呼叫check_page_free_list和check_page_alloc兩個函式來判斷你完成的函式的邏輯對不對,基本上通過驗證就沒問題了。

2、原理。

jos使用page資料結構來管理記憶體,乙個page代表乙個pgsize(4k)大小的物理頁面,page資料結構的定義在memlayout.h中,共有兩個域,第乙個域是pp_link,指向下乙個空閒page結構的指標,第二個域是乙個short整形,代表當前此物理頁面的引用次數,若為0則是沒有被引用也就是空閒頁面。

其次使用free_page_list維護乙個空閒物理記憶體的鍊錶,free_page_list本身就是乙個page指標,然後通過page結構體裡面的pp_link域構成空閒鍊錶。

再次乙個page代表4k,在pmap.c中的i386_memory_detect函式中檢測記憶體後,使用總物理記憶體/4k得到所需要的page數量,賦值給npages,換句話說npages代表所需page結構體的數量。

最後所有page在記憶體(物理記憶體)中的存放是連續的,存放於pages處,可以通過陣列的形式訪問各個page,而pages緊接於end符號之上,end符號是編譯器匯出符號,其值約為kernel的bss段在記憶體(虛擬記憶體)中的位址+bss段的段長,對應物理和虛擬記憶體布局也就是在kernel向上的緊接著的高位址部分連續分布著pages陣列。

除此之外jos提供page2pa,pa2page等函式可以進行page資料結構的指標向實體地址的轉換,或反轉換等。

3、具體函式實現

首先被呼叫的是boot_alloc(),它要在什麼都沒做好的情況下開闢出n位元組的空閒空間,並返回其首位址。

做法很簡單,end符號向上均為未使用空間,只要返回這些空間就行。

首先將end符號向上和4k位元組對齊(jos已經幫我們完成),然後將這個位址(也就是nextfree)加上你要分配的空間並依然4k位元組對齊,接著返回原先的nextfree即可。

[cpp]view plain

copy

char

* result;  

result=nextfree;  

nextfree+=roundup(n,pgsize);  

return

result;  

然後完成mem_init()中的部分**,可以看到mem_init()中首先檢查可用記憶體大小,然後呼叫boot_alloc()分配了乙個頁面給kern_pgdir,這個在part2中會用,現在沒啥用。

接下來需要我們給pages分配空間。通過以上原理分析,很容易得出**:

[cpp]view plain

copy

pages=(

struct

page*)boot_alloc(npages*

sizeof

(struct

page);  

接著完成page_init()。

在page_init()裡系統首先給我們初始化了pages陣列以及page_free_list,可以看到這個page_free_list指向了所有的page結構,換句話說此時認為所有的頁面都是空閒可分配的,這當然是不對的,所以就要從中把一些我們已經用的記憶體頁面從中剔除出去,這包括0位址向上的第乙個頁面(包括idt等),io hole(0xa0000--0x100000,包括vga display ,bios等),kernel位址之上的部分(kernel本身+kern_pgdir+pages)。巧合的是,io hole,和kernel之上部分是連續的位址,因為kernel就載入在0x100000處,所以其實只需要剔除兩塊位址,第一塊是0位址開始的第乙個頁面,第二塊就是io hole開始的向上的一組連續的頁面。

分析一下page_free_list的**邏輯,不難發現這個鍊錶是從pages陣列的末尾開始從高位址指向低位址,所以我們先計算出要剔除的page的位址,然後通過指標操作剔除即可。

首先剔除第乙個頁面,只需讓第二個頁面(下標為1)的pp_link域指向空,因為原本其指向的是第乙個頁面,而第乙個頁面的pp_link域指向空,也等價與pages[1].pp_link=pages[0].pp_link

其次剔除一組連續頁面,使用pgstart和pgend代表這組連續位址空間的首尾所在的page結構(所謂首是低位址,所謂尾是高位址)。

首位址也就是iophysmem所在位址,注意iophysmem已經是實體地址了,所以只需要使用pa2page得到page結構即可。

pgend是較高位的位址,首先將end符號位址轉化成實體地址(-kernbase),然後再加上剛才分配的kern_pgdir(乙個pgsize)和pages陣列所占用的空間即可。當然更嚴謹點應該4k位元組對齊的,不過不對其也能落在正確的4k範圍內,不影響程式正確性。

其次注意到pgstart和pgend這兩個page也是要剔除的,所以需要找到pgend的上乙個page,和pgstart的下乙個page,這兩個page應該是空閒的。因為所有page的組織是按陣列進行組織,所以只需要進行+1和-1的位址操作即可。

接著改變pp_link域,跳過中間的區域即可。

接下來是很簡單的page_alloc()。

**邏輯很簡單,從page_free_list頭剔除乙個page,然後改變page_free_list使其為其pp_link即可。

接著是page_free,直接上圖:

至此part1結束:

一點感想:

在使用者態程式設計反而覺得記憶體的分配是理所當然的,然而在os核心中寫相關**時,產生的一種很奇妙的感覺就是這個過程是由程式設計師自己掌控的,並且分配的結果會反而影響後面的**邏輯。

what an amazing experience !

自製作業系統 (2)

接上篇,檔案跳轉到了entry.s裡面,這是kernel的入口。首先面臨這麼乙個問題,kernel被載入到了什麼地方?回想上篇elf檔案的載入機制,以及objdump裡列印出的kernel資訊,可以看到,kernel的 段 text段 被載入到了0x100000的位置,也就是1m的位置,所以記憶體布...

自製作業系統 二

組合語言學習與makefile入門 2.helloos.nas程式核心部分。jmp指令 jump,跳轉。相當於c中的goto。mov指令 move,移動。理解mov指令就理解彙編一大半。指賦值的功能 把乙個東西移走了,他原來占用的位置不會空出 3.cpu的一種儲存電路 暫存器,相當於變數的功能。以下...

自製作業系統(二)

現在呢,大致流程寫在下面 首先,編寫彙編 檔名為myos.asm,所有 大致如下 下面是標準fat12格式軟盤專用 db 0xeb,0x4e,0x90 db myos ipl 啟動區名稱,必須8位元組 dw 512 每個扇區必須為512位元組 db 1 蔟必須為1個扇區 dw 1 fat的起始位置必...