操作系统学习之用C语言模拟伙伴(Buddy)算法

前言

学到了操作系统的的虚拟内存部分,硬件不太好的我学起来有些吃力,概念性知识点太多,所以我决定用软件的方式,实现一下虚拟内存常用的算法,因为用到了指针,暂时用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链接:见博客网站
CSDN: https://blog.csdn.net/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=

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值