天天看點

作業系統真象還原實驗記錄之實驗十三:記憶體管理系統作業系統真象還原實驗記錄之實驗十三:記憶體管理系統

作業系統真象還原實驗記錄之實驗十三:記憶體管理系統

對應書P374 8.3節

1.相關基礎知識總結

1.1 記憶體池規劃

通過分頁實驗已經很清楚,虛拟位址——實位址的映射規則依賴的是頁表

作業系統真象還原實驗記錄之實驗十三:記憶體管理系統作業系統真象還原實驗記錄之實驗十三:記憶體管理系統

對于所有程序和核心來說,都有自己獨立的虛拟記憶體池,獨立的頁表。這就是為什麼每個程序都有自己的獨立的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.

繼續閱讀