zoukankan      html  css  js  c++  java
  • 【自制操作系统10】内存管理系统

    本章我们要完成初步的内存管理系统,最终简单实现一个从内核内存池中获取 3 页的内存这样一个函数实现。

    一、到目前为止的程序流程图

    为了让大家清楚目前的程序进度,画了到目前为止的程序流程图,如下。

    图中红色部分就是我们本章的代码在全局流程中的位置,下面蓝色部分是将 malloc_page 方法树状拆解开来看。不考虑太多细节,本章就是完成一个可以 从内存中分配指定页数的内存(代码中为 3 页),并将起始地址打印出来。下面我们看看要怎样实现这个功能。

    二、先上代码

    主要代码

     1 #include "print.h"
     2 #include "init.h"
     3 void main(void){
     4     put_str("I am kernel
    ");
     5     init_all();
     6     // 这就是我们今天主要实现的功能,从内核的内存池中申请到 3 页的内存页
     7     void* addr = get_kernel_pages(3);
     8     put_str("
     get_kernel_pages start vaddr is ");
     9     put_int((uint32_t)addr);
    10     put_str("
    ");
    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
    ");
    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("
    "); 
    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("
    ");
    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
    ");
    176 }
    177 
    178 // 初始化内存
    179 void mem_init() {
    180     put_str("mem_init start
    ");
    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
    ");
    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_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 }

    四、运行

    我们看到,我们成功调用函数,获取了 3 个内核的内存页,起始地址为 0xC0100000

    写在最后:开源项目和课程规划

    如果你对自制一个操作系统感兴趣,不妨跟随这个系列课程看下去,甚至加入我们(下方有公众号和小助手微信),一起来开发。

    参考书籍

    《操作系统真相还原》这本书真的赞!强烈推荐

    项目开源

    项目开源地址:https://gitee.com/sunym1993/flashos

    当你看到该文章时,代码可能已经比文章中的又多写了一些部分了。你可以通过提交记录历史来查看历史的代码,我会慢慢梳理提交历史以及项目说明文档,争取给每一课都准备一个可执行的代码。当然文章中的代码也是全的,采用复制粘贴的方式也是完全可以的。

    如果你有兴趣加入这个自制操作系统的大军,也可以在留言区留下您的联系方式,或者在 gitee 私信我您的联系方式。

    课程规划

    本课程打算出系列课程,我写到哪觉得可以写成一篇文章了就写出来分享给大家,最终会完成一个功能全面的操作系统,我觉得这是最好的学习操作系统的方式了。所以中间遇到的各种坎也会写进去,如果你能持续跟进,跟着我一块写,必然会有很好的收货。即使没有,交个朋友也是好的哈哈。

    目前的系列包括

  • 相关阅读:
    对于edu近期bug的debug思路(等待放假归来验证)
    CLR最近异步操作和多线程的学习心的
    【友晶科技TERASIC】【翻译】SOC FPGA的SD卡image制作——2. 硬件介绍
    【友晶科技TERASIC】【翻译】SOC FPGA的SD卡image制作——7. 配置和编译 Linux 内核
    【友晶科技TERASIC】【翻译】SOC FPGA的SD卡image制作——1.介绍
    【友晶科技TERASIC】【翻译】SOC FPGA的SD卡image制作——4. 配置和编译 UBoot
    【友晶科技TERASIC】【翻译】SOC FPGA的SD卡image制作——5. 生成并编译设备树
    【友晶科技TERASIC】【翻译】SOC FPGA的SD卡image制作——6.先创建一张 SD 卡并测试设计
    (二)【OpenCL入门教程】OpenCL 简介
    (一)【OpenCL入门教程】异构计算的概念
  • 原文地址:https://www.cnblogs.com/flashsun/p/12350366.html
Copyright © 2011-2022 走看看