1. 程式人生 > >【自制作業系統10】記憶體管理系統

【自制作業系統10】記憶體管理系統

本章我們要完成初步的記憶體管理系統,最終簡單實現一個從核心記憶體池中獲取 3 頁的記憶體這樣一個函式實現。

一、到目前為止的程式流程圖

為了讓大家清楚目前的程式進度,畫了到目前為止的程式流程圖,如下。

圖中紅色部分就是我們本章的程式碼在全域性流程中的位置,下面藍色部分是將 malloc_page 方法樹狀拆解開來看。不考慮太多細節,本章就是完成一個可以 從記憶體中分配指定頁數的記憶體(程式碼中為 3 頁),並將起始地址打印出來。下面我們看看要怎樣實現這個功能。

二、先上程式碼

主要程式碼

 1 #include "print.h"
 2 #include "init.h"
 3 void main(void){
 4     put_str("I am kernel\n");
 5     init_all();
 6     // 這就是我們今天主要實現的功能,從核心的記憶體池中申請到 3 頁的記憶體頁
 7     void* addr = get_kernel_pages(3);
 8     put_str("\n get_kernel_pages start vaddr is ");
 9     put_int((uint32_t)addr);
10     put_str("\n");
11     while(1);
12 }
main.c
  1 #include "memory.h"
  2 #include "bitmap.h"
  3 #include "stdint.h"
  4 #include "global.h"
  5 #include "print.h"
  6 #include "string.h"
  7 #include "interrupt.h"
  8 
  9 #define PG_SIZE 4096
 10 #define MEM_BITMAP_BASE 0xc009a000
 11 #define K_HEAP_START 0xc0100000
 12 
 13 #define PDE_IDX(addr) ((addr & 0xffc00000) >> 22) // 虛擬地址高10位,pde
 14 #define PTE_IDX(addr) ((addr & 0x003ff000) >> 12) // 虛擬地址中間10位,pte
 15 
 16 struct pool {
 17     struct bitmap pool_bitmap;
 18     uint32_t phy_addr_start; //本記憶體池管理的實體記憶體起始
 19     uint32_t pool_size;
 20 };
 21 
 22 struct pool kernel_pool, user_pool;
 23 struct virtual_addr kernel_vaddr;
 24 
 25 // 在pf表示的虛擬記憶體池中申請pg_cnt個虛擬頁,成功返回起始地址,失敗返回NULL
 26 static void* vaddr_get(enum pool_flags pf, uint32_t pg_cnt) {
 27     int vaddr_start = 0, bit_idx_start = -1;
 28     uint32_t cnt = 0;
 29     if (pf == PF_KERNEL) {
 30         bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);
 31         if (bit_idx_start == -1) {
 32             return NULL;
 33         }
 34         while(cnt < pg_cnt) {
 35             bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1);
 36         }
 37         vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
 38     } else {
 39         // 使用者記憶體池,將來再說
 40     }
 41     return (void*)vaddr_start;
 42 }
 43 
 44 // 得到虛擬地址vaddr對應的pte指標
 45 uint32_t* pte_ptr(uint32_t vaddr) {
 46     uint32_t* pte = (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr) * 4);
 47     return pte;
 48 }
 49 
 50 // 得到虛擬地址vaddr對應的pde指標
 51 uint32_t* pde_ptr(uint32_t vaddr) {
 52     uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr) * 4);
 53     return pde;
 54 }
 55 
 56 //在m_pool指向的實體記憶體池中分配1個物理頁
 57 static void* palloc(struct pool* m_pool) {
 58     //找到一個物理頁
 59     int bit_idx = bitmap_scan(&m_pool->pool_bitmap, 1);
 60     if (bit_idx == -1) {
 61         return NULL;
 62     }
 63     // 將此位置1將
 64     bitmap_set(&m_pool->pool_bitmap, bit_idx, 1);
 65     uint32_t page_phyaddr = ((bit_idx * PG_SIZE) + m_pool->phy_addr_start);
 66     return (void*)page_phyaddr;
 67 }
 68 
 69 // 頁表中新增虛擬地址_vaddr與實體地址_page_phyaddr的對映
 70 static void page_table_add(void* _vaddr, void* _page_phyaddr) {
 71     uint32_t vaddr = (uint32_t)_vaddr;
 72     uint32_t page_phyaddr = (uint32_t)_page_phyaddr;
 73     uint32_t* pde = pde_ptr(vaddr);
 74     uint32_t* pte = pte_ptr(vaddr);
 75     
 76     // 判斷頁目錄項的p位,為1表示該表已存在
 77     if (*pde & 0x00000001) {
 78         if(!(*pte & 0x00000001)) {
 79             *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
 80         } else {
 81             // pte repeat
 82         }
 83     } else {
 84         // 頁目錄項不存在,先建立頁目錄項,再建立頁表項
 85         uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);
 86         *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
 87         memset((void*)((int)pte & 0xfffff000), 0, PG_SIZE);
 88         *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
 89     }
 90 }
 91 
 92 // 分配pg_cnt個頁空間,成功返回起始虛擬地址,失敗返回NULL
 93 void* malloc_page(enum pool_flags pf, uint32_t pg_cnt) {
 94     // 1 通過 vaddr_get 在虛擬記憶體池中申請虛擬地址
 95     // 2 通過 palloc 在實體記憶體池中申請物理頁
 96     // 3 通過 page_table_add 將以上得到的虛擬地址和實體地址在頁表中完成對映
 97     void* vaddr_start = vaddr_get(pf, pg_cnt);
 98     if (vaddr_start == NULL) {
 99         return NULL;
100     }
101     
102     uint32_t vaddr = (uint32_t)vaddr_start;
103     uint32_t cnt = pg_cnt;
104     struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;
105     
106     // 虛擬地址和實體地址逐個對映
107     while (cnt-- > 0) {
108         void* page_phyaddr = palloc(mem_pool);
109         if (page_phyaddr == NULL) {
110             return NULL;
111         }
112         page_table_add((void*)vaddr, page_phyaddr);
113         vaddr += PG_SIZE;
114     }
115     return vaddr_start;
116 }
117 
118 // 從核心實體記憶體池中申請1頁記憶體,成功返回虛擬地址,失敗NULL
119 void* get_kernel_pages(uint32_t pg_cnt) {
120     void* vaddr = malloc_page(PF_KERNEL, pg_cnt);
121     if (vaddr != NULL) {
122         memset(vaddr, 0, pg_cnt * PG_SIZE);
123     }
124     return vaddr;
125 }        
126 
127 // 初始化記憶體池
128 static void mem_pool_init(uint32_t all_mem) {
129     put_str("  mem_pool_init start\n");
130     uint32_t page_table_size = PG_SIZE * 256;
131     uint32_t used_mem = page_table_size + 0x100000; // 低端1M記憶體 + 頁表大小
132     uint32_t free_mem = all_mem - used_mem;
133     uint16_t all_free_pages = free_mem / PG_SIZE;
134     
135     uint16_t kernel_free_pages = all_free_pages / 2; // 使用者和核心各分一半的可用記憶體
136     uint16_t user_free_pages = all_free_pages - kernel_free_pages;
137     uint32_t kbm_length = kernel_free_pages / 8;
138     uint32_t ubm_length = user_free_pages / 8;
139     uint32_t kp_start = used_mem; // 核心記憶體池起始
140     uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE;
141     
142     kernel_pool.phy_addr_start = kp_start;
143     user_pool.phy_addr_start = up_start;
144     
145     kernel_pool.pool_size = kernel_free_pages * PG_SIZE;
146     user_pool.pool_size = user_free_pages * PG_SIZE;
147     
148     kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;
149     user_pool.pool_bitmap.btmp_bytes_len = ubm_length;
150     
151     kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;
152     user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);
153     
154     // 輸出記憶體池資訊
155     put_str(" kernel_pool_bitmap_start:"); 
156     put_int((int)kernel_pool.pool_bitmap.bits); 
157     put_str(" kernel_pool_phy_addr_start:"); 
158     put_int(kernel_pool.phy_addr_start); 
159     put_str("\n"); 
160     put_str("user_pool_bitmap_start:"); 
161     put_int((int)user_pool.pool_bitmap.bits); 
162     put_str(" user_pool_phy_addr_start:"); 
163     put_int(user_pool.phy_addr_start); 
164     put_str("\n");
165     
166     // 將點陣圖直0
167     bitmap_init(&kernel_pool.pool_bitmap);
168     bitmap_init(&user_pool.pool_bitmap);
169     
170     // 初始化核心虛擬地址點陣圖
171     kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length;
172     kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length);
173     kernel_vaddr.vaddr_start = K_HEAP_START;
174     bitmap_init(&kernel_vaddr.vaddr_bitmap);
175     put_str("   mem_pool_init done\n");
176 }
177 
178 // 初始化記憶體
179 void mem_init() {
180     put_str("mem_init start\n");
181     //uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));
182     uint32_t mem_bytes_total = 32 * 1024 * 1024;
183     mem_pool_init(mem_bytes_total);
184     put_str("mem_init done\n");
185 }
memory.c
 1  #include "bitmap.h" 
 2  #include "stdint.h" 
 3  #include "string.h" 
 4  #include "print.h" 
 5  #include "interrupt.h" 
 6  
 7  // 點陣圖初始化,把每一位都設定為0
 8  void bitmap_init(struct bitmap* btmp) {
 9      memset(btmp->bits, 0, btmp->btmp_bytes_len);
10  }
11  
12  // 判斷bit_idx位是否為1,若為1,則返回true
13  bool bitmap_scan_test(struct bitmap* btmp, uint32_t bit_idx) {
14      uint32_t byte_idx = bit_idx / 8;
15      uint32_t bit_odd = bit_idx % 8;
16      return (btmp->bits[byte_idx] & (1 << bit_odd));
17  }
18  
19  // 在點陣圖中申請連續cnt個位,成功則返回起始位下標,失敗返回-1
20  int bitmap_scan(struct bitmap* btmp, uint32_t cnt) {
21      uint32_t idx_byte = 0;
22      // 逐個位元組比較
23      while((0xff == btmp->bits[idx_byte]) && (idx_byte < btmp->btmp_bytes_len)) {
24          idx_byte++;
25      }
26      //未找到空閒位,返回-1
27      if(idx_byte == btmp->btmp_bytes_len) {
28          return -1;
29      }
30      // 某位元組中找到了空閒位
31      int idx_bit = 0;
32      while((uint8_t)(1 << idx_bit) & btmp->bits[idx_byte]) {
33          idx_bit++;
34      }
35      int bit_idx_start = idx_byte * 8 + idx_bit;
36      // 只需要1位直接返回
37      if (cnt == 1) {
38          return bit_idx_start;
39      }
40      // 需要多於1位,還得繼續判斷
41      uint32_t bit_left = (btmp->btmp_bytes_len * 8 - bit_idx_start);
42      uint32_t next_bit = bit_idx_start + 1;
43      uint32_t count = 1; // 已找到的空閒位
44      
45      bit_idx_start = -1;
46      while(bit_left-- > 0) {
47          if(!(bitmap_scan_test(btmp, next_bit))) {
48              count++;
49          } else {
50              count = 0;
51          }
52          // 找到連續的cnt個空位
53          if(count == cnt) {
54              bit_idx_start = next_bit - cnt + 1;
55              break;
56          }
57          next_bit++;
58      }
59      return bit_idx_start;
60  }
61      
62  // 將點陣圖btmp的bit_idx位設定為value
63  void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, int8_t value) {
64      uint32_t byte_idx = bit_idx / 8;
65      uint32_t bit_odd = bit_idx % 8;
66      
67      if(value) {
68          // value為1
69          btmp->bits[byte_idx] |= (1 << bit_odd);
70      } else {
71          btmp->bits[byte_idx] &= ~(1 << bit_odd);
72      }
73  }
bitmap.c

