前言
学到了操作系统的的虚拟内存部分,硬件不太好的我学起来有些吃力,概念性知识点太多,所以我决定用软件的方式,实现一下虚拟内存常用的算法,因为用到了指针,暂时用C语言写一下Buddy算法、FIFO算法、LRU算法、Clock算法。我知道其实图形化的算法展示更加直观和容易理解,但是有些惭愧,JavaFX只是学了入门,动画3D之类的还没深入学,只能用自己的方式去实现,不过。目的主要是利用他们的思想。虚拟内存的知识会简单的总结一下,但是不会详细展开,因为我自己也不是很理解,只是先实现一下算法。
本博客实现一下Buddy算法
Buddy算法:操作系统学习之用C语言模拟伙伴(Buddy)算法
FIFO算法:操作系统学习之用C语言模拟FIFO算法
LRU算法:操作系统学习之用C语言模拟LRU算法
Clock算法:操作系统学习之用C语言模拟CLOCK算法
本源代码原创,转载请注明,同时由于本人才疏学浅,刚入门操作系统,如有错误敬请指教
本文原创,创作不易,转载请注明!!!
本文链接
个人博客:https://ronglin.fun/?p=197
PDF链接:见博客网站
优快云: https://blog.youkuaiyun.com/RongLin02/article/details/117340021
概念
在内存管理中,有动态分区方案和固定分区方案,但都存在一定的缺陷。固定分区方案限制了活动进程的数量,且若可用的分区大小与进程大小很不匹配,则内存空间的利用率就会非常低。动态分区的维护特别复杂,并且会引入进行压缩的额外开销。于是一个折中的方案提出–伙伴系统。
理论部分虽然十分头疼,但是一定要仔仔细细的看。
教科书的解释
操作系统-精髓与设计原理(第九版)的解释
伙伴系统中可用内存块的大小为2k个字,L≤K≤U,其中2L表示分配的最小快的尺寸,2U表示分配的最大快的尺寸,通常2U是可供分配的整个内存的大小。
最初,可用于分配的整个空间被视为一个大小块为2U的块。若请求的大小s满足2U-1 ≤ s ≤ 2U,则分配整个空间。否则,该块分成两个大小相等的伙伴,大小均为2U-1。若有2U-2 ≤ s ≤ 2U-1,则给该请求分配两个伙伴中的任何一个;否则,其中的一个伙伴又被分成两半,持续这一过程,直到产生大于等于s的最小快,并且分配给该请求。在任何时候,伙伴系统中为所有大小为2i的“空洞”维护一个列表。空洞可通过对半分裂从i+1列表中移出,并且在i列表中产生两个大小为2i的伙伴。当i列表中的一对伙伴都变成未分配的块时,将他们从i列表中移出,合并为i+1列表中的一个块。
理解
文字好多,看着有点吃力。其实仔细一分析,这个伙伴系统和一个名为“4096”的游戏很像,请求时,找一个“合适”的盒子放进程,当释放的时候就像游戏的那样,相同大小的块合并。
不过,伙伴系统实际来说,用一个二叉树表示更为合适.
我这里用一个链表数组来维护,
这里有个资料可以看看内存管理算法–Buddy伙伴算法
算法模拟
不多bb,开始头秃
源代码
本源代码原创,转载请注明,同时由于本人才疏学浅,刚入门操作系统,如有错误敬请指教
#include<stdio.h>
#include<stdlib.h>
#include <time.h>
#define MAX_SIZE 1024
#define MAX_NUM_PROC 10
#define MAX_PROC_SIZE 100
//进程结构体
//state 阻塞/初始-1 就绪0 运行1
struct Process
{
int pid;
int ppid;
int state;
int p_size;
void init()
{
pid = -1;
ppid = -1;
state = -1;
p_size = 0;
}
}procs[MAX_NUM_PROC];
//buddy中的每一块的结构
struct Block
{
struct Process proc;
int use;
Block* next;
void init()
{
proc.init();
use = 0;
next = NULL;
}
};
//buddy链表的头
struct Buddy
{
int all_size;
Block* next;
void init()
{
all_size = MAX_SIZE;
next = NULL;
}
};
void initBuddy(struct Buddy* L,int list_num);//初始化buddy队列
void printState(struct Buddy* L,int list_num);//打印当前buddy队列的状态
int dealProcess(struct Buddy* L,int index,struct Process proc);//处理进程的请求
void SplitBlock(struct Buddy* L,int index);//将上一级的块分裂成两个当前块
int main()
{
//初始化
int list_num = 0;
for(int mi = MAX_SIZE;mi>0;mi/=2)
list_num++;
//printf("list_num = %d\n",list_num); list_num = 11;
struct Buddy buddy[list_num];
initBuddy(buddy,list_num);
printState(buddy,list_num);
//生成n个进程,每一个进程所需空间随机
for(int i=0;i<MAX_NUM_PROC;i++)
{
srand((unsigned)time(NULL)*i);
procs[i].init();
procs[i].pid=i+1;
procs[i].ppid=0;
procs[i].p_size = rand() % MAX_PROC_SIZE +1; //随机数范围(0,MAX_PROC_SIZE];
}
//现在开始实现分配
printf("\n现在开始实现分配\n");
for(int i=0;i<MAX_NUM_PROC;i++)
{
printf("\npid = %d,state = %d,p_size=%d\n",procs[i].pid,procs[i].state,procs[i].p_size);
if(procs[i].p_size ==0)
{
printf("此进程所需大小为0,不需要分配空间\n");
continue;
}
int res=dealProcess(buddy,list_num-1,procs[i]);
//int res=0;
if(res)
{
printf("分配成功,分配块大小:%d\n",res);
procs[i].state=1;
}
else
{
printf("分配失败\n");
}
}
printState(buddy,list_num);
return 0;
}
void initBuddy(struct Buddy* L,int list_num)
{
int size_two=1;
struct Block* first_block =(struct Block*)malloc(sizeof(struct Block));
first_block->init();
for(int i=0;i<list_num;i++)
{
L[i].init();
L[i].all_size = size_two;
if(i ==10)//设置默认的最大块
{
L[i].next=first_block;
}
size_two *= 2;
}
}
void printState(struct Buddy* L,int list_num)
{
for(int i=0;i<list_num;i++)
{
printf("size=%d: ",L[i].all_size);
struct Block* p=L[i].next;
while(p)
{
if(p->use)
{
printf("pid=%d",p->proc.pid);
}
else
{
printf("use=%d",p->use);
}
printf(";");
p=p->next;
}
printf("\n");
}
}
int dealProcess(struct Buddy* L,int index,struct Process proc)
{
if(index <= -1 || L[index].all_size<proc.p_size)
return 0;
if(index==0 || L[index-1].all_size < proc.p_size )//当前的块就是要找的块
{
int flag = 0;
struct Block* p = L[index].next;
while(p)
{
if(p->use==0)
{
flag=1;
p->proc=proc;
p->use=1;
return L[index].all_size;
}
p=p->next;
}
if(flag==0)
{
SplitBlock(L,index);//分裂大块
//现在寻找执行完分裂之后是否存在可用的块
p = L[index].next;
while(p)
{
if(p->use==0)
{
flag=1;
p->proc=proc;
p->use=1;
return L[index].all_size;
}
p=p->next;
}
if(flag==0)
return 0;
}
}
else
{
return dealProcess(L,index-1,proc);
}
}
void SplitBlock(struct Buddy* L,int index)
{
if(L[index].all_size== MAX_SIZE)
return ;
int flag=0;
struct Block* p = L[index+1].next;
struct Block* q = NULL;
while(p)//寻找空闲的块
{
if(p->use == 0)
{
flag=1;
break;
}
p=p->next;
}
if(flag ==0)
{
SplitBlock(L,index+1);
}
p = L[index+1].next;
while(p)//寻找空闲的块
{
if(p->use == 0)//找到了,大块分裂成两个本块
{
if(q==NULL)//说明p指向的块是头节点之后的那个
{
L[index+1].next=p->next;
}
else
{
q->next=p->next;
}
free(p);
//尾插法插入两个新块
struct Block* t1 =(struct Block*)malloc(sizeof(struct Block));
struct Block* t2 =(struct Block*)malloc(sizeof(struct Block));
t1->init();
t2->init();
t1->next=t2;
if(L[index].next==NULL)
{
L[index].next=t1;
}
else
{
struct Block* t = L[index].next;
while(t->next)
t=t->next;
t->next=t1;
}
break;
}
q=p;
p=p->next;
}
}
运行结果
size=1:
size=2:
size=4:
size=8:
size=16:
size=32:
size=64:
size=128:
size=256:
size=512:
size=1024: use=0;
现在开始实现分配
pid = 1,state = -1,p_size=39
分配成功,分配块大小:64
pid = 2,state = -1,p_size=2
分配成功,分配块大小:2
pid = 3,state = -1,p_size=66
分配成功,分配块大小:128
pid = 4,state = -1,p_size=29
分配成功,分配块大小:32
pid = 5,state = -1,p_size=92
分配成功,分配块大小:128
pid = 6,state = -1,p_size=56
分配成功,分配块大小:64
pid = 7,state = -1,p_size=19
分配成功,分配块大小:32
pid = 8,state = -1,p_size=82
分配成功,分配块大小:128
pid = 9,state = -1,p_size=45
分配成功,分配块大小:64
pid = 10,state = -1,p_size=9
分配成功,分配块大小:16
size=1:
size=2: pid=2;use=0;
size=4: use=0;
size=8: use=0;
size=16: pid=10;
size=32: pid=4;pid=7;use=0;
size=64: pid=1;pid=6;pid=9;use=0;
size=128: pid=3;pid=5;pid=8;
size=256: use=0;
size=512:
size=1024:
结果的图示:
代码缺点
先说这个模拟的缺点,缺点是:只是维护了链表数组,并没有维护"伙伴"这个性质,仅仅是进程有需求,就按照buddy分配,没有维护哪两个块是伙伴,也就是说只体现了分配,没体现伙伴。所以没写释放进程之后的块合并过程,因为不知道哪两个块是伙伴,要想解决也简单,就是在Block结构体中,再加一个属性,用来表示"伙伴",然后分裂和合并的时候,根据这个属性确定自己的"伙伴"。
实现匆忙,再加上本人理论不扎实,有好想法可告诉我。
解释代码
代码250多行,解释起来有点麻烦,简单的解释一下。
先说输出:
use=0表示这个块被切割了,但是还没用,如果被用了,就输出是哪个进程占用。可以对照上图查看。
结构体
进程结构体
这里边包含了一些一个进程的基本信息,因为不涉及到硬件,只是纯软件模拟,比PCB中的属性少了很多,然后定义是在全局中定义了MAX_NUM_PROC
个进程,每个进程的初始化是在主函数的for循环中,用了一个随机函数,生成进程的所需空间,随机数范围是(0,MAX_PROC_SIZE],可根据自己的需要修改。
Buddy结构体
这个是每个链表的头,数据域提供了这个链表的表示的大小,比如表示的512、64等。
Block结构体
每一个块的数据结构,数据域有两个一个是存放的进程,还有一个是是否被使用,这个use
属性的意思是,伙伴系统分裂出来两个,一个被使用,而它的伙伴可能还没被使用。
主函数
主函数的工作比较简单,就是初始化buddy链表,生成MAX_NUM_PROC
个进程,然后按照buddy算法分配内存给进程。
子函数
void initBuddy(struct Buddy* L,int list_num);//初始化buddy队列
void printState(struct Buddy* L,int list_num);//打印当前buddy队列的状态
int dealProcess(struct Buddy* L,int index,struct Process proc);//处理进程的请求
void SplitBlock(struct Buddy* L,int index);//将上一级的块分裂成两个当前块
一共四个子函数,第一个和第二个是用来描述buddy链表,难点是后两个递归函数的实现过程。
dealProcess()
函数是递归寻找 对于 进程 “适合"大小的块,然后将块分给进程。如果没找到这个块,就会调用SplitBlock()
函数,将上层的大块,依次递归分裂生成小块,直到本层,如果分裂完,还没有适合的块,就返回错误,会输出"分配失败”。
总结
尝试过用面向对象的写法,最开始用Java写,结果发现由于Java的指针机制,链表的实现太麻烦,后来又用了C++的面向对象,发现需要太多的引用和指针,维护链表着实有点大材小用,而且有点麻烦,最后决定用面向过程的思维写,既然是面向过程,最后决定用C语言实现,buddy算法思维容易理解,可真正实现的时候发现了很多很多问题。这些算法真的要自己敲一遍才知道它真正的意义,获益匪浅。
=w=