-
理解基于段页式内存地址的转换机制
-
理解页表的建立和使用方法
-
理解物理内存的管理方法
手动实现简易的物理内存管理系统
基本的函数
default_pmm_manager
的结构体变量,类型为 struct pmm_manager
。这个结构体变量包含了默认的物理内存管理器的信息和功能。
具体来说,它的意思如下:
-
.name = "default_pmm_manager"
:给物理内存管理器取了一个名字,即 "defaultpmmmanager"。 -
.init = default_init
:指定了初始化函数,即default_init
函数,用于初始化物理内存管理器。 -
.init_memmap = default_init_memmap
:指定了初始化内存映射的函数,即default_init_memmap
函数,用于初始化空闲物理页的映射。 -
.alloc_pages = default_alloc_pages
:指定了分配内存页的函数,即default_alloc_pages
函数,用于从物理内存中分配一页或多页的内存。 -
.free_pages = default_free_pages
:指定了释放内存页的函数,即default_free_pages
函数,用于将不再使用的内存页释放回物理内存。 -
.nr_free_pages = default_nr_free_pages
:指定了获取空闲内存页数量的函数,即default_nr_free_pages
函数,用于获取当前物理内存中的空闲页数量。 -
.check = default_check
:指定了检查函数,即default_check
函数,用于检查物理内存管理器的正确性。
- 首次适应分配算法 实现 first-fit 连续物理内存分配算法
首先
假设我们有一段连续的内存,通过 default_init_memmap()
函数初始化为以下状态:
```Plain Text plaintextCopy code +-------------------+ | Free (nrfree) | +-------------------+ ^ ^ | | freelist End
### `static struct Page * default_alloc_pages(size_t n)` 此函数为分配空闲页
让我们通过图示来解释代码的执行过程。
假设我们有一个空闲页链表,如下所示:
+-------+ +-------+ +-------+ +-------+
| Page1 | | Page2 | | Page3 | | Page4 |
+-------+ +-------+ +-------+ +-------+
4页 3页 5页 2页
每个空闲页都有一个属性 `property` 记录了它的大小。
现在,假设我们调用 `default_alloc_pages(4)`,希望分配一个大小为 4 页的空闲块。
代码执行过程如下:
2. **初始化**:我们初始化一个指针 `le` 指向空闲页链表的头部。
3. **遍历空闲页链表**:我们开始从头部遍历空闲页链表,寻找一个大小大于等于 4 页的空闲块。
4. **找到合适的空闲块**:假设在遍历过程中,我们找到了 `Page3`,它的大小为 5 页。
+-------+ +-------+ +-------+ |
Page1 | | Page2 | | Page4 |
+-------+ +-------+ +-------+
4页 3页 2页
5. **分配空闲块**:我们将 `Page3` 分配出去,并从空闲页链表中删除。
+-------+ +-------+ +-------+ |
Page1 | | Page2 | | Page4 |
+-------+ +-------+ +-------+
4页 3页 2页
6. **更新空闲页链表**:由于 `Page3` 的大小超过了需要的 4 页,因此我们需要将剩余的部分重新加入到空闲页链表中。
+-------+ +-------+ +-------+ +-------+ |
Page1 | | Page2 | | Page3 | | Page4 |
+-------+ +-------+ +-------+ +-------+
4页 3页 1页 2页
这样,我们就完成了一次内存分配的过程。整个过程中,代码按照从头部开始的顺序遍历空闲页链表,找到了第一个大小满足要求的空闲块,并进行了相应的分配操作。
### 即 `default_free_pages` 函数,用于将不再使用的内存页释放回物理内存。
给定内存中包含的页以及释放内存的情况,如下所示:
+-------+-------+-------+-------+-------+-------+ |
Page1 | Page2 | Page3 | Page4 | Page5 | Page6 |
+-------+-------+-------+-------+-------+-------+
假设我们要释放从 `Page2` 开始的 3 页内存。
7. **初始状态**:在初始状态下,我们有六个页,每个页都是可用的。
+-------+-------+-------+-------+-------+-------+
| Page1 | Page2 | Page3 | Page4 | Page5 | Page6 |
+-------+-------+-------+-------+-------+-------+
8. **释放内存页**:我们从 `Page2` 开始释放 3 页内存,并将它们标记为可用。
+-------+-------+-------+-------+-------+-------+ |
Page1 | Free | Free | Free | Page5 | Page6 |
+-------+-------+-------+-------+-------+-------+ ```
- 合并连续的空闲页:由于释放的内存页与相邻的空闲页相连,因此我们将它们合并为一个更大的空闲页。
即一整个free空闲页
default_nr_free_pages
:指定了获取空闲内存页数量的函数
static size_t
defaultnrfree_pages(void) {
return nr_free;
}
实现寻找虚拟地址对应的页表项
页目录项(Page Directory Entry)和页表项(Page Table Entry)是 x86 架构下操作系统管理虚拟内存的重要数据结构。
页目录项(Page Directory Entry):
-
Present (P):表示页目录项是否存在,用于判断页表是否存在于内存中。
-
Read/Write (R/W):表示页表对应的页面是否可读写,用于设置页表权限。
-
User/Supervisor (U/S):表示页表对应的页面是否可供用户态程序访问,用于设置页表权限。
-
Accessed (A):表示该页表项是否被访问过,用于页面置换算法。
-
Dirty (D):表示该页表对应的页面是否被修改过,用于页面置换算法和页面写回判断。
-
Page Table Base Address (PTBA):存储页表的物理地址,指向页表的起始地址。
页表项(Page Table Entry):
-
Present (P):表示页表项是否存在,用于判断物理页面是否存在于内存中。
-
Read/Write (R/W):表示物理页面是否可读写,用于设置页面权限。
-
User/Supervisor (U/S):表示物理页面是否可供用户态程序访问,用于设置页面权限。
-
Accessed (A):表示该页表项对应的页面是否被访问过,用于页面置换算法。
-
Dirty (D):表示该页表对应的页面是否被修改过,用于页面置换算法和页面写回判断。
-
Page Base Address (PBA):存储物理页面的起始地址。
当ucore执行过程中访问内存出现页访问异常时,硬件会进行以下处理:
-
检查页目录项和页表项中的标志位,如Present、Read/Write、User/Supervisor等,确定访问权限。
-
如果页表项或页目录项不存在,会引发页错误异常(Page Fault Exception)。
-
操作系统会根据页错误异常的处理机制,将缺失的页面从磁盘加载到内存中,更新页表项,并重新执行引发异常的指令。
释放某虚地址所在的页并取消对应二级页表项的映射
当释放一个包含某虚地址的物理内存页时,需要让对应此物理内存页的管理数据结构 Page 做相关的清除处理,使得此物理内存页成为空闲;另外还需把表示虚地址与物理地址对应关系的二级页表项清除