BUAA_OS_lab2上机Extra解题记录
2022.4.14
两点开始上机,原定上机时间2h,考到中间延长了1h。Exam题半小时做完,Extra构思、写代码就花了1h多,之后生生坐了1.5h的牢(悲)。到结束也没调试出最后的bug。晚上又做了一遍,花了好久才调通。特来记录我的解题思路。
1. 题面
请你对现有的物理内存管理机制进行修改,对 MOS 中 物理内存的高地址 建立伙伴系统。下面对本题中所需要实现的伙伴系统进行描述:
需要实现3个函数:
2. 思路:
我的思路有点绕,但自认为比较符合题目的意思。首先,我们仿照Page内存管理结构体建立Buddy结构体,并类似地建立链表结构,定义连续的buddys数组(类似pages),buddy_free_list链表头,buddy2pa,pa2buddy宏。
typedef LIST_ENTRY(Buddy) Buddy_list_entry;
struct Buddy {
Buddy_list_entry link;
u_long i; // 量度所管理Buddy内存空间的大小
u_long alloc_size; // 主要用于区分内存块是否被分配出去
};
struct Buddy *buddys;
LIST_HEAD(Buddy_list, Buddy);
struct Buddy_list buddy_free_list;
#define buddy_begin 0x02000000
#define buddy_end 0x04000000
#define buddy2pa(buddy) (u_long)( ((buddy)-buddys)*0x1000 + buddy_begin )
#define pa2buddy(pa) ( buddys + (( (pa) - buddy_begin )>>12) )
跟Page类似,我们buddys数组中的每一个元素Buddy都对应一个32MB~64MB地址空间的一个4KB连续页,不过在这里我们主要使用页的首地址概念,即每个元素Buddy指向(管理)以对应页首地址为起始地址的连续内存空间,且这段空间满足$size=4*2^iKB$。
处理过程例图:
buddy_init函数
原型: void buddy_init(void)
buddy_init用于初始化Buddy系统。此时,我们需要申请空间建立buddys数组(可以穿在buddy_free_list上的元素),并将初始的8块4MB空间放到空闲Buddy列表中。
我采用alloc函数申请buddys数组,但这其实只是为了应付测试的权宜之计而已,要用在实际的操作系统里会连带着引发一些错误(alloc没有引入Page管理内存)。
代码如下:
void buddy_init(void) {
struct Buddy *buddy;
u_long MB4 = (1<<22);
u_long nbuddy = npage >> 1;
u_long addr;
buddys = (struct Buddy *)alloc(sizeof(struct Buddy) * nbuddy, BY2PG, 1); // 申请空间
LIST_INIT(&buddy_free_list); // 初始化空闲链表
for (addr = buddy_begin; addr < buddy_end; addr += MB4) { // 往链表中添加元素
buddy = pa2buddy(addr);
buddy->i = 10; buddy->alloc_size = 0;
LIST_INSERT_TAIL(&buddy_free_list, buddy, link);
}
}
初始化之后,我们的存储模型大致是这个结构:
每个Block(即未分配的Buddy)由其最开始的一个页所对应的Buddy结构体管理,其他结构体默认为空,不存放在空闲链表中。
buddy_alloc
原型:int buddy_alloc(u_int size, u_int *pa, u_int *pi)
代码:(代码多次修改,有些丑陋)
int buddy_alloc(u_int size, u_int *pa, u_char *pi) {
struct Buddy *buddy = 0; // 我们要分配的buddy空间
struct Buddy *temp; // 用于迭代
struct Buddy *splitBuddy;
u_long nowAddr, nextAddr;
LIST_FOREACH(temp, &buddy_free_list, link) { // 找到足够大、未分配的Buddy
if (temp->alloc_size == 0 && ( 1<<(temp->i + 12) ) >= size) {
buddy = temp;
break;
}
}
if (buddy == 0) return -1; // 未找到
else {
while ( (1<<(buddy->i + 11)) >= size && buddy->i != 0 ) { // 不满足结束条件,则将buddy迭代分割,直到满足结束条件
nowAddr = buddy2pa(buddy);
nextAddr = nowAddr + ( 1<<(buddy->i + 11) ); // 拆分之后,第二份的的地址
splitBuddy = pa2buddy(nextAddr);
buddy->i = buddy->i - 1; // 拆分后大小缩小为一半
splitBuddy->i = buddy->i;
LIST_INSERT_AFTER(buddy, splitBuddy, link); // 将拆分内容插入链表
}
LIST_REMOVE(buddy, link); // 已经把buddy分配出去了,就在空闲列表中将buddy删除
buddy->alloc_size = 1 << (buddy->i + 12);
*pa = buddy2pa(buddy);
*pi = buddy->i;
return 0;
}
}
举例:假如说,我们在空闲链表中找到一个区域{0x8000, size=0x8000},我们将其分割,易知其i+12-1位为0(由切割方式易推导出结论),即将其第i+12 - 1位(从0开始数的位数)位置为1,加和或(or)均可实现。
每次拆分完之后,我们把拆分下来的buddy右半边插入到原buddy后,同时将buddy更新为左半边(这一块要仔细理解。buddy的位置实际上可以表示很多以buddy为起始的内存空间,关键点在i。也就是说,切割前和切割后,以buddy为起始位置的两个内存区域实际上都用了buddy来管理,只不过他们互斥)。
buddy_alloc的时间复杂度为O(n).
buddy_free
原型: void buddy_free(u_int pa)
注:最容易出错的函数。
void buddy_free(u_int pa) {
struct Buddy *buddy = pa2buddy(pa);
struct Buddy *pair, *temp, *now;
u_long pairAddr, mergedAddr, buddyAddr;
buddy->alloc_size = 0;
if (LIST_EMPTY(&buddy_free_list)) LIST_INSERT_HEAD(&buddy_free_list, buddy, link);
else {
now = 0;
LIST_FOREACH(temp, &buddy_free_list, link) {
if (temp <= buddy) now = temp;
}
if (now != 0) LIST_INSERT_AFTER(now, buddy, link);
else LIST_INSERT_HEAD(&buddy_free_list, buddy, link);
}
while(1) {
if(buddy->i == 10) return; // 4MB
buddyAddr = buddy2pa(buddy);
pairAddr = buddy2pa(buddy) ^ ( 1 << (buddy->i + 12) ); // pair Buddy's addr
pair = pa2buddy(pairAddr); // pair Buddy's struct pointer
if (pair->alloc_size == 0 && pair->i == buddy->i) { // meet merge conditions: free; equal size.
mergedAddr = ( buddyAddr & ( ~( 1 << (buddy->i + 12) ) ) );
pa2buddy(mergedAddr)->i += 1;
if (mergedAddr == buddyAddr) LIST_REMOVE(pair, link);
else LIST_REMOVE(buddy, link);
// 错误:没有意识到其中两项是相同的!只需要删除一项就行了。
// LIST_INSERT_AFTER(buddy, pa2buddy(buddyAddr), link);
// LIST_REMOVE(buddy, link);
// LIST_REMOVE(other, link);
buddy = pa2buddy(mergedAddr);
buddy->alloc_size = 0;
}
else break;
}
}
作为参数的pa是我们分配出去的内存起始地址,因此一定对齐,可通过pa2buddy查询到。
首先,我们把释放的Buddy标记为释放(alloc_size=0),然后插入链表。此时需要特判几种情况:
1. 链表为空: 直接插入尾部即可
2. 链表非空:查询最后一个小于(理论上没有等于)Buddy.addr的元素。
这里有一个坑,如果你直接在这个元素后面插入,会出错。原因是这个元素可能为0(代表链表中地址均大于Buddy.addr),所以我们在now=0时插入到头部即可。
然后进入while循环,不断迭代合并即可。(mark:有空写写细节)