simon пре 1 година
родитељ
комит
201a65c0a4
3 измењених фајлова са 197 додато и 0 уклоњено
  1. 6 0
      kernel/main.c
  2. 174 0
      kernel/memory.c
  3. 17 0
      kernel/memory.h

+ 6 - 0
kernel/main.c

@@ -1,6 +1,7 @@
 #include "../lib/kernel/print.h"
 #include "../lib/kernel/print.h"
 #include "debug.h"
 #include "debug.h"
 #include "init.h"
 #include "init.h"
+#include "memory.h"
 
 
 int main(void)
 int main(void)
 {
 {
@@ -9,6 +10,11 @@ int main(void)
     // asm volatile("sti"); // 使能中断
     // asm volatile("sti"); // 使能中断
     // ASSERT(1 == 2);
     // ASSERT(1 == 2);
 
 
+    void *addr = get_kernel_pages(4);
+    put_str("\n  get_kernel_page start vaddr is ");
+    put_int((uintptr_t)addr);
+    put_str("\n");
+
     while (1)
     while (1)
         ;
         ;
     return 0;
     return 0;

+ 174 - 0
kernel/memory.c

@@ -1,5 +1,7 @@
 #include "memory.h"
 #include "memory.h"
+#include "debug.h"
 #include "../lib/kernel/print.h"
 #include "../lib/kernel/print.h"
+#include "../lib/string.h"
 
 
 #define PG_SIZE 4096 // 1 页 = 4KB = 4096 字节
 #define PG_SIZE 4096 // 1 页 = 4KB = 4096 字节
 /************************ 位图地址 ****************************************
 /************************ 位图地址 ****************************************
@@ -12,6 +14,15 @@
 /*0xc0000000 是内核从虚拟地址 3G 起,0xc0100000 指跨过低端 1MB 内存,使虚拟地址在逻辑上连续 */
 /*0xc0000000 是内核从虚拟地址 3G 起,0xc0100000 指跨过低端 1MB 内存,使虚拟地址在逻辑上连续 */
 #define K_HEAP_START 0xc0100000
 #define K_HEAP_START 0xc0100000
 
 
+#define PDE_IDX(addr) ((addr & 0xffc00000) >> 22) // 获取虚拟地址的页目录项 pde 的索引(高 10 位)
+#define PTE_IDX(addr) ((addr & 0x003ff000) >> 12) // 获取虚拟地址的页表项 pte 的索引(中间 10 位)
+
+// static functions
+static void mem_pool_init(uint32_t all_mem);                  // 初始化内存池
+static void *vaddr_get(enum pool_flags pf, uint32_t pg_cnt);  // 在 pf 指向的虚拟内存池中申请 pg_cnt 个虚拟页
+static void *palloc(struct pool *m_pool);                     // 从内存池 m_pool 中申请 1 个物理页,成功则返回页框的物理地址,失败则返回 NULL
+static void page_table_add(void *_vaddr, void *_page_pyaddr); // 在页表中添加虚拟地址 _vaddr 与物理地址 _page_pyaddr 的映射
+
 /* 内存池结构,生成两个实例用于管理内核内存池和用户内存池 */
 /* 内存池结构,生成两个实例用于管理内核内存池和用户内存池 */
 struct pool
 struct pool
 {
 {
@@ -103,4 +114,167 @@ void mem_init(void)
     uint32_t mem_bytes_total = (*(uint32_t *)(0xb00)); // 从地址 0xb00 处取出总内存数
     uint32_t mem_bytes_total = (*(uint32_t *)(0xb00)); // 从地址 0xb00 处取出总内存数
     mem_pool_init(mem_bytes_total);                    // 初始化内存池
     mem_pool_init(mem_bytes_total);                    // 初始化内存池
     put_str("mem_init done\n");
     put_str("mem_init done\n");
+}
+
+// 从内核内存池中申请 pg_cnt 个页空间,成功则返回其虚拟地址,失败则返回 NULL
+static void *vaddr_get(enum pool_flags pf, uint32_t pg_cnt)
+{
+    uintptr_t vaddr_start = 0;
+    int bit_idx_start = -1;
+    uint32_t cnt = 0;
+    if (PF_KERNEL == pf)
+    {
+        bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);
+        if (-1 == bit_idx_start)
+        {
+            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
+    {
+        // 用户内存池
+        // TODO
+    }
+    return (void *)vaddr_start;
+}
+
+/****************************************************************************
+ * 处理器处理 32 位地址的三个步骤:
+ * 1. 分段:通过段描述符找到段基址,然后加上段内偏移地址
+ * 2. 分页:通过页目录表和页表找到物理地址
+ *    (1)页目录表的索引是虚拟地址的高 10 位,处理 pde 索引,得到页表物理地址
+ *    (2)页表的索引是虚拟地址的中间 10 位,处理 pte 索引,得到普通物理页的物理地址
+ *    (3)页内偏移地址是虚拟地址的低 12 位,加上普通物理页的物理地址,得到最终物理地址
+ * 3. 访问内存
+ ****************************************************************************/
+
+// 得到虚拟地址 vaddr 对应的 pte 指针(虚拟地址)
+uint32_t *pte_ptr(uint32_t vaddr)
+{
+    // 先访问到页目录表地址(高 10 位)
+    //    最后一个页目录项保存的是页目录表的地址,我们需要让高 10 们指向最后一个页目录项,即第 1023 个页目录项
+    //    1023=0x3ff,移动到高 10 位,所以是 0xffc00000,此处是页目录表的物理地址 0x100000
+    // 找到页表地址(中间 10 位)
+    //    页表物理地址 = 页目录表的物理地址 + vaddr 的页目录项索引
+    // 在页表中找到 pte 地址
+    //    页表物理地址 + vaddr 的页表项索引 * 4
+
+    uint32_t *pte = (uint32_t *)((uintptr_t)0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr) * 4);
+    return pte;
+}
+
+// 得到虚拟地址 vaddr 对应的 pde 指针(虚拟地址)
+uint32_t *pde_ptr(uint32_t vaddr)
+{
+    // 由于最后一个页目录项中存储的是页目录物理地址,所以 32 位地址中高 20 位为 0xfffff,时表示访问到的是最后一个目录项
+    // 即获得了页目录表的物理地址
+    // 因此新虚拟地址 new_vaddr = 0xfffff000 + vaddr 的页目录项索引 * 4
+    return (uint32_t *)((uintptr_t)0xfffff000 + PDE_IDX(vaddr) * 4);
+}
+
+// 在 m_pool 指向的物理内存池中申请1个物理页,成功则返回页框的物理地址,失败则返回NULL
+static void *palloc(struct pool *m_pool)
+{
+    int bit_idx = bitmap_scan(&m_pool->pool_bitmap, 1); // 找一个物理页面
+    if (-1 == bit_idx)
+    {
+        return NULL;
+    }
+    bitmap_set(&m_pool->pool_bitmap, bit_idx, 1); // 将此位bit_idx置 1
+    uint32_t page_phyaddr = ((m_pool->phy_addr_start + bit_idx * PG_SIZE));
+    return (void *)(uintptr_t)page_phyaddr;
+}
+
+// 页表中添加虚拟地址 _vaddr 与物理地址 _page_phyaddr 的映射
+static void page_table_add(void *_vaddr, void *_page_pyaddr)
+{
+    uint32_t vaddr = (uint32_t)(uintptr_t)_vaddr, page_phyaddr = (uint32_t)(uintptr_t)_page_pyaddr;
+    uint32_t *pde = pde_ptr(vaddr);
+    uint32_t *pte = pte_ptr(vaddr);
+
+    /*********************************************************************
+     * !! 注意:
+     * !! 执行 *pte 时,会访问到空的 pte,所以确保 pte 的地址在 pde 创建之后,
+     * !! 否则会引发 page_fault。因此 *pde o 0 时,*pte 不会执行
+     ********************************************************************/
+    // 先在页目录内判断目录项的 p 位,若为 1,则表示该表已存在
+    if (*pde & 0x00000001)
+    {
+        // 页目录项和页表项的第 0 位为 P 位,为 1 表示存在
+        ASSERT(!(*pte & 0x00000001));
+        if (!(*pte & 0x00000001))
+        {
+            // 页表项不存在,创建页表项
+            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
+        }
+        else
+        { // todo: 目前应该不会执行到这里,因为上面的 ASSERT 会先执行
+            PANIC("pte repeat");
+            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
+        }
+    }
+    else // 页目录项不存在,要先创建页目录再创建页表项
+    {
+        /* 页表中用到的页框一律从内核空间分配 */
+        uint32_t pde_phyaddr = (uint32_t)(uintptr_t)palloc(&kernel_pool);
+        *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1); // 页目录项赋值页表的物理地址
+        /* 分配到的物理页地址 pde_phyaddr 对应的物理内存清 0,
+         * 避免里面的陈旧数据变成了页表项,从而让页表混乱
+         * 访问到 pde 对应的物理地址,用 pte 取高 20 位。
+         * 因为 pte 基于该 pde 对应的物理地址再寻址,把低 12 位置 0 便是该 pde 对应的物理页的起始
+         */
+        _memset((void *)((uintptr_t)pte & 0xfffff000), 0, PG_SIZE);
+        ASSERT(!(*pte & 0x00000001));
+        *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1); // 页表项赋值物理页的地址
+
+        ASSERT(!(*pte & 0x00000001));
+        *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1); // 再次检查并确认
+    }
+}
+// 分配 pg_cnt 个页空间,成功则返回虚拟地址,失败则返回 NULL
+void *malloc_page(enum pool_flags pf, uint32_t pg_cnt)
+{
+    // todo: 内核和用户空间各约 16MB,所以暂时不考虑超过 16MB 的内存申请,保守起见 15MB
+    //  pg_cnt< 15 * 1024 / 4KB = 3840 页
+    ASSERT(pg_cnt > 0 && pg_cnt < 3840);
+
+    void *vaddr_start = vaddr_get(pf, pg_cnt);
+    if (NULL == vaddr_start)
+    {
+        return NULL;
+    }
+    uint32_t vaddr = (uint32_t)(uintptr_t)vaddr_start, cnt = pg_cnt;
+    struct pool *mem_pool = (PF_KERNEL == pf ? &kernel_pool : &user_pool);
+
+    // 因为虚拟地址是连续的,但物理地址可以不连续,所以逐个做映射
+    while (cnt-- > 0)
+    {
+        void *page_phyaddr = palloc(mem_pool);
+        if (NULL == page_phyaddr)
+        {
+            // 失败时要将曾经已申请的虚拟地址和物理页全部回滚
+            return NULL;
+        }
+        page_table_add((void *)(uintptr_t)vaddr, page_phyaddr);
+        vaddr += PG_SIZE; // 下一个虚拟页
+    }
+
+    return vaddr_start;
+}
+
+// 申请内核内存,成功则返回其虚拟地址,失败则返回 NULL
+void *get_kernel_pages(uint32_t pg_cnt)
+{
+    void *vaddr = malloc_page(PF_KERNEL, pg_cnt);
+    if (NULL != vaddr)
+    {
+        // 若分配的地址不为空,则将页内存清 0 后返回
+        _memset(vaddr, 0, pg_cnt * PG_SIZE);
+    }
+    return vaddr;
 }
 }

