自顶向下设计-动态分区分配算法
首次适应算法(FF)
将空闲分区链以地址递增的顺序连接;在进行内存分配时,从链首开始顺序查找,直到找到一块分区的大小可以满足需求时,按照该作业的大小,从该分区中分配出内存,将剩下的空闲分区仍然链在空闲分区链中。
循环首次适应算法(NF)
分配内存时不是从链首进行查找可以分配内存的空闲分区,而是从上一次分配内存的空闲分区的下一个分区开始查找,直到找到可以为该进程分配内存的空闲分区。
实验要求
1. 实验描述:
(1)假设内存总大小为 1024,开始地址为 0,结束地址为 1023。有 10 个进程,它们所需要的内存大小随机在[100,200]之间产生。从第 1 个进程开始,使用指定的分区分配算法来依次为进程分配内存分区;如果还有足够大的空闲分区,则给该进程进行分配,并更新空闲分区链表。
(2)当对一块空闲分区进行划分时,在这块空闲分区的地址范围内随机产生一个划分的开始位置,然后划分出当前进程大小的分区。如果没有足够大的空闲分区,则提示内存分配失败,然后继续为下一个进程分配空闲内存分区,直到 10 个进程都处理完毕。
(3)10 个进程处理完内存分配后,执行内存回收过程。注意,在回收的时候,如果发现当前回收块的前面和后面都是空闲区块,则我们只把回收块与前面的空闲区块进行合并回收。依次从第 1 个成功分配了内存分区的进程开始,回收其占用的内存分区,直到所有被占用的分区都回收完毕。
2. 实验输出:
(1)在内存分配过程中,输出每一次内存分配结果:即成功还是失败,还有空闲/占用分区块情况;
(2)在内存回收过程中,输出每一次内存回收后的结果:即空闲/占用分区块情况。
自顶向下模块化设计
整体框架
根据题意,需要实现的算法在整体上分为Init、Alloc、Recycle、Print
-
Init:需要实现的功能是初始化Block和PCB,故分为两个函数进行实现。
-
Alloc:需要实现的是分配算法本身,而分配需要用到划分函数,分别实现FF和NF分配算法。
-
Recycle:需要实现的是回收机制本身,可能会用到若干个辅助函数。
-
Print:穿插在上面几个部分之中,根据具体需要设计即可,需要用到四个Print函数,用来输出PCB、Block以及每次划分、回收结果。
部分具体实现
Init部分
由于实现起来相对简单,故不做对具体实现的讲解。
void Set_Block(Block* block, int id, int size, int startAddr, bool status, int pid, Block* prev, Block* next);
Block* Init_Block();
void Init_PCB(PCB* pcb);
Alloc部分(以FF为例)
void FF_alloc(PCB* pcb, Block* block);
void NF_alloc(PCB* pcb, Block* block);
void Refresh_List(Block* block, Block* temp, PCB* pcb);
void Random_divide(Block* block, PCB* pcb);
FF主函数:主要工作是通过代码进行逻辑实现,所谓逻辑实现就是指用没有实现的抽象函数和简单代码将整个算法过程给描述出来。
void FF_alloc(PCB* pcb, Block* block) {
Block* temp = block;
while (pcb != NULL) {
temp = block; //每次从头开始查找
while (temp != NULL){
if (pcb->neededMem <= temp->size && temp->status) {
Refresh_List(block,temp, pcb); //更新空闲分区链表,传入链表头指针、被选中的区块和PCB需求内存大小
Print_divide(block, pcb, true); //输出划分结果,输入的block是链表头指针
pcb->blockID = temp->id; //分配成功记录为区块号,否则不做修改(默认-1)
pcb->status = 1; //分配成功则更改为1,否则不做修改(默认-1)
pcb = pcb->next; //进程状态改变
break;
}
temp = temp->next;
if (temp == NULL) {
Print_divide(block, pcb, false);
pcb = pcb->next; //不分配了,跳下一个
}
}
}
return;
}
Random_divide子函数:实现分配中最为复杂的一个步骤,划分情况可大致分为三种,具体实现看代码。
void Random_divide(Block* block, PCB* pcb) {
int Uboard = 0;
int divideAddr_start = 0;
int divideAddr_end = 0;
Uboard = block->size - pcb->neededMem + 1; //计算随机范围的上界,+1是因为被划分的起始地址本身也算一单位内存
divideAddr_start = block->startAddr + rand() % Uboard;
divideAddr_end = divideAddr_start + pcb->neededMem - 1; //-1理由类似上方
if (pcb->neededMem == block->size) {
//这种情况下不需要划分
block->pid = pcb->pid; //直接使block被占用
block->status = false;
return;
}
else if (divideAddr_start == block->startAddr || divideAddr_end == (block->startAddr + block->size - 1)) {
//这种情况下划分为两块
Block* temp = (Block*)malloc(sizeof(Block));
if (divideAddr_start == block->startAddr) {
//如果从头开始切,说明前一个是PCB用到的区块
Set_Block(temp, block->id + 1, block->size - pcb->neededMem, divideAddr_end + 1, true, pcb->pid, block, block->next);
block->pid = pcb->pid;
block->status = false;
block->size = pcb->neededMem;
if (block->next) {
block->next->prev = temp; //真的是大坑
}
block->next = temp;
}
else {
//如果切除结束的地方在末尾,说明后一个是PCB用到的区块
Set_Block(temp, block->id + 1, pcb->neededMem, divideAddr_start, false, pcb->pid, block, block->next);
block->size -= pcb->neededMem;
if (block->next) {
block->next->prev = temp; //真的是大坑
}
block->next = temp;
}
}
else {
//切成三份的情况,最为复杂
Block* temp_mid = (Block*)malloc(sizeof(Block)); //中间块,或者说是被选中块
Block* temp_last = (Block*)malloc(sizeof(Block)); //末尾块
Set_Block(temp_mid, block->id + 1, pcb->neededMem, divideAddr_start, false, pcb->pid, block, temp_last); //具体咋回事可以自己推导
Set_Block(temp_last, temp_mid->id &#