本章咱們要完成初步的內存管理系統,最終簡單實現一個從內核內存池中獲取 3 頁的內存這樣一個函數實現。html
爲了讓你們清楚目前的程序進度,畫了到目前爲止的程序流程圖,以下。git
圖中紅色部分就是咱們本章的代碼在全局流程中的位置,下面藍色部分是將 malloc_page 方法樹狀拆解開來看。不考慮太多細節,本章就是完成一個能夠 從內存中分配指定頁數的內存(代碼中爲 3 頁),並將起始地址打印出來。下面咱們看看要怎樣實現這個功能。數據結構
主要代碼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 }
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 }
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 }
頭文件及其餘函數
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
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
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 在內存中的位置。右邊是最終實現的函數 get_kernel_pages 要作的三件事,即學習
下面咱們把每個關鍵部分拿出來說解,並附上關鍵代碼。ui
內存池是實現申請內存函數的基礎,主要目的就是管理一段內存,說明哪塊內存被佔用了,哪塊內存是空閒的。管理這些內存佔用狀況的數據結構,用的是 bitmap,每個比特對應着一塊 4K 的內存。spa
內存池一共分爲四個,內核的物理地址內存池、用戶的物理地址內存池、內核的虛擬地址內存池、用戶的虛擬地址內存池。操作系統
管理物理地址的內存池的結構爲 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 函數就是將這兩個結構的三個內存池變量賦好值,代碼一目瞭然,各個值就是上述內存圖中所表現的,就不展開敘述了。
該函數先是從虛擬內存池中獲取指定頁數的連續內存(vaddr_get),獲取到以後,再循環調用從物理內存池中獲取一頁一頁的物理內存(palloc),每獲取到一個物理內存,就將虛擬內存與物理內存的映射關係加入到頁表(page_table_add)。
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;
得出 所得到的虛擬地址的起始的虛擬內存地址(好繞哈哈)
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 }
很少說了,跟上面的函數實際上是如出一轍的,只不過是得到一個物理頁而不是多個,最終返回了 所得到的物理地址的起始的物理地址。
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 經過頁表創建關係,經過頁表建立關係要算出四個值
其中 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
若是你對自制一個操做系統感興趣,不妨跟隨這個系列課程看下去,甚至加入咱們,一塊兒來開發。
《操做系統真相還原》這本書真的贊!強烈推薦
當你看到該文章時,代碼可能已經比文章中的又多寫了一些部分了。你能夠經過提交記錄歷史來查看歷史的代碼,我會慢慢梳理提交歷史以及項目說明文檔,爭取給每一課都準備一個可執行的代碼。固然文章中的代碼也是全的,採用複製粘貼的方式也是徹底能夠的。
若是你有興趣加入這個自制操做系統的大軍,也能夠在留言區留下您的聯繫方式,或者在 gitee 私信我您的聯繫方式。
本課程打算出系列課程,我寫到哪以爲能夠寫成一篇文章了就寫出來分享給你們,最終會完成一個功能全面的操做系統,我以爲這是最好的學習操做系統的方式了。因此中間遇到的各類坎也會寫進去,若是你能持續跟進,跟着我一塊寫,必然會有很好的收貨。即便沒有,交個朋友也是好的哈哈。
目前的系列包括