+ 17 - 0
kernel/memory.h

@@ -16,6 +16,19 @@
 #include "../lib/stdint.h"
 #include "../lib/stdint.h"
 #include "../lib/kernel/bitmap.h"
 #include "../lib/kernel/bitmap.h"
 
 
+enum pool_flags
+{
+    PF_KERNEL = 1, // 内核内存池
+    PF_USER = 2    // 用户内存池
+};
+
+#define PG_P_1 1  // 页表项或页目录项存在属性位, 1 表示存在
+#define PG_P_0 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 virtual_addr
 {
 {
@@ -25,5 +38,9 @@ struct virtual_addr
 
 
 extern struct pool kernel_pool, user_pool; // 内核内存池和用户内存池
 extern struct pool kernel_pool, user_pool; // 内核内存池和用户内存池
 void mem_init(void);                       // 内存管理初始化
 void mem_init(void);                       // 内存管理初始化
+uint32_t *pte_ptr(uint32_t vaddr);
+uint32_t *pde_ptr(uint32_t vaddr);
+void *malloc_page(enum pool_flags pf, uint32_t pg_cnt); // 申请 pg_cnt 个虚拟页
+void *get_kernel_pages(uint32_t pg_cnt);                // 申请内核内存
 
 
 #endif // __KERNEL_MEMORY_H
 #endif // __KERNEL_MEMORY_H