Browse Source

Combine vma (#42)

* 合并vma

* 调整vma映射,修复bug

* 删除注释

Co-authored-by: fslongjin <longjin@RinGoTek.cn>
Co-authored-by: houmkh <100781004+houmkh@users.noreply.github.com>
houmkh 2 years ago
parent
commit
be9ac3d58b
7 changed files with 113 additions and 42 deletions
  1. 8 1
      kernel/mm/internal.h
  2. 2 1
      kernel/mm/mm.c
  3. 3 1
      kernel/mm/mm.h
  4. 67 26
      kernel/mm/mmap.c
  5. 1 1
      kernel/mm/slab.c
  6. 22 6
      kernel/mm/vma.c
  7. 10 6
      kernel/process/process.c

+ 8 - 1
kernel/mm/internal.h

@@ -2,6 +2,10 @@
 
 #include "mm.h"
 
+
+// 当vma被成功合并后的返回值
+#define __VMA_MERGED 1
+
 /**
  * @brief 将vma结构体插入mm_struct的链表之中
  *
@@ -69,4 +73,7 @@ int __anon_vma_del(struct vm_area_struct *vma);
  * @param paddr 物理地址
  * @return struct Page* 创建成功的page
  */
-struct Page* __create_mmio_page_struct(uint64_t paddr);
+struct Page* __create_mmio_page_struct(uint64_t paddr);
+
+// 判断给定的两个值是否跨越了2M边界
+#define CROSS_2M_BOUND(val1, val2) ((val1 & PAGE_2M_MASK) != (val2 & PAGE_2M_MASK))

+ 2 - 1
kernel/mm/mm.c

@@ -615,7 +615,8 @@ uint64_t mm_do_brk(uint64_t old_brk_end_addr, int64_t offset)
         {
             struct vm_area_struct *vma = NULL;
             mm_create_vma(current_pcb->mm, i, PAGE_2M_SIZE, VM_USER | VM_ACCESS_FLAGS, NULL, &vma);
-            mm_map_vma(vma, alloc_pages(ZONE_NORMAL, 1, PAGE_PGT_MAPPED)->addr_phys);
+            mm_map(current_pcb->mm, i, PAGE_2M_SIZE, alloc_pages(ZONE_NORMAL, 1, PAGE_PGT_MAPPED)->addr_phys);
+            // mm_map_vma(vma, alloc_pages(ZONE_NORMAL, 1, PAGE_PGT_MAPPED)->addr_phys, 0, PAGE_2M_SIZE);
         }
         current_pcb->mm->brk_end = end_addr;
     }

+ 3 - 1
kernel/mm/mm.h

