「BUAA OS Lab2-1-Extra」数组模拟伙伴系统
Part 0 前言
本篇博客记录模拟伙伴系统的数组实现方法,Part 1为题目描述,Part 2为设计分析,Part 3为课上源码(已AC并开源)参考,读者可自取所需。
同时,本博客系统已支持评论区,欢迎您参考 Gitment评论区配置与使用中Part2开通您在本博客的权限,留下您的想法。
Part 1 题目描述
请你对现有的物理内存管理机制进行修改,对 MOS 中$ 64 MB $物理内存的高地址 $ 32 MB $建立伙伴
系统。下面对本题中所需要实现的伙伴系统进行描述:
内存区间的初始化
伙伴系统将高地址$ 32 MB $划分为数个内存区间,每个内存区间有两种状态:已分配和未分配。
每个内存区间的大小只可能是$ 4 \times 2^i KB $,其中$ i $是整数且$ 0 \le i \le 10 $。
初始,共有$ 8 $个$ 4 MB $大小的内存区间,状态均为未分配。
内存区间的分配
每次通过伙伴系统分配 $ x B $的空间时,找到满足如下三个条件的内存区间:
- 该内存区间的状态为未分配。
- 其大小不小于 $x B $。
- 满足上面两个条件的前提下,该内存区间的起始地址最小。
如果不存在这样的内存区间,则本次分配失败;否则,执行如下步骤:
- 设该内存区间的大小为 $ y B $,若$ \frac{y}{2} < x $或$ y = 4 K$,则将该内存区间的状态设为已分配,将该内存区间分配并结束此次分配过程。
- 否则,将该内存区间分裂成两个大小相等的内存区间,状态均为未分配。
- 继续选择起始地址更小的那个内存区间,并返回步骤 1。
内存区间的释放
当一个内存区间使用完毕,通过伙伴系统释放时,将其状态设为未分配。
我们称两个内存区间$ x $和$ y $是可合并的,当且仅当它们满足如下四个条件:
- $ x $和$ y $的状态均为未分配。
- $ x $和$ y $是由同一个内存区间一次分裂所产生的两个内存区间。
若存在两个可合并的内存区间,则将两个内存区间合并,若合并后仍存在两个可合并的内存区间,则继续合并,直到不存在两个可合并的内存区间为止。
请你实现如下的三个函数:
初始化函数 buddy_init
- 函数原型为:
void buddy_init(void)
- 调用此函数后,为 MOS 中 $ 64 MB $物理内存的高地址$ 32 MB $初始化伙伴系统。初始化结束后,伙伴系统中仅有只有$ 8 $个$ 4 MB $的待分配内存区间。
分配函数 buddy_alloc
- 函数原型为:
int buddy_alloc(u_int size, u_int *pa, u_char *pi)
- 调用此函数后,通过伙伴系统分配大小不小于
size
字节的空间,分配逻辑见上述描述。
如果分配失败,返回$ −1$。否则,将pa
指向所分配内存区间的起始地址,设所分配内存区间的大小为$ 4 \times 2^i KB $,令*pi = i
,并返回$ 0 $。
,令*pi = i
,并返回$ 0 $。
释放函数 buddy_free
- 函数原型为:
void buddy_free(u_int pa)
- 调用此函数后,通过伙伴系统释放一个状态为已分配的内存区间,其起始地址为
pa
。释放后的合并逻辑见上述描述。
注意事项
你需要先在include/pmap.h
中加入如下三个函数定义:
1 | void buddy_init(void); |
之后再在mm/pmap.c
中实现这三个函数。
评测逻辑
评测过程中,我们会将所有的Makefile
文件、include.mk
以及 init/init.c
替换为 lab2 初始配置,接着将init/init.c
中的mips_init
函数改为如下形式:
1 | void mips_init(){ |
最后的*((volatile char*)(0xB0000010)) = 0;
会终止 Gxemul 模拟器的运行,避免占用评测资源。
buddy_test
在评测过程中新添加到init/init.c
的函数,其中仅包含以下两种操作:
- 调用
buddy_alloc
函数,我们保证size
不为$ 0 $。 - 调用
buddy_free
函数,我们保证pa
是之前某次调用buddy_alloc
所得到的。
每调用一个函数算一次操作,我们保证总操作数不超过$ 1000 $。
运行make
指令的最大时间为$ 10 $秒,运行Gxemul模拟器的最大时间为$ 4 $秒。
设伙伴系统管理的物理页数为$ n $,标程中buddy_alloc
和buddy_free
两个函数的时间复杂度均为$ O(n) $,请你尽量以此复杂度设计算法。
Part 2 设计分析
基于树的实现
通过题目的描述,我们可以自然地得到树的设计思路:设计TreeNode
结构体为树节点,储存左右儿子(不一定存在)和父亲的指针信息,储存分配信息以及储存空间大小。
受限于上机时间、实现难度和buddy_free
函数时间复杂度的考虑,笔者最终并没有采取这种办法,以下着重介绍第二种思路:基于数组的实现。
基于数组的实现
题目给出,对于给定size
,若其可能被分配,其分配大小只能为$ 4 \times 2^i KB $,因此,我们以一页($ 4 KB $)为单位,将内存分为$ 8 \times 1024 $个页面,利用数组buddyPage[8 * 1024]
表示。
对于页数$2 ^ i $,我们按照如下公式计算得到:
得到页数$2 ^ i $后,由于伙伴系统的特点,我们从下标$ 0 $开始,以$2 ^ i $为步长,从低至高遍历buddyPage
,若某一步内buddyPage
全部未分配,则将其标记为该次分配并返回;若遍历结束仍未找到满足条件的块,标记为未找到。
对于buddy_free
函数,我们需要释放以给定地址开始的内存,因此,我们在分配时需要区分不同的分配。举例来说,两个相邻的$ 4KB $在两次分配中分别被分配,那么当给定第一个$ 4KB $的地址时,只能释放第一个$ 4KB $。
基于数组的实现保证了我们时间复杂度为$ O(n) $,同时正确性可以逻辑证明,兼顾了实现难度和实际性能。
Part 3 课上源码
全局变量与辅助函数
1 | int buddyPage[9999] = {0}; |
buddy_init
1 | void buddy_init(void) |
buddy_alloc
1 | int buddy_alloc(u_int size, u_int *pa, u_char *pi) |
buddy_free
1 | void buddy_free(u_int pa) |
Part 4 后记
Part2中提到了树的实现,笔者不会写树了(菜qwq),并且分析时感觉可能需要递归释放空间,可能会超过$ O(n) $的限制,因此没有继续深究,若读者通过此方法实现或有想法,欢迎和我取得联系一起探讨。
Part3中的代码为上机时最后一次提交,注释掉了一些调试用代码,并且很多地方的细节处理的不够漂亮(可能还有英文单词拼写错误),欢迎批评指正。
This is copyright.