## 内存管理

内存的分配和释放我们以页为单位进行管理，采用位图这一数据结构，每个位标志一张页是否已分配，页表的映射采用Sv39页表映射，通过MMU来处理虚拟内存到物理内存的转换

### 初始化流程

内存初始化的流程主要在`mmu_map.c`和`page_alloc.c`中进行。

1. 首先，在`page_alloc.c`中的`mem_init()`函数中进行内存初始化。这个函数首先将内存的起始和结束地址对齐到页的边界，然后计算出总的可用页数。这个函数还会将所有的页标记为未使用。

2. 然后，在`mmu_map.c`中的`mmu_init()`函数中进行MMU的初始化。这个函数首先清零页全局目录（PGD），然后创建标识映射和虚拟UART映射，最后启用MMU。

   - 创建标识映射是通过`create_identity_mapping()`函数实现的。这个函数会将内核代码段的虚拟地址映射到相同的物理地址，然后将剩余的虚拟地址映射到物理地址。

   - 创建虚拟UART映射是通过`create_VIRT_UART_mapping()`函数实现的。这个函数会将虚拟UART的地址映射到物理内存中。

3. 在创建映射的过程中，会用到`create_pgd_mapping()`，`create_pmd_mapping()`和`create_pte_mapping()`这三个函数。这些函数的工作原理都是一样的，它们都是通过循环，将虚拟地址和物理地址一一对应起来。

4. 在创建映射的过程中，如果需要新的页表，就会调用`alloc_pgtable()`函数来分配。这个函数会调用`page_alloc()`函数来分配一个新的页，然后将这个页的内容全部清零，最后返回这个页的地址。

5. 最后，`enable_mmu()`函数会启用MMU，这样就完成了初始化的过程。



### 内存分配

在`page_alloc.c`文件中，`page_alloc`函数用于分配内存页，`more_page_alloc`函数用于连续分配多页内存。目前主要使用`more_page_alloc`函数,通过检索位图结构来分配连续多页的内存



### 内存释放

在`page_alloc.c`文件中，`page_free`和`more_page_free`函数用于释放内存页。`page_free`函数释放指定数量的内存页，`more_page_free`函数释放指定地址开始的连续内存页。目前主要使用more_page_free函数

### 页表映射

采用Sv39页表映射

在`mmu_map.c`文件中，实现了内存管理单元（MMU）的相关操作。Sv39是一种三级页表，包括页全局目录（PGD）、页中间目录（PMD）和页表项（PTE）。我们的映射方式采用了恒等映射，将虚拟地址映射到同等数值的物理地址上

- `create_identity_mapping`函数用于创建标识映射，即将虚拟地址直接映射到相同的物理地址。

- `create_pgd_mapping`、`create_pmd_mapping`和`create_pte_mapping`函数用于创建页表映射。这些函数都是通过修改页表来实现虚拟地址到物理地址的映射。在这个过程中，如果需要新的页表，就会调用`alloc_pgtable()`函数来分配。

- `alloc_pgtable()`函数用于分配新的页表。这个函数会调用`page_alloc()`函数来分配一个新的页，然后将这个页的内容全部清零，最后返回这个页的地址。

- `get_pmd_from_pgd`和`get_pte_from_pmd`函数用于从页全局目录（PGD）和页中间目录（PMD）获取页表项（PTE）。这些函数通过计算索引来定位到具体的页表项。

- `pt_pgd_item`函数用于设置页全局目录（PGD）的项。这个函数会将页表的物理地址和属性合并成一个项，然后写入到页全局目录（PGD）中。

- `create_VIRT_UART_mapping`函数用于创建虚拟UART的映射。这个函数会将虚拟UART的地址映射到物理内存中。

- `mmu_init`函数用于初始化MMU，并建立恒等映射

虚拟地址到物理地址的转换是通过MMU自动完成的。当CPU访问一个虚拟地址时，MMU会根据页表来找到对应的物理地址。如果找不到对应的物理地址，或者访问违反了页表中的权限设置，MMU会触发一个异常。

页表结构如下：


![页表映射结构图](./%E9%A1%B5%E8%A1%A8%E6%98%A0%E5%B0%84.jpeg)