@@ -462,9 +462,11 @@ int mm_create_vma(struct mm_struct *mm, uint64_t vaddr, uint64_t length, vm_flag
  *
  * @param vma 要进行映射的VMA结构体
  * @param paddr 起始物理地址
+ * @param offset 要映射的起始位置在vma中的偏移量
+ * @param length 要映射的长度
  * @return int 错误码
  */
-int mm_map_vma(struct vm_area_struct *vma, uint64_t paddr);
+int mm_map_vma(struct vm_area_struct *vma, uint64_t paddr, uint64_t offset, uint64_t length);
 
 /**
  * @brief 在页表中映射物理地址到指定的虚拟地址(需要页表中已存在对应的vma)

+ 67 - 26
kernel/mm/mmap.c

@@ -2,6 +2,7 @@
 #include "slab.h"
 #include "internal.h"
 #include <common/compiler.h>
+#include <debug/bug.h>
 
 extern uint64_t mm_total_2M_pages;
 
@@ -319,6 +320,10 @@ void mm_unmap_proc_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_sta
 int mm_create_vma(struct mm_struct *mm, uint64_t vaddr, uint64_t length, vm_flags_t vm_flags, struct vm_operations_t *vm_ops, struct vm_area_struct **res_vma)
 {
     int retval = 0;
+    // 输入的地址如果不是4K对齐,则报错
+    if (unlikely(vaddr & (PAGE_4K_SIZE - 1)))
+        return -EINVAL;
+
     struct vm_area_struct *vma = vm_area_alloc(mm);
     if (unlikely(vma == NULL))
         return -ENOMEM;
@@ -328,12 +333,16 @@ int mm_create_vma(struct mm_struct *mm, uint64_t vaddr, uint64_t length, vm_flag
     vma->vm_end = vaddr + length;
     // 将VMA加入mm的链表
     retval = vma_insert(mm, vma);
-    if (retval == -EEXIST) // 之前已经存在了相同的vma,直接返回
+    if (retval == -EEXIST || retval == __VMA_MERGED) // 之前已经存在了相同的vma,直接返回
     {
         *res_vma = vma_find(mm, vma->vm_start);
         kfree(vma);
-        return -EEXIST;
+        if (retval == -EEXIST)
+            return -EEXIST;
+        else
+            return 0;
     }
+
     if (res_vma != NULL)
         *res_vma = vma;
     return 0;
@@ -344,11 +353,16 @@ int mm_create_vma(struct mm_struct *mm, uint64_t vaddr, uint64_t length, vm_flag
  *
  * @param vma 要进行映射的VMA结构体
  * @param paddr 起始物理地址
+ * @param offset 要映射的起始位置在vma中的偏移量
+ * @param length 要映射的长度
  * @return int 错误码
  */
-int mm_map_vma(struct vm_area_struct *vma, uint64_t paddr)
+int mm_map_vma(struct vm_area_struct *vma, uint64_t paddr, uint64_t offset, uint64_t length)
 {
-    int retval = 0;
+   int retval = 0;
+    uint64_t mapped = 0;
+    BUG_ON((offset & (PAGE_4K_SIZE - 1)) != 0);
+    length = PAGE_4K_ALIGN(length); // 将length按照4K进行对齐
     // 获取物理地址对应的页面
     struct Page *pg;
     uint64_t page_flags = 0;
@@ -374,17 +388,39 @@ int mm_map_vma(struct vm_area_struct *vma, uint64_t paddr)
     // 将anon vma与vma进行绑定
     __anon_vma_add(pg->anon_vma, vma);
     barrier();
-
-    uint64_t length = vma->vm_end - vma->vm_start;
-    // ==== 将地址映射到页表 ====
-    uint64_t len_4k = length % PAGE_2M_SIZE;
-    uint64_t len_2m = length - len_4k;
+    // 长度超过界限
+    BUG_ON(vma->vm_start + offset + length > vma->vm_end);
 
     /*
         todo: 限制页面的读写权限
     */
-    // kdebug("len2m=%d", len_2m);
-    // 先映射2M页
+
+    // ==== 将地址映射到页表 ====
+    uint64_t len_4k, len_2m;
+    // 将地址使用4k页填补,使得地址按照2M对齐
+    len_4k = PAGE_2M_ALIGN(vma->vm_start + offset) - (vma->vm_start + offset);
+    if (len_4k > 0)
+        len_4k = (len_4k > length) ? length : len_4k;
+    if (len_4k)
+    {
+        if (vma->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)vma->vm_mm->pgd, true, vma->vm_start + offset, paddr, len_4k, page_flags, false, false, true);
+        if (unlikely(retval != 0))
+            goto failed;
+
+        mapped += len_4k;
+        length -= len_4k;
+    }
+
+    len_4k = length % PAGE_2M_SIZE;
+    len_2m = length / PAGE_2M_SIZE;
+
+    // 映射连续的2M页
     if (likely(len_2m > 0))
     {
         if (vma->vm_flags & VM_USER)
@@ -392,31 +428,32 @@ int mm_map_vma(struct vm_area_struct *vma, uint64_t paddr)
         else
             page_flags |= PAGE_KERNEL_PAGE;
         // 这里直接设置user标志位为false,因为该函数内部会对其进行自动校正
-        retval = mm_map_proc_page_table((uint64_t)vma->vm_mm->pgd, true, vma->vm_start, paddr, len_2m, page_flags, false, false, false);
+        retval = mm_map_proc_page_table((uint64_t)vma->vm_mm->pgd, true, vma->vm_start + offset + mapped, paddr + mapped, len_2m, page_flags, false, false, false);
+
         if (unlikely(retval != 0))
             goto failed;
+        mapped += len_2m;
     }
-
+    // 最后再使用4K页填补
     if (likely(len_4k > 0))
     {
-        len_4k = ALIGN(len_4k, PAGE_4K_SIZE);
 
         if (vma->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)vma->vm_mm->pgd, true, vma->vm_start + len_2m, paddr + len_2m, len_4k, page_flags, false, false, true);
+        retval = mm_map_proc_page_table((uint64_t)vma->vm_mm->pgd, true, vma->vm_start + offset + mapped, paddr + mapped, len_4k, page_flags, false, false, true);
+
         if (unlikely(retval != 0))
             goto failed;
+        mapped += len_4k;
     }
 
     if (vma->vm_flags & VM_IO)
         vma->page_offset = 0;