標頭檔案及其他

 1 mbr.bin: mbr.asm
 2     nasm -I include/ -o out/mbr.bin mbr.asm -l out/mbr.lst
 3     
 4 loader.bin: loader.asm
 5     nasm -I include/ -o out/loader.bin loader.asm -l out/loader.lst
 6     
 7 kernel.bin: kernel/main.c
 8     gcc -I lib/kernel/ -I lib/ -I kernel/ -c -fno-builtin -o out/main.o kernel/main.c
 9     nasm -f elf -o out/print.o lib/kernel/print.asm -l out/print.lst
10     nasm -f elf -o out/kernel.o kernel/kernel.asm -l out/kernel.lst
11     gcc -I lib/kernel/ -I lib/ -I kernel/ -c -fno-builtin -o out/string.o lib/string.c
12     gcc -I lib/kernel/ -I lib/ -I kernel/ -c -fno-builtin -o out/interrupt.o kernel/interrupt.c
13     gcc -I lib/kernel/ -I lib/ -I kernel/ -c -fno-builtin -o out/init.o kernel/init.c
14     gcc -I lib/kernel/ -I lib/ -I kernel/ -c -fno-builtin -o out/bitmap.o kernel/bitmap.c
15     gcc -I lib/kernel/ -I lib/ -I kernel/ -c -fno-builtin -o out/memory.o kernel/memory.c
16     ld -Ttext 0xc0001500 -e main -o out/kernel.bin out/main.o out/init.o out/interrupt.o out/print.o out/kernel.o out/memory.o out/bitmap.o out/string.o
17     
18 os.raw: mbr.bin loader.bin kernel.bin
19     ../bochs/bin/bximage -hd -mode="flat" -size=60 -q target/os.raw
20     dd if=out/mbr.bin of=target/os.raw bs=512 count=1
21     dd if=out/loader.bin of=target/os.raw bs=512 count=4 seek=2
22     dd if=out/kernel.bin of=target/os.raw bs=512 count=200 seek=9
23     
24 run:
25     make install
26     make only-qemu-run
27     
28 brun:
29     make install
30     make only-bochs-run
31     
32 bdrun:
33     make install
34     make only-bochsdbg-run
35     
36 only-qemu-run:
37     qemu-system-i386 -m 512 target/os.raw
38     
39 only-bochs-run:
40     ../bochs/bin/bochs -f ../bochs/bochsrc.disk -q
41     
42 only-bochsdbg-run:
43     ../bochs/bin/bochs -f ../bochs/bochsrc.disk -q
44     
45 only-run-s:
46     qemu-system-i386 -s -S -m 512 target/os.raw --nographic
47     
48 install:
49     make clean
50     make -r os.raw
51     
52 clean:
53     rm -rf target/*
54     rm -rf out/*
55     rm -rf os.raw
56     rm -rf os.raw.lock
57     rm -rf bochs.out
Makefile
 1 #ifndef __KERNEL_MEMORY_H
 2 #define __KERNEL_MEMORY_H
 3 #include "stdint.h"
 4 #include "bitmap.h"
 5 
 6 enum pool_flags {
 7     PF_KERNEL = 1, // 核心記憶體池
 8     PF_USER = 2 // 使用者記憶體池
 9 };
10 
11 #define PG_P_1  1    // 頁表項或頁目錄項存在屬性位
12 #define PG_P_0  0    // 頁表項或頁目錄項存在屬性位
13 #define PG_RW_R 0    // R/W 屬性位值, 讀/執行
14 #define PG_RW_W 2    // R/W 屬性位值, 讀/寫/執行
15 #define PG_US_S 0    // U/S 屬性位值, 系統級
16 #define PG_US_U 4    // U/S 屬性位值, 使用者級
17 
18 // 虛擬地址池,用於虛擬地址管理
19 struct virtual_addr {
20     struct bitmap vaddr_bitmap;
21     uint32_t vaddr_start;
22 };
23 
24 extern struct pool kernel_pool, user_pool;
25 void mem_init(void);
26 #endif
memory.h
 1 #ifndef __LIB_KERNEL_BITMAP_H 
 2 #define __LIB_KERNEL_BITMAP_H 
 3 #include "global.h" 
 4 #define BITMAP_MASK 1 
 5 struct bitmap { 
 6     uint32_t btmp_bytes_len; 
 7     // 在遍歷點陣圖時,整體上以位元組為單位,細節上是以位為單位,所以此處點陣圖的指標必須是單位元組
 8     uint8_t* bits; 
 9 }; 
10 
11 void bitmap_init(struct bitmap* btmp); 
12 bool bitmap_scan_test(struct bitmap* btmp, uint32_t bit_idx);
13 int bitmap_scan(struct bitmap* btmp, uint32_t cnt); 
14 void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, int8_t value); 
15 #endif
bitmap.h

三、程式碼解讀

整段程式碼起始就做了兩件事

  1. 初始化記憶體池,包括核心記憶體池與使用者記憶體池。每個記憶體池分別有 物理的(kernel_pool、user_pool)和 虛擬的(kernel_vaddr、user_vaddr)兩種,管理方式是通過 bitmap 這種資料結構實現的
  2. 實現申請記憶體函式,本章僅實現了 get_kernel_pages,即從核心實體記憶體池中申請1頁記憶體,成功返回虛擬地址,失敗NULL

我把上面兩件事畫在了一張圖裡,左邊展示了我們的記憶體佈局,以及一些關鍵的資料結構 bitmap 在記憶體中的位置。右邊是最終實現的函式 get_kernel_pages 要做的三件事,即

  1. vaddr_get,從虛擬地址中獲取連續可用記憶體
  2. palloc,從實體記憶體池中一個個獲取可用的實體記憶體頁
  3. page_table_add,通過上面的虛擬地址和實體地址,建立頁表

下面我們把每一個關鍵部分拿出來講解,並附上關鍵程式碼。

初始化記憶體池

記憶體池是實現申請記憶體函式的基礎,主要目的就是管理一段記憶體,說明哪塊記憶體被佔用了,哪塊記憶體是空閒的。管理這些記憶體佔用情況的資料結構,用的是 bitmap,每一個位元對應著一塊 4K 的記憶體。

記憶體池一共分為四個,核心的實體地址記憶體池、使用者的實體地址記憶體池、核心的虛擬地址記憶體池、使用者的虛擬地址記憶體池。

管理實體地址的記憶體池的結構為 pool,兩個記憶體池變數為 kernel_pool,user_pool

struct pool {
    struct bitmap pool_bitmap;
    uint32_t phy_addr_start; //本記憶體池管理的實體記憶體起始
    uint32_t pool_size;
};

管理虛擬地址的記憶體池的結構為 virtual_addr,兩個記憶體池變數本章我們只實現了一個 kernel_vaddr

struct virtual_addr {
    struct bitmap vaddr_bitmap;
    uint32_t vaddr_start; //本記憶體池管理的虛擬記憶體起始
};

兩個結構只是實體記憶體池結構比虛擬記憶體池結構多了一個 pool_size,因為實體地址是有限的,而虛擬地址可以相對來說是無限的。

mem_pool_init 函式就是將這兩個結構的三個記憶體池變數賦好值,程式碼一目瞭然,各個值就是上述記憶體圖中所表現的,就不展開敘述了。

申請記憶體函式 get_kernel_pages 實現

該函式先是從虛擬記憶體池中獲取指定頁數的連續記憶體(vaddr_get),獲取到之後,再迴圈呼叫從實體記憶體池中獲取一頁一頁的實體記憶體(palloc),每獲取到一個實體記憶體,就將虛擬記憶體與實體記憶體的對映關係加入到頁表(page_table_add)。

先看 vaddr_get 函式

 1 static void* vaddr_get(enum pool_flags pf, uint32_t pg_cnt) {
 2     int vaddr_start = 0, bit_idx_start = -1;
 3     uint32_t cnt = 0;
 4     if (pf == PF_KERNEL) {
 5         bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);
 6         if (bit_idx_start == -1) {
 7             return NULL;
 8         }
 9         while(cnt < pg_cnt) {
10             bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1);
11         }
12         vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
13     } else {
14         // 使用者記憶體池,將來再說
15     }
16     return (void*)vaddr_start;
17 }

該函式如果不考慮 bitmap 底層實現,則非常容易理解,就是利用 bitmap 的資料結構, 呼叫 bitmap_scan 搜尋出一片連續的記憶體,再呼叫 bitmap_set 將申請到的記憶體點陣圖部分設定為 1(已用),最後通過公式

vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;

得出 所獲得的虛擬地址的起始的虛擬記憶體地址(好繞哈哈)

再看 palloc 函式

 1  //在m_pool指向的實體記憶體池中分配1個物理頁
 2  static void* palloc(struct pool* m_pool) {
 3      //找到一個物理頁
 4      int bit_idx = bitmap_scan(&m_pool->pool_bitmap, 1);
 5      if (bit_idx == -1) {
 6          return NULL;
 7      }
 8      // 將此位置1將
 9      bitmap_set(&m_pool->pool_bitmap, bit_idx, 1);
10      uint32_t page_phyaddr = ((bit_idx * PG_SIZE) + m_pool->phy_addr_start);
11      return (void*)page_phyaddr;
12  }

不多說了,跟上面的函式其實是一模一樣的,只不過是獲得一個物理頁而不是多個,最終返回了 所獲得的實體地址的起始的實體地址。

最後看 page_table_add 函式

 1 // 頁表中新增虛擬地址_vaddr與實體地址_page_phyaddr的對映
 2 static void page_table_add(void* _vaddr, void* _page_phyaddr) {
 3     uint32_t vaddr = (uint32_t)_vaddr;
 4     uint32_t page_phyaddr = (uint32_t)_page_phyaddr;
 5     uint32_t* pde = pde_ptr(vaddr);
 6     uint32_t* pte = pte_ptr(vaddr);
 7     
 8     // 判斷頁目錄項的p位,為1表示該表已存在
 9     if (*pde & 0x00000001) {
10         if(!(*pte & 0x00000001)) {
11             *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
12         } else {
13             // pte repeat
14         }
15     } else {
16         // 頁目錄項不存在,先建立頁目錄項,再建立頁表項
17         uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);
18         *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
19         memset((void*)((int)pte & 0xfffff000), 0, PG_SIZE);
20         *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
21     }
22 }

該函式也很好理解,前面兩個函式已經獲得了一個個的虛擬記憶體,並且也獲得了一個個的實體記憶體,這兩個值作為入參進入本函式,最終建立了一個個的頁目錄項(如果沒有),和一個個的頁表項。

簡單說最重要的就是後面畫黃線的兩條賦值語句

*pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
*pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);

最終我們的 main 函式裡是申請了 3 頁的記憶體空間,所以 page_table_add 這個函式也會被呼叫三次,我把這三次的關鍵值都打了出來

  vaddr page_phyaddr 建立頁目錄項 *pde *pte pde_value pte_value
第一次 C0100000 200000 0xFFFFFC00 0xFFF00400 已有頁目錄項,無需 200007
第二次 C0101000 201000 0xFFFFFC00 0xFFF00404 已有頁目錄項,無需 201007
第三次 C0102000 202000 0xFFFFFC00 0xFFF00408 已有頁目錄項,無需 202007

拿第一次舉例,本函式就是要將虛擬地址 C0100000 和實體地址 200000 通過頁表建立關係,通過頁表建立關係要算出四個值

  1. 需要賦值的頁目錄項地址 *pde
  2. 需要給該頁目錄項賦的實際值 pde_value
  3. 需要賦值的頁表項地址 *pte
  4. 需要給改頁表項賦的實際值 pte_value

其中 2 和 4 的值好說,由於已經存在頁目錄項,所以頁目錄項賦值這一步就省略了。然後頁表項賦的值,就是最終要對映的實體地址的值的高 20 位以及需要的屬性,也就是 200007。關於這塊有疑問的,可以回顧一下【自制作業系統05】開啟記憶體分頁機制,在這裡我只把關鍵的頁表圖貼出來。

頁目錄項和頁表項結構

 我們已經賦值的頁目錄表和頁表

虛擬地址到實體地址的轉換

對於 1 和 3,也就是需要賦值的頁目錄項和頁表項的地址,我覺得是不太好讀懂的程式碼

 1 // 得到虛擬地址vaddr對應的pte指標
 2 uint32_t* pte_ptr(uint32_t vaddr) {
 3     uint32_t* pte = (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr) * 4);
 4     return pte;
 5 }
 6 
 7 // 得到虛擬地址vaddr對應的pde指標
 8 uint32_t* pde_ptr(uint32_t vaddr) {
 9     uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr) * 4);
10     return pde;
11 }

但我們先倒推一下還是很好理解的,還是拿第一次的資料舉例,頁目錄項地址 *pde = 0xFFFFFC00,頁表項地址 *pte = 0xFFF00400。首先你要明確的是,這是虛擬地址,通過我們之前總結出的頁表對映關係

0x00000000-0x000fffff -> 0x000000000000-0x0000000fffff
0xc0000000-0xc00fffff -> 0x000000000000-0x0000000fffff
0xffc00000-0xffc00fff -> 0x000000101000-0x000000101fff
0xfff00000-0xffffefff -> 0x000000101000-0x0000001fffff
0xfffff000-0xffffffff -> 0x000000100000-0x000000100fff

可以得出它們對應的實體地址分別是 *pde = 0x100C00,*pte = 0x101400。再把第二次和第三次都算出來,在頁表圖中的表現就是:

在已存在的頁目錄項 0x100C00 中,新增三個頁表項,分別指向需要對映的實體地址。如下!

從結果上看,感覺正是我們所需要的,在原有頁表基礎上,往下找位置插入而已。

插入好新頁表項後,頁表對映關係變成了下面這樣,紅色為新增。很好理解,因為第 0 個和第 768 個頁目錄項都對應著第一個頁表,我們在第一個頁表中添加了三個(連續的就被合併成一個對映關係展示了)頁表專案,所以自然就多了兩處地址對映關係

0x00000000-0x000fffff -> 0x000000000000-0x0000000fffff
0x00100000-0x00102fff -> 0x000000200000-0x000000202fff
0xc0000000-0xc00fffff -> 0x000000000000-0x0000000fffff
0xc0100000-0xc0102fff -> 0x000000200000-0x000000202fff
0xffc00000-0xffc00fff -> 0x000000101000-0x000000101fff
0xfff00000-0xffffefff -> 0x000000101000-0x0000001fffff
0xfffff000-0xffffffff -> 0x000000100000-0x000000100fff

倒推之後,再來品一品這個程式碼,這也解決了我們之前所說的,如何通過一個虛擬地址,找到它所在的頁目錄表和頁表。思路是,我們首先能夠通過這個 vaddr,能推出頁目錄項和頁表項的實體地址。拿頁目錄項的實體地址來說,我們需要拼湊出一個頁目錄項的虛擬地址,讓其可以訪問到此頁目錄項的實體地址,涉及到了一些奇怪的技巧。我這裡不想展開說這段程式碼了,只要知道就好,想起來真的很燒腦。

 1 // 得到虛擬地址vaddr對應的pte指標
 2 uint32_t* pte_ptr(uint32_t vaddr) {
 3     uint32_t* pte = (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr) * 4);
 4     return pte;
 5 }
 6 
 7 // 得到虛擬地址vaddr對應的pde指標
 8 uint32_t* pde_ptr(uint32_t vaddr) {
 9     uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr) * 4);
10     return pde;
11 }

 

四、執行

我們看到,我們成功呼叫函式,獲取了 3 個核心的記憶體頁,起始地址為 0xC0100000

 

寫在最後:開源專案和課程規劃

如果你對自制一個作業系統感興趣,不妨跟隨這個系列課程看下去,甚至加入我們,一起來開發。

參考書籍

《作業系統真相還原》這本書真的贊!強烈推薦

專案開源

專案開源地址:https://gitee.com/sunym1993/flashos

當你看到該文章時,程式碼可能已經比文章中的又多寫了一些部分了。你可以通過提交記錄歷史來檢視歷史的程式碼,我會慢慢梳理提交歷史以及專案說明文件,爭取給每一課都準備一個可執行的程式碼。當然文章中的程式碼也是全的,採用複製貼上的方式也是完全可以的。

如果你有興趣加入這個自制作業系統的大軍,也可以在留言區留下您的聯絡方式,或者在 gitee 私信我您的聯絡方式。

課程規劃

本課程打算出系列課程,我寫到哪覺得可以寫成一篇文章了就寫出來分享給大家,最終會完成一個功能全面的作業系統,我覺得這是最好的學習作業系統的方式了。所以中間遇到的各種坎也會寫進去,如果你能持續跟進,跟著我一塊寫,必然會有很好的收貨。即使沒有,交個朋友也是好的哈哈。

目前的系列包括

  • 【自制作業系統01】硬核講解計算機的啟動過程
  • 【自制作業系統02】環境準備與啟動區實現
  • 【自制作業系統03】讀取硬碟中的資料
  • 【自制作業系統04】從真實模式到保護模式
  • 【自制作業系統05】開啟記憶體分頁機制
  • 【自制作業系統06】終於開始用 C 語言了,第一行核心程式碼!
  • 【自制作業系統07】深入淺出特權級
  • 【自制作業系統08】中斷
  • 【自制作業系統09】中斷的程式碼實現