From 51bc465a250efecc7f1f4d646784df6c248c0daa Mon Sep 17 00:00:00 2001 From: fslongjin Date: Thu, 11 Aug 2022 22:15:53 +0800 Subject: [PATCH] =?UTF-8?q?new:=20=E6=98=A0=E5=B0=84vma?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kernel/common/glib.h | 2 +- kernel/mm/Makefile | 5 +- kernel/mm/internal.h | 29 ++++++++++++ kernel/mm/mm-types.h | 5 +- kernel/mm/mm.c | 64 ------------------------- kernel/mm/mm.h | 35 ++++++++++---- kernel/mm/mmap.c | 104 +++++++++++++++++++++++++++++++++++++++-- kernel/mm/utils.c | 109 +++++++++++++++++++++++++++++++++++++++++++ kernel/mm/vma.c | 61 +++++++++++++++++++++--- 9 files changed, 326 insertions(+), 88 deletions(-) create mode 100644 kernel/mm/internal.h create mode 100644 kernel/mm/utils.c diff --git a/kernel/common/glib.h b/kernel/common/glib.h index 86292f5c..4b48b18f 100644 --- a/kernel/common/glib.h +++ b/kernel/common/glib.h @@ -78,7 +78,7 @@ ul round(double x) * @param _align * @return ul 对齐后的地址 */ -ul ALIGN(const ul addr, const ul _align) +static __always_inline ul ALIGN(const ul addr, const ul _align) { return (ul)((addr + _align - 1) & (~(_align - 1))); } diff --git a/kernel/mm/Makefile b/kernel/mm/Makefile index fa8edb1c..cb9b69ea 100644 --- a/kernel/mm/Makefile +++ b/kernel/mm/Makefile @@ -2,7 +2,7 @@ CFLAGS += -I . -all:mm.o slab.o mm-stat.o vma.o mmap.o +all:mm.o slab.o mm-stat.o vma.o mmap.o utils.o mm.o: mm.c gcc $(CFLAGS) -c mm.c -o mm.o @@ -18,3 +18,6 @@ vma.o: vma.c mmap.o: mmap.c gcc $(CFLAGS) -c mmap.c -o mmap.o + +utils.o: utils.c + gcc $(CFLAGS) -c utils.c -o utils.o \ No newline at end of file diff --git a/kernel/mm/internal.h b/kernel/mm/internal.h new file mode 100644 index 00000000..20fc1020 --- /dev/null +++ b/kernel/mm/internal.h @@ -0,0 +1,29 @@ +#pragma once + +#include "mm.h" + +/** + * @brief 将vma结构体插入mm_struct的链表之中 + * + * @param mm 内存空间分布结构体 + * @param vma 待插入的VMA结构体 + * @param prev 链表的前一个结点 + */ +void __vma_link_list(struct mm_struct * mm, struct vm_area_struct * vma, struct vm_area_struct * prev); + +/** + * @brief 将vma给定结构体从vma链表的结点之中删除 + * + * @param mm 内存空间分布结构体 + * @param vma 待插入的VMA结构体 + */ +void __vma_unlink_list(struct mm_struct * mm, struct vm_area_struct * vma); + +/** + * @brief 获取指定虚拟地址处映射的物理地址 + * + * @param mm 内存空间分布结构体 + * @param vaddr 虚拟地址 + * @return uint64_t 已映射的物理地址 + */ +uint64_t __mm_get_paddr(struct mm_struct * mm, uint64_t vaddr); diff --git a/kernel/mm/mm-types.h b/kernel/mm/mm-types.h index e9fa9b7f..642c6390 100644 --- a/kernel/mm/mm-types.h +++ b/kernel/mm/mm-types.h @@ -2,6 +2,7 @@ #include struct mm_struct; +typedef uint64_t vm_flags_t; /** * @brief 内存页表结构体 @@ -33,13 +34,13 @@ typedef struct */ struct vm_area_struct { - struct List list; // 循环链表结构体 + struct vm_area_struct *vm_prev, *vm_next; // 虚拟内存区域的范围是一个左闭右开的区间:[vm_start, vm_end) uint64_t vm_start; // 区域的起始地址 uint64_t vm_end; // 区域的结束地址 struct mm_struct *vm_mm; // 虚拟内存区域对应的mm结构体 - uint64_t vm_flags; // 虚拟内存区域的标志位, 具体可选值请见mm.h + vm_flags_t vm_flags; // 虚拟内存区域的标志位, 具体可选值请见mm.h struct vm_operations_t *vm_ops; // 操作方法 uint64_t ref_count; // 引用计数 diff --git a/kernel/mm/mm.c b/kernel/mm/mm.c index fde0bbee..c86844f8 100644 --- a/kernel/mm/mm.c +++ b/kernel/mm/mm.c @@ -110,7 +110,6 @@ void mm_init() // 初始化bitmap, 先将整个bmp空间全部置位。稍后再将可用物理内存页复位。 memset(memory_management_struct.bmp, 0xff, memory_management_struct.bmp_len); io_mfence(); - kdebug("1212112"); // 初始化内存页结构 // 将页结构映射于bmp之后 memory_management_struct.pages_struct = (struct Page *)(((unsigned long)memory_management_struct.bmp + memory_management_struct.bmp_len + PAGE_4K_SIZE - 1) & PAGE_4K_MASK); @@ -120,7 +119,6 @@ void mm_init() // 将pages_struct全部清空,以备后续初始化 memset(memory_management_struct.pages_struct, 0x00, memory_management_struct.pages_struct_len); // init pages memory - kdebug("ffff"); io_mfence(); // 初始化内存区域 memory_management_struct.zones_struct = (struct Zone *)(((ul)memory_management_struct.pages_struct + memory_management_struct.pages_struct_len + PAGE_4K_SIZE - 1) & PAGE_4K_MASK); @@ -618,65 +616,3 @@ uint64_t mm_do_brk(uint64_t old_brk_end_addr, int64_t offset) return end_addr; } -/** - * @brief 检测指定地址是否已经被映射 - * - * @param page_table_phys_addr 页表的物理地址 - * @param virt_addr 要检测的地址 - * @return true 已经被映射 - * @return false - */ -bool mm_check_mapped(ul page_table_phys_addr, uint64_t virt_addr) -{ - ul *tmp; - - tmp = phys_2_virt((ul *)((ul)page_table_phys_addr & (~0xfffUL)) + ((virt_addr >> PAGE_GDT_SHIFT) & 0x1ff)); - - // pml4页表项为0 - if (*tmp == 0) - return 0; - - tmp = phys_2_virt((ul *)(*tmp & (~0xfffUL)) + ((virt_addr >> PAGE_1G_SHIFT) & 0x1ff)); - - // pdpt页表项为0 - if (*tmp == 0) - return 0; - - // 读取pdt页表项 - tmp = phys_2_virt(((ul *)(*tmp & (~0xfffUL)) + (((ul)(virt_addr) >> PAGE_2M_SHIFT) & 0x1ff))); - - // pde页表项为0 - if (*tmp == 0) - return 0; - - if (*tmp & (1 << 7)) - { - // 当前为2M物理页 - return true; - } - else - { - // 存在4级页表 - tmp = phys_2_virt(((ul *)(*tmp & (~0xfffUL)) + (((ul)(virt_addr) >> PAGE_4K_SHIFT) & 0x1ff))); - if (*tmp != 0) - return true; - else - return false; - } -} - -/** - * @brief 检测是否为有效的2M页(物理内存页) - * - * @param paddr 物理地址 - * @return int8_t 是 -> 1 - * 否 -> 0 - */ -int8_t mm_is_2M_page(uint64_t paddr) -{ - if (likely((paddr >> PAGE_2M_SHIFT) < mm_total_2M_pages)) - return 1; - else - return 0; -} -// #pragma GCC pop_options \ No newline at end of file diff --git a/kernel/mm/mm.h b/kernel/mm/mm.h index 4fdc348b..ca64e19f 100644 --- a/kernel/mm/mm.h +++ b/kernel/mm/mm.h @@ -381,6 +381,7 @@ ul set_page_attr(struct Page *page, ul flags); #define VM_IO (1 << 4) // MMIO的内存区域 #define VM_SOFTDIRTY (1 << 5) #define VM_MAYSHARE (1 << 6) // 该vma可被共享 +#define VM_USER (1 << 7) // 该vma可被用户态访问 /* VMA basic access permission flags */ #define VM_ACCESS_FLAGS (VM_READ | VM_WRITE | VM_EXEC) @@ -396,7 +397,6 @@ static inline void vma_init(struct vm_area_struct *vma, struct mm_struct *mm) memset(vma, 0, sizeof(struct vm_area_struct)); vma->vm_mm = mm; vma->vm_ops = NULL; - list_init(&vma->list); } /** @@ -422,15 +422,15 @@ static inline bool vma_is_accessible(struct vm_area_struct *vma) /** * @brief 获取一块新的vma结构体,并将其与指定的mm进行绑定 - * + * * @param mm 与VMA绑定的内存空间分布结构体 * @return struct vm_area_struct* 新的VMA */ -struct vm_area_struct * vm_area_alloc(struct mm_struct *mm); +struct vm_area_struct *vm_area_alloc(struct mm_struct *mm); /** * @brief 释放vma结构体 - * + * * @param vma 待释放的vma结构体 */ void vm_area_free(struct vm_area_struct *vma); @@ -489,14 +489,29 @@ void mm_unmap_proc_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_sta }) /** - * @brief 检测指定地址是否已经被映射 + * @brief 创建VMA,并将物理地址映射到指定的虚拟地址处 * - * @param page_table_phys_addr 页表的物理地址 - * @param virt_addr 要检测的地址 - * @return true 已经被映射 - * @return false + * @param mm 要绑定的内存空间分布结构体 + * @param vaddr 起始虚拟地址 + * @param length 长度(字节) + * @param paddr 起始物理地址 + * @param vm_flags vma的标志 + * @param vm_ops vma的操作接口 + * @return int 错误码 */ -bool mm_check_mapped(ul page_table_phys_addr, uint64_t virt_addr); +int mm_map_vma(struct mm_struct *mm, uint64_t vaddr, uint64_t length, uint64_t paddr, vm_flags_t vm_flags, struct vm_operations_t *vm_ops); + +/** + * @brief 在页表中取消指定的vma的映射 + * + * @param mm 指定的mm + * @param vma 待取消映射的vma + * @param paddr 返回的被取消映射的起始物理地址 + * @return int 返回码 + */ +int mm_umap_vma(struct mm_struct *mm, struct vm_area_struct * vma, uint64_t *paddr); + + /** * @brief 检测是否为有效的2M页(物理内存页) diff --git a/kernel/mm/mmap.c b/kernel/mm/mmap.c index eee89428..c616cc37 100644 --- a/kernel/mm/mmap.c +++ b/kernel/mm/mmap.c @@ -1,5 +1,6 @@ #include "mm.h" #include "slab.h" +#include "internal.h" #include extern uint64_t mm_total_2M_pages; @@ -72,10 +73,16 @@ int mm_map_proc_page_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_s // 计算线性地址对应的pml4页表项的地址 mm_pgt_entry_num_t pgt_num; mm_calculate_entry_num(length, &pgt_num); - // kdebug("ent1=%d ent2=%d ent3=%d, ent4=%d", pgt_num.num_PML4E, pgt_num.num_PDPTE, pgt_num.num_PDE, pgt_num.num_PTE); + // 已映射的内存大小 uint64_t length_mapped = 0; + // 对user标志位进行校正 + if (flags & PAGE_U_S) + user = true; + else + user = false; + uint64_t pml4e_id = ((virt_addr_start >> PAGE_GDT_SHIFT) & 0x1ff); uint64_t *pml4_ptr; if (is_phys) @@ -192,7 +199,6 @@ failed:; return -EFAULT; } - /** * @brief 从页表中清除虚拟地址的映射 * @@ -262,7 +268,7 @@ void mm_unmap_proc_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_sta ul *pde_ptr = pd_ptr + pde_id; // 存在4级页表 - if (unlikely(((*pde_ptr) & (1 << 7)) == 0)) + if (((*pde_ptr) & (1 << 7)) == 0) { // 存在4K页 uint64_t pte_id = (((virt_addr_start + length_unmapped) >> PAGE_4K_SHIFT) & 0x1ff); @@ -291,7 +297,7 @@ void mm_unmap_proc_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_sta // 3级页表已经空了,释放页表 if (unlikely(mm_check_page_table(pd_ptr)) == 0) - kfree(pd_ptr); + kfree(pd_ptr); } // 2级页表已经空了,释放页表 if (unlikely(mm_check_page_table(pdpt_ptr)) == 0) @@ -299,3 +305,93 @@ void mm_unmap_proc_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_sta } flush_tlb(); } + +/** + * @brief 创建VMA,并将物理地址映射到指定的虚拟地址处 + * + * @param mm 要绑定的内存空间分布结构体 + * @param vaddr 起始虚拟地址 + * @param length 长度(字节) + * @param paddr 起始物理地址 + * @param vm_flags vma的标志 + * @param vm_ops vma的操作接口 + * @return int 错误码 + */ +int mm_map_vma(struct mm_struct *mm, uint64_t vaddr, uint64_t length, uint64_t paddr, vm_flags_t vm_flags, struct vm_operations_t *vm_ops) +{ + int retval = 0; + struct vm_area_struct *vma = vm_area_alloc(mm); + if (unlikely(vma == NULL)) + return -ENOMEM; + vma->vm_ops = vm_ops; + vma->vm_flags = vm_flags; + vma->vm_start = vaddr; + vma->vm_end = vaddr + length; + + // 将VMA加入链表 + __vma_link_list(mm, vma, mm->vmas); + uint64_t len_4k = length % PAGE_2M_SIZE; + uint64_t len_2m = length - len_4k; + + // ==== 将地址映射到页表 + /* + todo: 限制页面的读写权限 + */ + + // 先映射2M页 + if (likely(len_2m > 0)) + { + uint64_t page_flags = 0; + if (vm_flags & VM_USER) + page_flags = PAGE_USER_PAGE; + else + page_flags = PAGE_KERNEL_PAGE; + // 这里直接设置user标志位为false,因为该函数内部会对其进行自动校正 + retval = mm_map_proc_page_table((uint64_t)mm->pgd, true, vaddr, paddr, len_2m, page_flags, false, false, false); + if (unlikely(retval != 0)) + goto failed; + } + + if (likely(len_4k > 0)) + { + len_4k = ALIGN(len_4k, PAGE_4K_SIZE); + + uint64_t page_flags = 0; + if (vm_flags & VM_USER) + page_flags = PAGE_USER_4K_PAGE; + else + page_flags = PAGE_KERNEL_4K_PAGE; + // 这里直接设置user标志位为false,因为该函数内部会对其进行自动校正 + retval = mm_map_proc_page_table((uint64_t)mm->pgd, true, vaddr + len_2m, paddr + len_2m, len_4k, page_flags, false, false, true); + if (unlikely(retval != 0)) + goto failed; + } + + flush_tlb(); + return 0; +failed:; + __vma_unlink_list(mm, vma); + vm_area_free(vma); + return retval; +} + +/** + * @brief 在页表中取消指定的vma的映射 + * + * @param mm 指定的mm + * @param vma 待取消映射的vma + * @param paddr 返回的被取消映射的起始物理地址 + * @return int 返回码 + */ +int mm_umap_vma(struct mm_struct *mm, struct vm_area_struct *vma, uint64_t *paddr) +{ + // 确保vma对应的mm与指定的mm相一致 + if (unlikely(vma->vm_mm != mm)) + return -EINVAL; + + if (paddr != NULL) + *paddr = __mm_get_paddr(mm, vma->vm_start); + + mm_unmap_proc_table((uint64_t)mm->pgd, true, vma->vm_start, vma->vm_end - vma->vm_start); + return 0; +} \ No newline at end of file diff --git a/kernel/mm/utils.c b/kernel/mm/utils.c new file mode 100644 index 00000000..9f9cd048 --- /dev/null +++ b/kernel/mm/utils.c @@ -0,0 +1,109 @@ +#include "internal.h" + +extern uint64_t mm_total_2M_pages; + +/** + * @brief 获取指定虚拟地址处映射的物理地址 + * + * @param mm 内存空间分布结构体 + * @param vaddr 虚拟地址 + * @return uint64_t 已映射的物理地址 + */ +uint64_t __mm_get_paddr(struct mm_struct *mm, uint64_t vaddr) +{ + ul *tmp; + + tmp = phys_2_virt((ul *)(((ul)mm->pgd) & (~0xfffUL)) + ((vaddr >> PAGE_GDT_SHIFT) & 0x1ff)); + + // pml4页表项为0 + if (*tmp == 0) + return 0; + + tmp = phys_2_virt((ul *)(*tmp & (~0xfffUL)) + ((vaddr >> PAGE_1G_SHIFT) & 0x1ff)); + + // pdpt页表项为0 + if (*tmp == 0) + return 0; + + // 读取pdt页表项 + tmp = phys_2_virt(((ul *)(*tmp & (~0xfffUL)) + (((ul)(vaddr) >> PAGE_2M_SHIFT) & 0x1ff))); + + // pde页表项为0 + if (*tmp == 0) + return 0; + + if (*tmp & (1 << 7)) + { + // 当前为2M物理页 + return (*tmp) & (~0x1fffUL); + } + else + { + // 存在4级页表 + tmp = phys_2_virt(((ul *)(*tmp & (~0xfffUL)) + (((ul)(vaddr) >> PAGE_4K_SHIFT) & 0x1ff))); + + return (*tmp) & (~0x1ffUL); + } +} + +/** + * @brief 检测指定地址是否已经被映射 + * + * @param page_table_phys_addr 页表的物理地址 + * @param virt_addr 要检测的地址 + * @return true 已经被映射 + * @return false + */ +bool mm_check_mapped(ul page_table_phys_addr, uint64_t virt_addr) +{ + ul *tmp; + + tmp = phys_2_virt((ul *)((ul)page_table_phys_addr & (~0xfffUL)) + ((virt_addr >> PAGE_GDT_SHIFT) & 0x1ff)); + + // pml4页表项为0 + if (*tmp == 0) + return 0; + + tmp = phys_2_virt((ul *)(*tmp & (~0xfffUL)) + ((virt_addr >> PAGE_1G_SHIFT) & 0x1ff)); + + // pdpt页表项为0 + if (*tmp == 0) + return 0; + + // 读取pdt页表项 + tmp = phys_2_virt(((ul *)(*tmp & (~0xfffUL)) + (((ul)(virt_addr) >> PAGE_2M_SHIFT) & 0x1ff))); + + // pde页表项为0 + if (*tmp == 0) + return 0; + + if (*tmp & (1 << 7)) + { + // 当前为2M物理页 + return true; + } + else + { + // 存在4级页表 + tmp = phys_2_virt(((ul *)(*tmp & (~0xfffUL)) + (((ul)(virt_addr) >> PAGE_4K_SHIFT) & 0x1ff))); + if (*tmp != 0) + return true; + else + return false; + } +} + +/** + * @brief 检测是否为有效的2M页(物理内存页) + * + * @param paddr 物理地址 + * @return int8_t 是 -> 1 + * 否 -> 0 + */ +int8_t mm_is_2M_page(uint64_t paddr) +{ + if (likely((paddr >> PAGE_2M_SHIFT) < mm_total_2M_pages)) + return 1; + else + return 0; +} diff --git a/kernel/mm/vma.c b/kernel/mm/vma.c index c0b6549e..0e264532 100644 --- a/kernel/mm/vma.c +++ b/kernel/mm/vma.c @@ -1,28 +1,77 @@ #include "mm.h" #include "slab.h" +#include "internal.h" /** * @brief 获取一块新的vma结构体,并将其与指定的mm进行绑定 - * + * * @param mm 与VMA绑定的内存空间分布结构体 * @return struct vm_area_struct* 新的VMA */ -struct vm_area_struct * vm_area_alloc(struct mm_struct *mm) +struct vm_area_struct *vm_area_alloc(struct mm_struct *mm) { - struct vm_area_struct * vma = (struct vm_area_struct *)kmalloc(sizeof(struct vm_area_struct),0); - if(vma) + struct vm_area_struct *vma = (struct vm_area_struct *)kmalloc(sizeof(struct vm_area_struct), 0); + if (vma) vma_init(vma, mm); return vma; } /** * @brief 释放vma结构体 - * + * * @param vma 待释放的vma结构体 */ void vm_area_free(struct vm_area_struct *vma) { - if(list_empty(&vma->list)) // 如果当前是剩余的最后一个vma + if (vma->vm_prev == NULL && vma->vm_next == NULL) // 如果当前是剩余的最后一个vma vma->vm_mm->vmas = NULL; kfree(vma); +} + +/** + * @brief 将vma结构体插入mm_struct的链表之中 + * + * @param mm 内存空间分布结构体 + * @param vma 待插入的VMA结构体 + * @param prev 链表的前一个结点 + */ +void __vma_link_list(struct mm_struct *mm, struct vm_area_struct *vma, struct vm_area_struct *prev) +{ + struct vm_area_struct *next = NULL; + vma->vm_prev = prev; + if (prev) // 若指定了前一个结点,则直接连接 + { + next = prev->vm_next; + prev->vm_next = vma; + } + else // 否则将vma直接插入到给定的mm的vma链表之中 + { + next = mm->vmas; + mm->vmas = vma; + } + + vma->vm_next = next; + + if (next != NULL) + next->vm_prev = vma; +} + +/** + * @brief 将vma给定结构体从vma链表的结点之中删除 + * + * @param mm 内存空间分布结构体 + * @param vma 待插入的VMA结构体 + */ +void __vma_unlink_list(struct mm_struct *mm, struct vm_area_struct *vma) +{ + struct vm_area_struct *prev, *next; + next = vma->vm_next; + prev = vma->vm_prev; + if (prev) + prev->vm_next = next; + else // 当前vma是链表中的第一个vma + mm->vmas = next; + + if (next) + next->vm_prev = prev; } \ No newline at end of file