-    else
-    { // 计算当前vma的起始地址在对应的物理页中的偏移量
-        vma->page_offset = paddr - (paddr & PAGE_2M_MASK);
-    }
+
     flush_tlb();
     return 0;
 failed:;
@@ -436,6 +473,7 @@ failed:;
 int mm_map(struct mm_struct *mm, uint64_t vaddr, uint64_t length, uint64_t paddr)
 {
     int retval = 0;
+    uint64_t offset = 0;
     for (uint64_t mapped = 0; mapped < length;)
     {
 
@@ -446,17 +484,20 @@ int mm_map(struct mm_struct *mm, uint64_t vaddr, uint64_t length, uint64_t paddr
             return -EINVAL;
         }
 
-        if (unlikely(vma->vm_start != (vaddr + mapped)))
-        {
-            kerror("Map addr failed: addr_start is not equal to current: %#018lx.", vaddr + mapped);
-            return -EINVAL;
-        }
+        // if (unlikely(vma->vm_start != (vaddr + mapped)))
+        // {
+        //     kerror("Map addr failed: addr_start is not equal to current: %#018lx.", vaddr + mapped);
+        //     return -EINVAL;
+        // }
 
-        retval = mm_map_vma(vma, paddr + mapped);
+        offset = vaddr + mapped - vma->vm_start;
+        uint64_t m_len = vma->vm_end - vma->vm_start - offset;
+        // kdebug("start=%#018lx, offset=%ld", vma->vm_start, offset);
+        retval = mm_map_vma(vma, paddr + mapped, offset, m_len);
         if (unlikely(retval != 0))
             goto failed;
 
-        mapped += vma->vm_end - vma->vm_start;
+        mapped += m_len;
     }
     return 0;
 failed:;

+ 1 - 1
kernel/mm/slab.c

@@ -708,6 +708,6 @@ unsigned long kfree(void *address)
 
         } while (slab_obj_ptr != kmalloc_cache_group[i].cache_pool_entry);
     }
-    kBUG("kfree(): Can't free memory.");
+    kBUG("kfree(): Can't free memory. address=%#018lx", address);
     return ECANNOT_FREE_MEM;
 }

+ 22 - 6
kernel/mm/vma.c

@@ -122,21 +122,35 @@ int vma_insert(struct mm_struct *mm, struct vm_area_struct *vma)
 {
 
     struct vm_area_struct *prev;
+
     prev = vma_find(mm, vma->vm_start);
+    
     if (prev && prev->vm_start <= vma->vm_start && prev->vm_end >= vma->vm_end)
     {
         // 已经存在了相同的vma
         return -EEXIST;
     }
-    else if (prev && (prev->vm_start == vma->vm_start || prev->vm_end == vma->vm_end)) // 暂时不支持扩展vma
+    // todo: bugfix: 这里的第二种情况貌似从来不会满足
+    else if (prev && ((vma->vm_start >= prev->vm_start && vma->vm_start <= prev->vm_end) || (prev->vm_start <= vma->vm_end && prev->vm_start >= vma->vm_start)))    
     {
-        kwarn("Not support: expand vma");
-        return -ENOTSUP;
+        //部分重叠
+        if ((!CROSS_2M_BOUND(vma->vm_start, prev->vm_start)) && (!CROSS_2M_BOUND(vma->vm_end, prev->vm_end))&& vma->vm_end)
+        {
+            //合并vma 并改变链表vma的范围
+            kdebug("before combining vma:vm_start = %#018lx, vm_end = %#018lx\n", vma->vm_start, vma->vm_end);
+
+            prev->vm_start = (vma->vm_start < prev->vm_start )? vma->vm_start : prev->vm_start;
+            prev->vm_end = (vma->vm_end > prev->vm_end) ? vma->vm_end : prev->vm_end;
+            // 计算page_offset
+            prev->page_offset = prev->vm_start - (prev->vm_start & PAGE_2M_MASK);
+            kdebug("combined vma:vm_start = %#018lx, vm_end = %#018lx\nprev:vm_start = %018lx, vm_end = %018lx\n", vma->vm_start, vma->vm_end, prev->vm_start, prev->vm_end);
+            kinfo("vma has same part\n");
+            return __VMA_MERGED;
+        }
     }
 
-    prev = vma_find(mm, vma->vm_end);
-    if (prev)
-        prev = prev->vm_prev;
+    // prev = vma_find(mm, vma->vm_start);
+
     if (prev == NULL) // 要将当前vma插入到链表的尾部
     {
         struct vm_area_struct *ptr = mm->vmas;
@@ -151,6 +165,8 @@ int vma_insert(struct mm_struct *mm, struct vm_area_struct *vma)
             }
         }
     }
