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

本文介绍了作者通过C语言实现操作系统中虚拟内存管理的Buddy算法,包括其工作原理和代码实现。作者通过链表数组维护内存块,并模拟进程分配和释放。尽管代码仅实现了分配,未涉及伙伴合并,但展示了Buddy算法的基础思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

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

一、 功能简介 本课件是一个动态演示数据结构算法执行过程的辅助教学软件, 它可适应读者对算法的输入数据和过程执行的控制方式的不同需求, 在计算机的屏幕上显示算法执行过程中数据的逻辑结构或存储结构的变化状况或递归算法执行过程中栈的变化状况。整个系统使用菜单驱动方式, 每个菜单包括若干菜单项。每个菜单项对应一个动作或一个子菜单。系统一直处于选择菜单项或执行动作状态, 直到选择了退出动作为止。 二、 系统内容 本系统内含84个算法,分属13部分内容,由主菜单显示,与《数据结构》教科书中自第2章至第11章中相对应。各部分演示算法如下: 1. 顺序表 (1)在顺序表中插入一个数据元素(ins_sqlist) (2)删除顺序表中一个数据元素(del_sqlist) (3)合并两个有序顺序表(merge_sqlist) 2. 链表 (1)创建一个单链表(Crt_LinkList) (2)在单链表中插入一个结点(Ins_LinkList) (3)删除单链表中的一个结点(Del_LinkList) (4)两个有序链表求并(Union) (5)归并两个有序链表(MergeList_L) (6)两个有序链表求交(ListIntersection_L) (7)两个有序链表求差(SubList_L) 3. 栈和队列 (1)计算阿克曼函数(AckMan) (2)栈的输出序列(Gen、Perform) (3)递归算法的演示  汉诺塔的算法(Hanoi)  解皇后问题的算法(Queen)  解迷宫的算法(Maze)  解背包问题的算法(Knap) (4)模拟银行(BankSimulation) (5)表达式求值(Exp_reduced) 4. 串的模式匹配 (1)古典算法(Index_BF) (2)求Next 函数值(Get_next)和按Next 函数值进行匹配 (Index_KMP(next)) (3)求 Next 修正值(Get_nextval)和按 Next 修正值进行匹配(Index_KMP(nextval)) 5. 稀疏矩阵 (1)矩阵转置 (Trans_Sparmat) (2)快速矩阵转置 (Fast_Transpos) (3)矩阵乘法 (Multiply_Sparmat) 6. 广义表 (1)求广义表的深度(Ls_Depth) (2)复制广义表(Ls_Copy) (3)创建广义表的存储结构(Crt_Lists) 7. 二叉树 (1)遍历二叉树  二叉树的线索化  先序遍历(Pre_order)  中序遍历(In_order)  后序遍历(Post_order) (2) 按先序建二叉树(CrtBT_PreOdr) (3) 线索二叉树  二叉树的线索化  生成先序线索(前驱或后继) (Pre_thre)  中序线索(前驱或后继) (In_thre)  后序线索(前驱或后继) (Post_thre)  遍历中序线索二叉树(Inorder_thlinked)  中序线索树的插入(ins_lchild_inthr)和删除(del_lchild_inthr)结点 (4)建赫夫曼树和求赫夫曼编码(HuffmanCoding) (5)森林转化成二叉树(Forest2BT) (6)二叉树转化成森林(BT2Forest) (7)按表达式建树(ExpTree)并求值(CalExpTreeByPostOrderTrav) 8. 图 (1)图的遍历  深度优先搜索(Travel_DFS)  广度优先搜索(Travel_BFS) (2)求有向图的强连通分量(Strong_comp) (3)有向无环图的两个算法  拓扑排序(Toposort)  关键路径(Critical_path) (4)求最小生成树  普里姆算法(Prim)  克鲁斯卡尔算法(Kruscal) (5)求关节点和重连通分量(Get_artical) (6)求最短路径  弗洛伊德算法(shortpath_Floyd)  迪杰斯特拉算法(shortpath_DIJ) 9. 存储管理 (1)边界标识法 (Boundary_tag_method) (2)伙伴系统 (Buddy_system) (3)紧缩无用单元 (Storage_compactio
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值