【自制操做系統10】內存管理系統

本章咱們要完成初步的內存管理系統,最終簡單實現一個從內核內存池中獲取 3 頁的內存這樣一個函數實現。html

1、到目前爲止的程序流程圖

爲了讓你們清楚目前的程序進度,畫了到目前爲止的程序流程圖,以下。git

圖中紅色部分就是咱們本章的代碼在全局流程中的位置,下面藍色部分是將 malloc_page 方法樹狀拆解開來看。不考慮太多細節,本章就是完成一個能夠 從內存中分配指定頁數的內存(代碼中爲 3 頁),並將起始地址打印出來。下面咱們看看要怎樣實現這個功能。數據結構

2、先上代碼

主要代碼ide

 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

3、代碼解讀

整段代碼起始就作了兩件事佈局

  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,經過上面的虛擬地址和物理地址,創建頁表

下面咱們把每個關鍵部分拿出來說解,並附上關鍵代碼。ui

初始化內存池

內存池是實現申請內存函數的基礎,主要目的就是管理一段內存,說明哪塊內存被佔用了,哪塊內存是空閒的。管理這些內存佔用狀況的數據結構,用的是 bitmap,每個比特對應着一塊 4K 的內存。spa

內存池一共分爲四個,內核的物理地址內存池、用戶的物理地址內存池、內核的虛擬地址內存池、用戶的虛擬地址內存池。操作系統

管理物理地址的內存池的結構爲 pool,兩個內存池變量爲 kernel_pooluser_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 }

 

4、運行

咱們看到,咱們成功調用函數,獲取了 3 個內核的內存頁,起始地址爲 0xC0100000

 

寫在最後:開源項目和課程規劃

若是你對自制一個操做系統感興趣,不妨跟隨這個系列課程看下去,甚至加入咱們,一塊兒來開發。

參考書籍

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

項目開源

項目開源地址:https://gitee.com/sunym1993/flashos

當你看到該文章時,代碼可能已經比文章中的又多寫了一些部分了。你能夠經過提交記錄歷史來查看歷史的代碼,我會慢慢梳理提交歷史以及項目說明文檔,爭取給每一課都準備一個可執行的代碼。固然文章中的代碼也是全的,採用複製粘貼的方式也是徹底能夠的。

若是你有興趣加入這個自制操做系統的大軍,也能夠在留言區留下您的聯繫方式,或者在 gitee 私信我您的聯繫方式。

課程規劃

本課程打算出系列課程,我寫到哪以爲能夠寫成一篇文章了就寫出來分享給你們,最終會完成一個功能全面的操做系統,我以爲這是最好的學習操做系統的方式了。因此中間遇到的各類坎也會寫進去,若是你能持續跟進,跟着我一塊寫,必然會有很好的收貨。即便沒有,交個朋友也是好的哈哈。

目前的系列包括

相關文章
相關標籤/搜索