作業系統真象還原實驗記錄之實驗十三:記憶體管理系統
對應書P374 8.3節
1.相關基礎知識總結
1.1 記憶體池規劃
通過分頁實驗已經很清楚,虛拟位址——實位址的映射規則依賴的是頁表
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL90TUNh3ZE1UNrRVZ0EjMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLwMzN1UzNyETMyEjNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
對于所有程序和核心來說,都有自己獨立的虛拟記憶體池,獨立的頁表。這就是為什麼每個程序都有自己的獨立的4GB,因為即使兩個程序通路同一個虛拟位址,不同的頁表會得到不同的實體位址映射。
2.實驗代碼
2.1 實作字元串操作函數 string.c
string.h
#ifndef __LIB_STRING_H
#define __LIB_STRING_H
#include "stdint.h"
#define NULL ((void*)0)
void memset(void* dst_, uint8_t value, uint32_t size);
void memcpy(void* dst_, const void* src_, uint32_t size);
int memcmp(const void* a_, const void* b_, uint32_t size);
char* strcpy(char* dst_, const char* src_);
uint32_t strlen(const char* str);
int8_t strcmp (const char *a, const char *b);
char* strchr(const char* string, const uint8_t ch);
char* strrchr(const char* string, const uint8_t ch);
char* strcat(char* dst_, const char* src_);
uint32_t strchrs(const char* filename, uint8_t ch);
#endif
2.1.1 針對size個位元組的操作函數
以位元組為機關就是以字元為機關,記憶體是一個位元組對應一個記憶體位址編号的,
比如一個char a 或者一個uint8_t a。a均代表八位比特的數值(或ASCII碼)。 *a代表這個ASCII碼的記憶體位址。++(*a)代表a的下一個記憶體位址編号
#include "string.h"
#include "global.h"
#include "debug.h"
/* 将dst_起始的size個位元組置為value */
void memset(void* dst_, uint8_t value, uint32_t size) {
assert(dst_ != NULL);
uint8_t* dst = (uint8_t*)dst_;
while (size-- > 0)
*dst++ = value;
}
/* 将src_起始的size個位元組複制到dst_ */
void memcpy(void* dst_, const void* src_, uint32_t size) {
assert(dst_ != NULL && src_ != NULL);
uint8_t* dst = dst_;
const uint8_t* src = src_;
while (size-- > 0)
*dst++ = *src++;
}
/* 連續比較以位址a_和位址b_開頭的size個位元組,若相等則傳回0,若a_大于b_傳回+1,否則傳回-1 */
int memcmp(const void* a_, const void* b_, uint32_t size) {
const char* a = a_;
const char* b = b_;
assert(a != NULL || b != NULL);
while (size-- > 0) {
if(*a != *b) {
return *a > *b ? 1 : -1;
}
a++;
b++;
}
return 0;
}
2.1.2 針對字元串函數的操作
/* 将字元串從src_複制到dst_ */
char* strcpy(char* dst_, const char* src_) {
assert(dst_ != NULL && src_ != NULL);
char* r = dst_; // 用來傳回目的字元串起始位址
while((*dst_++ = *src_++));
return r;
}
和memcpy相比,*src='0’時,程式終止,因為src是字元串首位址。
是以針對的是strcpy字元串,memcpy針對的是size個字元。
/* 傳回字元串長度 */
uint32_t strlen(const char* str) {
assert(str != NULL);
const char* p = str;
while(*p++);
return (p - str - 1);
}
/* 比較兩個字元串,若a_中的字元大于b_中的字元傳回1,相等時傳回0,否則傳回-1. */
int8_t strcmp (const char* a, const char* b) {
assert(a != NULL && b != NULL);
while (*a != 0 && *a == *b) {
a++;
b++;
}
/* 如果*a小于*b就傳回-1,否則就屬于*a大于等于*b的情況。在後面的布爾表達式"*a > *b"中,
* 若*a大于*b,表達式就等于1,否則就表達式不成立,也就是布爾值為0,恰恰表示*a等于*b */
return *a < *b ? -1 : *a > *b;
}
/* 從左到右查找字元串str中首次出現字元ch的位址(不是下标,是位址) */
char* strchr(const char* str, const uint8_t ch) {
assert(str != NULL);
while (*str != 0) {
if (*str == ch) {
return (char*)str; // 需要強制轉化成和傳回值類型一樣,否則編譯器會報const屬性丢失,下同.
}
str++;
}
return NULL;
}
/* 從後往前查找字元串str中首次出現字元ch的位址(不是下标,是位址) */
char* strrchr(const char* str, const uint8_t ch) {
assert(str != NULL);
const char* last_char = NULL;
/* 從頭到尾周遊一次,若存在ch字元,last_char總是該字元最後一次出現在串中的位址(不是下标,是位址)*/
while (*str != 0) {
if (*str == ch) {
last_char = str;
}
str++;
}
return (char*)last_char;
}
也就是最後一個ch字元的位址
/* 将字元串src_拼接到dst_後,将回拼接的串位址 */
char* strcat(char* dst_, const char* src_) {
assert(dst_ != NULL && src_ != NULL);
char* str = dst_;
while (*str++);
--str; // 别看錯了,--str是獨立的一句,并不是while的循環體
while((*str++ = *src_++)); // 當*str被指派為0時,此時表達式不成立,正好添加了字元串結尾的0.
return dst_;
}
/* 在字元串str中查找指定字元ch出現的次數 */
uint32_t strchrs(const char* str, uint8_t ch) {
assert(str != NULL);
uint32_t ch_cnt = 0;
const char* p = str;
while(*p != 0) {
if (*p == ch) {
ch_cnt++;
}
p++;
}
return ch_cnt;
}
2.2 位圖的實作
2.2.1 bitmap.h
#ifndef __LIB_KERNEL_BITMAP_H
#define __LIB_KERNEL_BITMAP_H
#include "global.h"
#define BITMAP_MASK 1
struct bitmap {
uint32_t btmp_bytes_len;
/* 在周遊位圖時,整體上以位元組為機關,細節上是以位為機關,是以此處位圖的指針必須是單位元組 */
uint8_t* bits;
};
void bitmap_init(struct bitmap* btmp);
bool bitmap_scan_test(struct bitmap* btmp, uint32_t bit_idx);
int bitmap_scan(struct bitmap* btmp, uint32_t cnt);
void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, int8_t value);
#endif
btmp_bytes_len:位圖比特數量
bits:位圖首位元組位址 bits[0]是第一個位元組 位圖内位元組有效通路數是
0~ btmp_bytes_len-1,位元組内有效通路位數是0~7
2.2.2 bitmap.c
#include "bitmap.h"
#include "stdint.h"
#include "string.h"
#include "print.h"
#include "interrupt.h"
#include "debug.h"
/* 将位圖btmp初始化 */
void bitmap_init(struct bitmap* btmp) {
memset(btmp->bits, 0, btmp->btmp_bytes_len);
}
/* 判斷bit_idx位是否為1,若為1則傳回true,否則傳回false */
bool bitmap_scan_test(struct bitmap* btmp, uint32_t bit_idx) {
uint32_t byte_idx = bit_idx / 8; // 向下取整用于索引數組下标
uint32_t bit_odd = bit_idx % 8; // 取餘用于索引數組内的位
return (btmp->bits[byte_idx] & (BITMAP_MASK << bit_odd));
}
/* 在位圖中申請連續cnt個位,傳回其起始位下标 */
int bitmap_scan(struct bitmap* btmp, uint32_t cnt) {
uint32_t idx_byte = 0; // 用于記錄空閑位所在的位元組
/* 先逐位元組比較,蠻力法 */
while (( 0xff == btmp->bits[idx_byte]) && (idx_byte < btmp->btmp_bytes_len)) {
/* 1表示該位已配置設定,是以若為0xff,則表示該位元組内已無空閑位,向下一位元組繼續找 */
idx_byte++;
}
ASSERT(idx_byte < btmp->btmp_bytes_len);
if (idx_byte == btmp->btmp_bytes_len) { // 若該記憶體池找不到可用空間
return -1;
}
/* 若在位圖數組範圍内的某位元組内找到了空閑位,
* 在該位元組内逐位比對,傳回空閑位的索引。*/
int idx_bit = 0;
/* 和btmp->bits[idx_byte]這個位元組逐位對比 */
while ((uint8_t)(BITMAP_MASK << idx_bit) & btmp->bits[idx_byte]) {
idx_bit++;
}
int bit_idx_start = idx_byte * 8 + idx_bit; // 空閑位在位圖内的下标
if (cnt == 1) {
return bit_idx_start;
}
uint32_t bit_left = (btmp->btmp_bytes_len * 8 - bit_idx_start); // 記錄還有多少位可以判斷
uint32_t next_bit = bit_idx_start + 1;
uint32_t count = 1; // 用于記錄找到的空閑位的個數
bit_idx_start = -1; // 先将其置為-1,若找不到連續的位就直接傳回
while (bit_left-- > 0) {
if (!(bitmap_scan_test(btmp, next_bit))) { // 若next_bit為0
count++;
} else {
count = 0;
}
if (count == cnt) { // 若找到連續的cnt個空位
bit_idx_start = next_bit - cnt + 1;
break;
}
next_bit++;
}
return bit_idx_start;
}
/* 将位圖btmp的bit_idx位設定為value */
void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, int8_t value) {
ASSERT((value == 0) || (value == 1));
uint32_t byte_idx = bit_idx / 8; // 向下取整用于索引數組下标
uint32_t bit_odd = bit_idx % 8; // 取餘用于索引數組内的位
/* 一般都會用個0x1這樣的數對位元組中的位操作,
* 将1任意移動後再取反,或者先取反再移位,可用來對位置0操作。*/
if (value) { // 如果value為1
btmp->bits[byte_idx] |= (BITMAP_MASK << bit_odd);
} else { // 若為0
btmp->bits[byte_idx] &= ~(BITMAP_MASK << bit_odd);
}
}
bitmap_init : 從位圖首位元組開始,一個位元組一個位元組全部置0
bitmap_scan_test:擷取bit_idx位在位圖中是第幾個位元組以及該位元組的第幾位,然後傳回和1相與的值。
bitmap_scan:先位圖從第一個位元組開始後依次和0xff比較,直到不等為止,說明該位元組含有空閑位;然後在該位元組内每位依次進行和1與的操作,直到找到第一個空位,用idx_bit記錄其在位元組内的偏移;最後,從該位開始,依次周遊位圖所有位,直到找到cnt個連續空閑位。找到傳回位下标,找不到傳回-1
bitmap_set:value為1或運算,value為0取反後再與
2.3 實作虛拟記憶體池
2.1 memory.h
#ifndef __KERNEL_MEMORY_H
#define __KERNEL_MEMORY_H
#include "stdint.h"
#include "bitmap.h"
/* 記憶體池标記,用于判斷用哪個記憶體池 */
enum pool_flags {
PF_KERNEL = 1, // 核心記憶體池
PF_USER = 2 // 使用者記憶體池
};
#define PG_P_1 1 // 頁表項或頁目錄項存在屬性位
#define PG_P_0 0 // 頁表項或頁目錄項存在屬性位
#define PG_RW_R 0 // R/W 屬性位值, 讀/執行
#define PG_RW_W 2 // R/W 屬性位值, 讀/寫/執行
#define PG_US_S 0 // U/S 屬性位值, 系統級
#define PG_US_U 4 // U/S 屬性位值, 使用者級
/* 用于虛拟位址管理 */
struct virtual_addr {
/* 虛拟位址用到的位圖結構,用于記錄哪些虛拟位址被占用了。以頁為機關。*/
struct bitmap vaddr_bitmap;
/* 管理的虛拟位址 */
uint32_t vaddr_start;
};
extern struct pool kernel_pool, user_pool;
void mem_init(void);
void* get_kernel_pages(uint32_t pg_cnt);
void* malloc_page(enum pool_flags pf, uint32_t pg_cnt);
void malloc_init(void);
uint32_t* pte_ptr(uint32_t vaddr);
uint32_t* pde_ptr(uint32_t vaddr);
#endif
vaddr_bitmap:虛拟位址位圖結構
vaddr_start:虛拟位址起始位址
global.h 增加一點定義
#define NULL ((void*)0)
#define bool int
#define true 1
#define false 0
2.2 memory.c
#include "memory.h"
#include "bitmap.h"
#include "stdint.h"
#include "global.h"
#include "debug.h"
#include "print.h"
#include "string.h"
#include "interrupt.h"
#define PG_SIZE 4096
/*************** 位圖位址 ********************
* 因為0xc009f000是核心主線程棧頂,0xc009e000是核心主線程的pcb.
* 一個頁框大小的位圖可表示128M記憶體, 位圖位置安排在位址0xc009a000,
* 這樣本系統最大支援4個頁框的位圖,即512M記憶體 */
#define MEM_BITMAP_BASE 0xc009a000
/*************************************/
#define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)
#define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)
/* 0xc0000000是核心從虛拟位址3G起. 0x100000意指跨過低端1M記憶體,使虛拟位址在邏輯上連續 */
#define K_HEAP_START 0xc0100000
/* 記憶體池結構,生成兩個執行個體用于管理核心記憶體池和使用者記憶體池 */
struct pool {
struct bitmap pool_bitmap; // 本記憶體池用到的位圖結構,用于管理實體記憶體
uint32_t phy_addr_start; // 本記憶體池所管理實體記憶體的起始位址
uint32_t pool_size; // 本記憶體池位元組容量
};
struct pool kernel_pool, user_pool; // 生成核心記憶體池和使用者記憶體池
struct virtual_addr kernel_vaddr; // 此結構是用來給核心配置設定虛拟位址
/* 在pf表示的虛拟記憶體池中申請pg_cnt個虛拟頁,
* 成功則傳回虛拟頁的起始位址, 失敗則傳回NULL */
static void* vaddr_get(enum pool_flags pf, uint32_t pg_cnt) {
int vaddr_start = 0, bit_idx_start = -1;
uint32_t cnt = 0;
if (pf == PF_KERNEL) { // 核心記憶體池
bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);
if (bit_idx_start == -1) {
return NULL;
}
while(cnt < pg_cnt) {
bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1);
}
vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
} else { // 使用者記憶體池
//将來實作使用者程序再補充
}
return (void*)vaddr_start;
}
/* 得到虛拟位址vaddr對應的pte指針*/
uint32_t* pte_ptr(uint32_t vaddr) {
/* 先通路到頁表自己 + \
* 再用頁目錄項pde(頁目錄内頁表的索引)做為pte的索引通路到頁表 + \
* 再用pte的索引做為頁内偏移*/
uint32_t* pte = (uint32_t*)(0xffc00000 + \
((vaddr & 0xffc00000) >> 10) + \
PTE_IDX(vaddr) * 4);
return pte;
}
/* 得到虛拟位址vaddr對應的pde的指針 */
uint32_t* pde_ptr(uint32_t vaddr) {
/* 0xfffff是用來通路到頁表本身所在的位址 */
uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr) * 4);
return pde;
}
/* 在m_pool指向的實體記憶體池中配置設定1個實體頁,
* 成功則傳回頁框的實體位址,失敗則傳回NULL */
static void* palloc(struct pool* m_pool) {
/* 掃描或設定位圖要保證原子操作 */
int bit_idx = bitmap_scan(&m_pool->pool_bitmap, 1); // 找一個實體頁面
if (bit_idx == -1 ) {
return NULL;
}
bitmap_set(&m_pool->pool_bitmap, bit_idx, 1); // 将此位bit_idx置1
uint32_t page_phyaddr = ((bit_idx * PG_SIZE) + m_pool->phy_addr_start);
return (void*)page_phyaddr;
}
/* 頁表中添加虛拟位址_vaddr與實體位址_page_phyaddr的映射 */
static void page_table_add(void* _vaddr, void* _page_phyaddr) {
uint32_t vaddr = (uint32_t)_vaddr, page_phyaddr = (uint32_t)_page_phyaddr;
uint32_t* pde = pde_ptr(vaddr);
uint32_t* pte = pte_ptr(vaddr);
/************************ 注意 *************************
* 執行*pte,會通路到pde。是以確定pde建立完成後才能執行*pte,
* 否則會引發page_fault。是以在pde未建立時,
* *pte隻能出現在下面最外層else語句塊中的*pde後面。
* *********************************************************/
/* 先在頁目錄内判斷目錄項的P位,若為1,則表示該表已存在 */
if (*pde & 0x00000001) {
ASSERT(!(*pte & 0x00000001));
if (!(*pte & 0x00000001)) { // 隻要是建立頁表,pte就應該不存在,多判斷一下放心
*pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1); // US=1,RW=1,P=1
} else { // 調試模式下不會執行到此,上面的ASSERT會先執行.關閉調試時下面的PANIC會起作用
PANIC("pte repeat");
}
} else { // 頁目錄項不存在,是以要先建立頁目錄項再建立頁表項.
/* 頁表中用到的頁框一律從核心空間配置設定 */
uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);
*pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
/******************* 必須将頁表所在的頁清0 *********************
* 必須把配置設定到的實體頁位址pde_phyaddr對應的實體記憶體清0,
* 避免裡面的陳舊資料變成了頁表中的頁表項,進而讓頁表混亂.
* pte的高20位會映射到pde所指向的頁表的實體起始位址.*/
memset((void*)((int)pte & 0xfffff000), 0, PG_SIZE);
/************************************************************/
ASSERT(!(*pte & 0x00000001));
*pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1); // US=1,RW=1,P=1
}
}
/* 配置設定pg_cnt個頁空間,成功則傳回起始虛拟位址,失敗時傳回NULL */
void* malloc_page(enum pool_flags pf, uint32_t pg_cnt) {
ASSERT(pg_cnt > 0 && pg_cnt < 3840);
/*********** malloc_page的原理是三個動作的合成: ***********
1通過vaddr_get在虛拟記憶體池中申請虛拟位址
2通過palloc在實體記憶體池中申請實體頁
3通過page_table_add将以上兩步得到的虛拟位址和實體位址在頁表中完成映射
***************************************************************/
void* vaddr_start = vaddr_get(pf, pg_cnt);
if (vaddr_start == NULL) {
return NULL;
}
uint32_t vaddr = (uint32_t)vaddr_start, cnt = pg_cnt;
struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;
/* 因為虛拟位址是連續的,但實體位址可以是不連續的,是以逐個做映射*/
while (cnt-- > 0) {
void* page_phyaddr = palloc(mem_pool);
/* 失敗時要将曾經已申請的虛拟位址和實體頁全部復原,
* 在将來完成記憶體回收時再補充 */
if (page_phyaddr == NULL) {
return NULL;
}
page_table_add((void*)vaddr, page_phyaddr); // 在頁表中做映射
vaddr += PG_SIZE; // 下一個虛拟頁
}
return vaddr_start;
}
/* 從核心實體記憶體池中申請pg_cnt頁記憶體,
* 成功則傳回其虛拟位址,失敗則傳回NULL */
void* get_kernel_pages(uint32_t pg_cnt) {
void* vaddr = malloc_page(PF_KERNEL, pg_cnt);
if (vaddr != NULL) { // 若配置設定的位址不為空,将頁框清0後傳回
memset(vaddr, 0, pg_cnt * PG_SIZE);
}
return vaddr;
}
/* 初始化記憶體池 */
static void mem_pool_init(uint32_t all_mem) {
put_str(" mem_pool_init start\n");
uint32_t page_table_size = PG_SIZE * 256; // 頁表大小= 1頁的頁目錄表+第0和第768個頁目錄項指向同一個頁表+
// 第769~1022個頁目錄項共指向254個頁表,共256個頁框
uint32_t used_mem = page_table_size + 0x100000; // 0x100000為低端1M記憶體
uint32_t free_mem = all_mem - used_mem;
uint16_t all_free_pages = free_mem / PG_SIZE; // 1頁為4k,不管總記憶體是不是4k的倍數,
// 對于以頁為機關的記憶體配置設定政策,不足1頁的記憶體不用考慮了。
uint16_t kernel_free_pages = all_free_pages / 2;
uint16_t user_free_pages = all_free_pages - kernel_free_pages;
/* 為簡化位圖操作,餘數不處理,壞處是這樣做會丢記憶體。
好處是不用做記憶體的越界檢查,因為位圖表示的記憶體少于實際實體記憶體*/
uint32_t kbm_length = kernel_free_pages / 8; // Kernel BitMap的長度,位圖中的一位表示一頁,以位元組為機關
uint32_t ubm_length = user_free_pages / 8; // User BitMap的長度.
uint32_t kp_start = used_mem; // Kernel Pool start,核心記憶體池的起始位址
uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE; // User Pool start,使用者記憶體池的起始位址
kernel_pool.phy_addr_start = kp_start;
user_pool.phy_addr_start = up_start;
kernel_pool.pool_size = kernel_free_pages * PG_SIZE;
user_pool.pool_size = user_free_pages * PG_SIZE;
kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;
user_pool.pool_bitmap.btmp_bytes_len = ubm_length;
/********* 核心記憶體池和使用者記憶體池位圖 ***********
* 位圖是全局的資料,長度不固定。
* 全局或靜态的數組需要在編譯時知道其長度,
* 而我們需要根據總記憶體大小算出需要多少位元組。
* 是以改為指定一塊記憶體來生成位圖.
* ************************************************/
// 核心使用的最高位址是0xc009f000,這是主線程的棧位址.(核心的大小預計為70K左右)
// 32M記憶體占用的位圖是2k.核心記憶體池的位圖先定在MEM_BITMAP_BASE(0xc009a000)處.
kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;
/* 使用者記憶體池的位圖緊跟在核心記憶體池位圖之後 */
user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);
/******************** 輸出記憶體池資訊 **********************/
put_str(" kernel_pool_bitmap_start:");put_int((int)kernel_pool.pool_bitmap.bits);
put_str(" kernel_pool_phy_addr_start:");put_int(kernel_pool.phy_addr_start);
put_str("\n");
put_str(" user_pool_bitmap_start:");put_int((int)user_pool.pool_bitmap.bits);
put_str(" user_pool_phy_addr_start:");put_int(user_pool.phy_addr_start);
put_str("\n");
/* 将位圖置0*/
bitmap_init(&kernel_pool.pool_bitmap);
bitmap_init(&user_pool.pool_bitmap);
/* 下面初始化核心虛拟位址的位圖,按實際實體記憶體大小生成數組。*/
kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length; // 用于維護核心堆的虛拟位址,是以要和核心記憶體池大小一緻
/* 位圖的數組指向一塊未使用的記憶體,目前定位在核心記憶體池和使用者記憶體池之外*/
kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length);
kernel_vaddr.vaddr_start = K_HEAP_START;
bitmap_init(&kernel_vaddr.vaddr_bitmap);
put_str(" mem_pool_init done\n");
}
/* 記憶體管理部分初始化入口 */
void mem_init() {
put_str("mem_init start\n");
uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));
mem_pool_init(mem_bytes_total); // 初始化記憶體池
put_str("mem_init done\n");
}
各個函數總結:
mem_init:調用mem_pool_init 傳入參數為記憶體容量
mem_pool_init:總結這個函數前,先看這2個結構體
struct virtual_addr {
/* 虛拟位址用到的位圖結構,用于記錄哪些虛拟位址被占用了。以頁為機關。*/
struct bitmap vaddr_bitmap;
/* 管理的虛拟位址 */
uint32_t vaddr_start;
};
struct pool {
struct bitmap pool_bitmap; // 本記憶體池用到的位圖結構,用于管理實體記憶體
uint32_t phy_addr_start; // 本記憶體池所管理實體記憶體的起始位址
uint32_t pool_size; // 本記憶體池位元組容量
};
分别為記憶體池和虛拟記憶體池。
struct pool kernel_pool, user_pool; // 生成核心記憶體池和使用者記憶體池
struct virtual_addr kernel_vaddr; // 此結構是用來給核心配置設定虛拟位址
memory.c申請了兩個pool結構體 kernel_pool, user_pool用作核心記憶體池和使用者記憶體池
還申請了 kernel_vaddr用作虛拟記憶體池。
mem_pool_init這個函數就是在獲得參數記憶體總量的情況下,完成了對這3個已申請的結構體的成員變量的填寫,進而确定了記憶體池的位址以及他們位圖的位址,建立了記憶體管理系統
其中3個記憶體池的位圖位址均在實位址1MB以下。核心記憶體池首位址在2MB處,虛拟記憶體池虛拟位址被指派為K_HEAP_START即0xc0100000。
這個函數執行完畢後,整個記憶體結構将呈現如下圖,
位圖均位于1MB以下,記憶體池均位于2MB以上
上面的函數用于建立位圖結構和記憶體池
下面的函數用于配置設定記憶體
vaddr_get 在虛拟記憶體池申請pg_cnt個虛拟頁,傳回虛拟首位址,虛拟記憶體池大小與核心記憶體池大小一緻。
pte_ptr:獲得參數虛拟位址對應的頁表項的實體位址對應的虛拟位址
pde_ptr:獲得參數虛拟位址對應的頁目錄項的實體位址對應的虛拟位址
palloc:向參數指向的記憶體池申請一個實體頁,傳回實體位址
page_table_add:配置設定記憶體的核心函數。
利用上述3個函數,完成頁表中虛拟位址與實體位址完成映射。這個函數的執行有兩種情況,第一種:虛拟位址對應的頁目錄項存在,直接進入頁表,将實體位址填入虛拟位址對應的頁表項;第二種:虛拟位址對應的頁目錄項不存在,先調用palloc獲得一頁實體頁的實體位址,然後将此實體位址填入虛拟位址對應的頁目錄項,也就是将此頁作為一個頁表,再進入頁表,将參數實體位址寫入虛拟位址對應頁表項。
注意,有了頁目錄表,頁表可以不連續,分散成多個實體頁。
malloc_page:
1通過vaddr_get在虛拟記憶體池中申請虛拟位址
2通過palloc在實體記憶體池中申請實體頁
3通過page_table_add将以上兩步得到的虛拟位址和實體位址在頁表中完成映射
申請的pg_cnt頁虛拟記憶體是連續的,配置設定的實體位址不一定連續,成功傳回pg_cnt頁虛拟記憶體首虛拟位址。失敗傳回null
get_kernel_pages 調用malloc_page,傳回不為null,則将配置設定的實體頁清0
main.c
#include "print.h"
#include "init.h"
#include "memory.h"
int main(void) {
put_str("I am kernel\n");
init_all();
void* addr = get_kernel_pages(3);
put_str("\n get_kernel_page start vaddr is");
put_int((uint32_t)addr);
put_str("\n");
while(1);
return 0;
}
申請3頁,也就是完成了3個虛拟頁首位址到實體位址映射
init.c 增加一句代碼
mem_init(); // 初始化記憶體管理系統
makefile增加的代碼
$(BUILD_DIR)/string.o: lib/string.c lib/string.h lib/stdint.h kernel/global.h \
lib/stdint.h kernel/debug.h
$(CC) $(CFLAGS) $< -o [email protected]
$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \
kernel/global.h lib/stdint.h lib/string.h lib/stdint.h \
lib/kernel/print.h kernel/interrupt.h kernel/debug.h
$(CC) $(CFLAGS) $< -o [email protected]
$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h lib/stdint.h lib/kernel/bitmap.h \
kernel/global.h kernel/global.h kernel/debug.h lib/kernel/print.h \
lib/kernel/io.h kernel/interrupt.h lib/string.h lib/stdint.h
$(CC) $(CFLAGS) $< -o [email protected]
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
lib/stdint.h kernel/init.h kernel/memory.h
$(CC) $(CFLAGS) $< -o [email protected]
生成了string.o bitmap.o memory.o main.o要加個memory.h
OBJS 也要增加3個新增的連結目标
實驗結果
編譯連結
make all
運作
./bochs -f bochsrc.disk
0xC0100000就是我們mem_init裡設定的虛拟記憶體池虛拟位址,結果符合代碼運作
3頁虛拟位址向實體位址的映射
完成3頁虛拟位址和實體位址的映射,隻需要記錄3個虛拟頁首位址在頁表的映射即可,因為虛拟位址後12位是偏移量。
最後再注意一個點:
在上次分頁實驗中我們已經完成了3GB~ 3GB+1MB 與 0~1MB的映射
和虛拟位址0~ 1MB 到實位址 0~1MB的映射。
是以這次試驗虛拟記憶體池的首位址是3GB+1MB往上的位址,3GB~ 3GB+1MB和0~1MB這兩塊虛拟位址預設為永遠不空閑,永遠不可被配置設定。
另外核心記憶體池和使用者記憶體池的起始位址是2MB往上到32MB,是以0~1MB的核心程式包括1MB ~2MB的頁表也是永遠不空閑,無法被配置設定。
當虛拟記憶體池首位址為3GB+1MB時,位圖初始化應該全部置0
同理,實體記憶體池位圖也應該全部置0.