+    else
+        prev = prev->vm_prev;
     __vma_link_list(mm, vma, prev);
     return 0;
 }

+ 10 - 6
kernel/process/process.c

@@ -259,7 +259,7 @@ static int process_load_elf_file(struct pt_regs *regs, char *path)
         pos = phdr->p_offset;
 
         uint64_t virt_base = 0;
-        uint64_t beginning_offset = 0;       // 由于页表映射导致的virtbase与实际的p_vaddr之间的偏移量
+        uint64_t beginning_offset = 0; // 由于页表映射导致的virtbase与实际的p_vaddr之间的偏移量
 
         if (remain_mem_size >= PAGE_2M_SIZE) // 接下来存在映射2M页的情况,因此将vaddr按2M向下对齐
             virt_base = phdr->p_vaddr & PAGE_2M_MASK;
@@ -278,11 +278,13 @@ static int process_load_elf_file(struct pt_regs *regs, char *path)
                 uint64_t pa = alloc_pages(ZONE_NORMAL, 1, PAGE_PGT_MAPPED)->addr_phys;
                 struct vm_area_struct *vma = NULL;
                 int ret = mm_create_vma(current_pcb->mm, virt_base, PAGE_2M_SIZE, VM_USER | VM_ACCESS_FLAGS, NULL, &vma);
+                
                 // 防止内存泄露
                 if (ret == -EEXIST)
                     free_pages(Phy_to_2M_Page(pa), 1);
                 else
-                    mm_map_vma(vma, pa);
+                    mm_map(current_pcb->mm, virt_base, PAGE_2M_SIZE, pa);
+                // mm_map_vma(vma, pa, 0, PAGE_2M_SIZE);
                 io_mfence();
                 memset((void *)virt_base, 0, PAGE_2M_SIZE);
                 map_size = PAGE_2M_SIZE;
@@ -298,10 +300,12 @@ static int process_load_elf_file(struct pt_regs *regs, char *path)
 
                     struct vm_area_struct *vma = NULL;
                     int val = mm_create_vma(current_pcb->mm, virt_base + off, PAGE_4K_SIZE, VM_USER | VM_ACCESS_FLAGS, NULL, &vma);
+                    // kdebug("virt_base=%#018lx", virt_base + off);
                     if (val == -EEXIST)
                         kfree(phys_2_virt(paddr));
                     else
-                        mm_map_vma(vma, paddr);
+                        mm_map(current_pcb->mm, virt_base + off, PAGE_4K_SIZE, paddr);
+                    // mm_map_vma(vma, paddr, 0, PAGE_4K_SIZE);
                     io_mfence();
                     memset((void *)(virt_base + off), 0, PAGE_4K_SIZE);
                 }
@@ -335,7 +339,7 @@ static int process_load_elf_file(struct pt_regs *regs, char *path)
         if (val == -EEXIST)
             free_pages(Phy_to_2M_Page(pa), 1);
         else
-            mm_map_vma(vma, pa);
+            mm_map_vma(vma, pa, 0, PAGE_2M_SIZE);
     }
 
     // 清空栈空间
@@ -957,7 +961,7 @@ uint64_t process_copy_mm(uint64_t clone_flags, struct process_control_block *pcb
                 if (unlikely(ret == -EEXIST))
                     free_pages(Phy_to_2M_Page(pa), 1);
                 else
-                    mm_map_vma(new_vma, pa);
+                    mm_map_vma(new_vma, pa, 0, PAGE_2M_SIZE);
 
                 memcpy((void *)phys_2_virt(pa), (void *)(vma->vm_start + i * PAGE_2M_SIZE), (vma_size >= PAGE_2M_SIZE) ? PAGE_2M_SIZE : vma_size);
                 vma_size -= PAGE_2M_SIZE;
@@ -974,7 +978,7 @@ uint64_t process_copy_mm(uint64_t clone_flags, struct process_control_block *pcb
             if (unlikely(ret == -EEXIST))
                 kfree((void *)va);
             else
-                mm_map_vma(new_vma, virt_2_phys(va));
+                mm_map_vma(new_vma, virt_2_phys(va), 0, map_size);
 
             memcpy((void *)va, (void *)vma->vm_start, vma_size);
         }