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$。

处理过程例图:
image

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);
    }
}

初始化之后,我们的存储模型大致是这个结构:
image

每个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:有空写写细节)

posted @ 2022-04-16 17:22  ever_garden  阅读(461)  评论(0编辑  收藏  举报