操作系统第5次实验报告:内存管理

0. 个人信息

  • 姓名 罗廷杨
  • 学号 201821121013
  • 班级 计算1811

1. 记录内存空间使用情况

    建立一个链表来记录内存空间的使用情况。首先定义一个结构体allocated_block来存放加载到内存中的进程,然后定义一个全局指针变量allocated_block_head来指向链表的头结点,当调用alloc_process函数来为进程分配内存空间时,就将成功分配到内存空间的进程块节点添加到链表中。当调用kill_process函数来对进程内存进行释放时,就将成功释放的进程块节点从链表中删除。

/*每个进程分配到的内存块描述*/
typedef struct allocated_block{
    int pid;
    int size;
    int start_addr;
    char process_name[NAME_LEN];
    struct allocated_block *next;
}AB;

/*进程分配内存块链表的首指针*/
AB *allocated_block_head = NULL; 

2. 记录空闲分区

    建立一个链表来记录空闲分区。首先定义一个结构体free_block_type来存放空闲内存块,然后定义一个全局指针变量free_block来指向链表的头结点,当调用init_free_block函数时对整个内存空间进行初始化,此时的内存空间都为空闲分区。当调用alloc_process函数来为进程分配内存空间时,就会使用首次适配算法来从空闲分区中选出一块空闲内存分配给进程,这样就有可能将连续的空闲分区划分成几块不连续的空闲分区,这时候就将这几块不连续的空闲分区添加到记录空闲分区的链表中,并按照分区大小从小到大排序。当调用kill_process函数来对进程内存进行释放时,就会将分配给该进程的内存空闲出来,此时需要将该空闲分区插入到空闲分区链表中,注意空闲分区块的排列顺序是从小到大。

/*描述每一个空闲块的数据结构*/
typedef struct free_block_type{
    int size;
    int start_addr;
    struct free_block_type *next;
}FBT;

/*指向内存中空闲块链表的首指针*/
FBT *free_block;

3. 内存分配算法

    首次适配算法的原理是把空闲表中最先能够满足要求的空闲区分配给进程,为适应这种算法要使得空闲区的大小按起始地址升序排列。根据首次适配算法在空闲分区链表中搜索合适空闲分区进行分配,分配时如果找到可满足空闲分区且分配后剩余空间足够大,则分割;如果找不可满足需要的空闲分区但空闲分区之和能满足需要,则采用内存紧缩技术,进行空闲分区的合并,然后再分配。在成功分配内存后,应保持空闲分区按照首次适配算法有序。

int allocate_mem(AB *ab){
    /*分配内存模块*/
    FBT *fbt,*pre;
    int request_size=ab->size;
    fbt = pre = free_block;
    //尝试寻找可分配空闲,具体结果在函数中有解释
    int f = find_free_mem(request_size);
    if(f == -1){
        //不够分配
        printf("空闲内存不足,内存分配失败!\n");
        return -1;
    }else{
        if(f == 0){
            //需要内存紧缩才能分配
            memory_compact();
        }
        //执行分配
        do_allocate_mem(ab);
    }
    //重新排布空闲分区
    rearrange(ma_algorithm);
    return 1;
} 

//执行分配内存
void do_allocate_mem(AB *ab){
    int request = ab->size;
    FBT *tmp = free_block;
    while(tmp != NULL){
        if(tmp->size >= request){
            //分配
            ab->start_addr = tmp->start_addr;
            int shengyu = tmp->size - request;
            tmp->size = shengyu;
            tmp->start_addr = tmp->start_addr + request;
            
            return ;
        }
        tmp = tmp->next;
    }
}

void rearrange_FF(){
    /*首次适应算法,空闲区大小按起始地址升序排序*/
    //这里使用冒泡排序方法
    if(free_block == NULL || free_block->next == NULL)
        return;
    FBT *t1,*t2,*head;
    head = free_block;
    for(t1 = head->next;t1;t1 = t1->next){
        for(t2 = head;t2 != t1;t2=t2->next){
            if(t2->start_addr > t2->next->start_addr){
                int tmp = t2->start_addr;
                t2->start_addr = t2->next->start_addr;
                t2->next->start_addr = tmp;

                tmp = t2->size;
                t2->size = t2->next->size;
                t2->next->size = tmp;
            }
        }
    }
}

4. 内存释放算法

    释放进程内存包括更新分区表和进程节点两个部分。更新分区表的步骤可以分为以下几点:第一:将新释放的结点插入到空闲分区队列末尾;第二:对空闲链表按照地址有序排列;第三:检查并合并相邻的空闲分区;第四:将空闲链表重新按照首次适配算法排序。释放进程节点比较简单,只要处理好节点的前后关联就比较容易实现。

//释放链表节点
int dispose(AB *free_ab){
    /*释放ab数据结构节点*/
    AB *pre,*ab;
    if(free_ab == allocated_block_head){
        //如果要是释放第一个节点
        allocated_block_head = allocated_block_head->next;
        free(free_ab);
        return 1;
    }
    pre = allocated_block_head;
    ab = allocated_block_head->next;
    while(ab!=free_ab){
        pre = ab;
        ab = ab->next;
    }
    pre->next = ab->next;
    free(ab);
    return 2;
}

//更新分区表
int free_mem(AB *ab){
    /* 将ab所表示的已分配区归还,并进行可能的合并 */
    int algorithm = ma_algorithm;
    FBT *fbt,*pre,*work;
    fbt = (FBT*)malloc(sizeof(FBT));
    if(!fbt) return -1;
    /*
    进行可能的合并,基本策略如下?
    1. 将新释放的结点插入到空闲分区队列末尾?
    2. 对空闲链表按照地址有序排列?
    3. 检查并合并相邻的空闲分区?
    4. 将空闲链表重新按照当前算法排序
    */
    fbt->size = ab->size;
    fbt->start_addr = ab->start_addr;

    //插至末尾
    work = free_block;
    if(work == NULL){
        free_block = fbt;
        fbt->next == NULL;
    }else{
        while(work ->next != NULL){
            work = work->next;
        }
        fbt->next = work->next;
        work->next = fbt;
    }
    //按地址重新排布
    rearrange_FF();

    //合并可能分区;即若两空闲分区相连则合并
    pre = free_block;
    while(pre->next){
        work = pre->next;
        if(pre->start_addr + pre->size == work->start_addr ){
            pre->size = pre->size + work->size;
            pre->next = work->next;
            free(work);
            continue;
        }else{
            pre = pre->next;
        }
    }

    //按照当前算法排序
    rearrange(ma_algorithm);
    return 1;
}

int kill_process(int pid){
    AB *ab;
    ab = find_process(pid);
    if(ab!=NULL){
        free_mem(ab);    //释放ab所表示的分配表
        dispose(ab);    //释放ab数据结构节点
        return 0;
    }else{
        return -1;
    }
}

5. 运行结果

(1)产生测试数据

随机为3个进程分配、释放内存10次以上,即随机产生10组以上数据:(进程Pi分配内存大小)或者(进程Pi结束)

int main(int argc, char const *argv[]){
    /* code */
    int sel1,sel2; 
    int total=0; //记录分配内存的次数 
    free_block = init_free_block(mem_size); //初始化空闲区
    
    Prc prc[PROCESS_NUM];//存放要加载的进程
    init_program(prc,PROCESS_NUM);//对这几个程进程进行初始化 
    srand( (unsigned)time( NULL ) );  
    
    for(int i=0;i<DATA_NUM;++i)
    {
        /*
           sel1=0表示为某进程分配内存空间 
           sel1=1表示为释放某进程占用的内存空间 
        */ 
        sel1=rand()%2;
        int count=0;
        //统计三个进程中有多少个进程已经分配内存 
        for(int j=0;j<PROCESS_NUM;++j){
            if(prc[j].pid!=-1)
                count++;
        }
        //如果全部分配进程或者进程分配到达5次,那么就不能继续分配内存,改为释放内存 
        if((count==PROCESS_NUM && sel1==0)||total==5)
            sel1=1;
        //如果全部未分配进程,那么就不能继续释放内存 
        if(count==0 && sel1==1)
            sel1=0;
        if(sel1==0)//为进程分配内存 
        {
            //随机找到一个未分配内存的进程 
            do{
                sel2=rand()%PROCESS_NUM;
            }while(prc[sel2].pid!=-1);
            alloc_process(prc[sel2]);//分配内存空间 
            prc[sel2].pid=pid;//改变标记 
            total++;
            display_mem_usage();//显示 
        }
        else//释放进程占用的内存空间 
        {
            //随机找到一个可释放进程 
            do{
                sel2=rand()%PROCESS_NUM;
            }while(prc[sel2].pid==-1);
            kill_process(prc[sel2].pid);//释放内存空间 
            prc[sel2].pid=-1;//改变标记 
            display_mem_usage();//显示 
        }
    }
}

运行结果(可见每次产生的数据以及内存使用情况和空闲分区情况)

 

(2)解释结果

初始化的空闲分区的内存范围为0到1024,下面给出4组结果的解释:

第一次操作为进程process-02分配了从0开始,大小为80的一块内存单元,分配后空闲分区内存剩余地址从80开始,大小为944的一块区域。

第二次操作为进程process-03分配了从80开始,大小为55的一块内存单元,分配后空闲分区内存剩余地址从135开始,大小为889的一块区域。

第三次操作为进程process-01分配了从135开始大小为21的一块内存单元,分配后空闲分区内存剩余地址从160开始,大小为864的一块区域。

第四次操作释放了进程process-02的内存空间,释放后空闲分区内存剩余地址从80开始大小为55的一块区域和地址从160开始大小为864的一块区域。

附录

源码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>

#define NAME_LEN 32 //进程名长度
#define MIN_SLICE 10         //最小碎片的大小
#define DEFAULT_MEM_SIZE 1024    //内存大小
#define DEFAULT_MEM_START 0        //起始位置
#define MA_FF 1              //定义首次适配算法 
#define DATA_NUM 10         //定义产生数据的组数 
#define PROCESS_NUM 3       //进程数 
#define PROCESS_SIZE 10     //程序大小 
#define ALLOC_SIZE 100    //随机数最大值 
/*描述每一个空闲块的数据结构*/
typedef struct free_block_type{
    int size;
    int start_addr;
    struct free_block_type *next;
}FBT;

/*每个进程分配到的内存块描述*/
typedef struct allocated_block{
    int pid;
    int size;
    int start_addr;
    char process_name[NAME_LEN];
    struct allocated_block *next;
}AB;

/*进程结构描述*/
typedef struct Process{
    int size;
    /*标识程序是否已经加载到内存中运行, 
     如果是则pid为进程id,否则为-1
    */ 
    int pid;
    char process_name[NAME_LEN];
}Prc;


FBT *free_block;//指向内存中空闲块链表的首指针

/*进程分配内存块链表的首指针*/
AB *allocated_block_head = NULL; 

static int pid = 0; /*初始pid*/

int mem_size = DEFAULT_MEM_SIZE; //内存大小
int ma_algorithm = MA_FF; //当前分配算法为首次适配算法  
int min_mem_size = 0; //设置剩余分区过小的标志

FBT *init_free_block(int mem_size);//初始化空闲分区链表
int display_mem_usage();//显示内存情况
int dispose(AB *free_ab);//释放链表节点
int free_mem();//更新分区表
int kill_process(int pid);//释放进程内存 
int allocate_mem(AB *ab);//分配内存模块
int alloc_process(Prc prc);//为进程分配内存 
void rearrange_FF();//首次适应算法 
void rearrange(int algorithm);//整理内存空闲块链表
void init_program(Prc prc[],int n);//初始化进程 

int main(int argc, char const *argv[]){
    /* code */
    int sel1,sel2; 
    int total=0; //记录分配内存的次数 
    free_block = init_free_block(mem_size); //初始化空闲区
    
    Prc prc[PROCESS_NUM];//存放要加载的进程
    init_program(prc,PROCESS_NUM);//对这几个程进程进行初始化 
    srand( (unsigned)time( NULL ) );  
    
    for(int i=0;i<DATA_NUM;++i)
    {
        /*
           sel1=0表示为某进程分配内存空间 
           sel1=1表示为释放某进程占用的内存空间 
        */ 
        sel1=rand()%2;
        int count=0;
        //统计三个进程中有多少个进程已经分配内存 
        for(int j=0;j<PROCESS_NUM;++j){
            if(prc[j].pid!=-1)
                count++;
        }
        //如果全部分配进程或者进程分配到达5次,那么就不能继续分配内存改为释放内存 
        if((count==PROCESS_NUM && sel1==0)||total==5)
            sel1=1;
        //如果全部未分配进程,那么就不能继续释放内存 
        if(count==0 && sel1==1)
            sel1=0;
        if(sel1==0)//为进程分配内存 
        {
            //随机找到一个未分配内存的进程 
            do{
                sel2=rand()%PROCESS_NUM;
            }while(prc[sel2].pid!=-1);
            alloc_process(prc[sel2]);//分配内存空间 
            prc[sel2].pid=pid;//改变标记 
            total++;
            display_mem_usage();//显示 
        }
        else//释放进程占用的内存空间 
        {
            //随机找到一个可释放进程 
            do{
                sel2=rand()%PROCESS_NUM;
            }while(prc[sel2].pid==-1);
            kill_process(prc[sel2].pid);//释放内存空间 
            prc[sel2].pid=-1;//改变标记 
            display_mem_usage();//显示 
        }
    }
}

//初始化空闲分区链表
FBT *init_free_block(int mem_size){
    FBT *fb;

    fb = (FBT*)malloc(sizeof(FBT));
    if(fb==NULL){
        printf("No mem\n");
        return NULL;
    }
    fb->size = mem_size;
    fb->start_addr = DEFAULT_MEM_START;
    fb->next = NULL;
    return fb;
}


int display_mem_usage(){
    //显示当前内存的使用情况,包括空闲分区的情况和已经分配的情况
    FBT *fbt = free_block;
    AB *ab = allocated_block_head;
    // if(fbt == NULL) return -1;
    printf("\e[0;31;1m------------------------------------------------------------------\e[0m\n");
    //显示空闲区
    printf("\e[0;32;1m空闲的内存:\e[0m\n");
    printf("\e[0;33;1m%20s %20s\e[0m\n","     start_addr","       size");
    while(fbt!=NULL){
        if(fbt->size!=0)
            printf("%20d %20d\n",fbt->start_addr,fbt->size);
        fbt = fbt->next;
    }

    //显示已分配区
    printf("\n");
    printf("\e[0;35;1m使用的内存:\e[0m\n");
    printf("\e[0;33;1m%10s %20s %20s %10s\e[0m\n","PID","ProcessName","start_addr","size");
    while(ab != NULL){
        printf("%10d %20s %20d %10d\n",ab->pid,ab->process_name,ab->start_addr,ab->size);
        ab = ab->next;
    }
    printf("\e[0;31;1m------------------------------------------------------------------\e[0m\n");
    return 0;
}

//释放链表节点
int dispose(AB *free_ab){
    /*释放ab数据结构节点*/
    AB *pre,*ab;
    if(free_ab == allocated_block_head){
        //如果要是释放第一个节点
        allocated_block_head = allocated_block_head->next;
        free(free_ab);
        return 1;
    }
    pre = allocated_block_head;
    ab = allocated_block_head->next;
    while(ab!=free_ab){
        pre = ab;
        ab = ab->next;
    }
    pre->next = ab->next;
    free(ab);
    return 2;
}

//更新分区表
int free_mem(AB *ab){
    /* 将ab所表示的已分配区归还,并进行可能的合并 */
    int algorithm = ma_algorithm;
    FBT *fbt,*pre,*work;
    fbt = (FBT*)malloc(sizeof(FBT));
    if(!fbt) return -1;
    /*
    进行可能的合并,基本策略如下?
    1. 将新释放的结点插入到空闲分区队列末尾?
    2. 对空闲链表按照地址有序排列?
    3. 检查并合并相邻的空闲分区?
    4. 将空闲链表重新按照当前算法排序
    */
    fbt->size = ab->size;
    fbt->start_addr = ab->start_addr;

    //插至末尾
    work = free_block;
    if(work == NULL){
        free_block = fbt;
        fbt->next == NULL;
    }else{
        while(work ->next != NULL){
            work = work->next;
        }
        fbt->next = work->next;
        work->next = fbt;
    }
    //按地址重新排布
    rearrange_FF();

    //合并可能分区;即若两空闲分区相连则合并
    pre = free_block;
    while(pre->next){
        work = pre->next;
        if(pre->start_addr + pre->size == work->start_addr ){
            pre->size = pre->size + work->size;
            pre->next = work->next;
            free(work);
            continue;
        }else{
            pre = pre->next;
        }
    }

    //按照当前算法排序
    rearrange(ma_algorithm);
    return 1;
}

//找到pid对应的链表节点
AB *find_process(int pid){
    AB *tmp = allocated_block_head;
    while(tmp != NULL){
        if(tmp->pid == pid){
            return tmp;
        }
        tmp = tmp->next;
    }
    printf("\e[0;31;1m 没有找到进程id为%d的进程! \e[0m\n",pid);
    return NULL;
}

int kill_process(int pid){
    AB *ab;
    ab = find_process(pid);
    if(ab!=NULL){
        free_mem(ab);    //释放ab所表示的分配表
        dispose(ab);    //释放ab数据结构节点
        return 0;
    }else{
        return -1;
    }
}

//寻找是否有分区可以非进程分配
int find_free_mem(int request){
    FBT *tmp = free_block;
    int mem_sum = 0;
    while(tmp){
        if(tmp->size >= request){
            //可以直接分配
            return 1;
        }
        mem_sum += tmp->size;
        tmp = tmp->next;
    }
    if(mem_sum >= request){
        //合并后分配
        return 0;
    }else{
        //没有足够的空间可供分配
        return -1;
    }

}

//将已分配表按起始地址从大到小排序
void sort_AB(){
    if(allocated_block_head == NULL || allocated_block_head->next == NULL)
        return;
    AB *t1,*t2,*head;
    head = allocated_block_head;
    for(t1 = head->next;t1;t1 = t1->next){
        for(t2 = head;t2 != t1;t2=t2->next){
            if(t2->start_addr > t2->next->start_addr){
                int tmp = t2->start_addr;
                t2->start_addr = t2->next->start_addr;
                t2->next->start_addr = tmp;

                tmp = t2->size;
                t2->size = t2->next->size;
                t2->next->size = tmp;
            }
        }
    }
}

//重新给所有进程分配内存地址
void reset_AB(int start){
    /*在真实操作系统中这个操作非常不容易,故内存紧缩并不能频繁使用*/
    AB *tmp = allocated_block_head;
    while(tmp != NULL){
        tmp->start_addr = start;
        start += tmp->size;
        tmp = tmp->next;
    }
}

void  memory_compact(){
    //进行内存紧缩
    FBT *fbttmp = free_block;
    AB *abtmp = allocated_block_head;
    //检测剩余内存
    int sum = 0;
    while(fbttmp!=NULL){
        sum += fbttmp->size;
        fbttmp = fbttmp->next;
    }

    //合并区块为一个
    fbttmp = free_block;
    fbttmp->size = sum;
    fbttmp->start_addr = 0;
    fbttmp->next=NULL;
    
    //释放多余分区
    FBT *pr = free_block->next;
    while(pr != NULL){
        fbttmp = pr->next;
        free(pr);
        pr = fbttmp;
    }
    //重新排序已分配空间
    sort_AB();
    reset_AB(sum);
}

//执行分配内存
void do_allocate_mem(AB *ab){
    int request = ab->size;
    FBT *tmp = free_block;
    while(tmp != NULL){
        if(tmp->size >= request){
            //分配
            ab->start_addr = tmp->start_addr;
            int shengyu = tmp->size - request;
            tmp->size = shengyu;
            tmp->start_addr = tmp->start_addr + request;
            
            return ;
        }
        tmp = tmp->next;
    }
}

int allocate_mem(AB *ab){
    /*分配内存模块*/
    FBT *fbt,*pre;
    int request_size=ab->size;
    fbt = pre = free_block;
    /*
    根据当前算法在空闲分区链表中搜索合适空闲分区进行分配,
        分配时注意以下情况:
    1. 找到可满足空闲分区且分配后剩余空间足够大,则分割
    2. 找到可满足空闲分区且但分配后剩余空间比较小,则一起分配
    3. 找不可满足需要的空闲分区但空闲分区之和能满足需要,
        则采用内存紧缩技术,进行空闲分区的合并,然后再分配
    4. 在成功分配内存后,应保持空闲分区按照相应算法有序
    5. 分配成功则返回1,否则返回-1
    */
    //尝试寻找可分配空闲,具体结果在函数中有解释
    int f = find_free_mem(request_size);
    if(f == -1){
        //不够分配
        printf("空闲内存不足,内存分配失败!\n");
        return -1;
    }else{
        if(f == 0){
            //需要内存紧缩才能分配
            memory_compact();
        }
        //执行分配
        do_allocate_mem(ab);
    }
    //重新排布空闲分区
    rearrange(ma_algorithm);
    return 1;
} 

//为进程分配内存 
int alloc_process(Prc prc){
    AB *ab;
    int ret;
    ab = (AB*)malloc(sizeof(AB));
    if(!ab) exit(-5);
    /*为ab赋值 */ 
    ab->next=NULL;
    pid++;//记录id 
    strcpy(ab->process_name,prc.process_name);
    ab->pid = pid; 
    ab->size=prc.size+rand()%ALLOC_SIZE;//随机分配内存 

    ret = allocate_mem(ab);        //从空闲分区分配内存,ret==1表示分配成功
    if((ret == 1) && (allocated_block_head == NULL)){
        /*如果此时allocated_block_head尚未赋值,则赋值*/
        allocated_block_head = ab;
        return 1;
    }else if(ret == 1){
        /*分配成功,将该分配块的描述插入已分配链表*/
        ab->next = allocated_block_head;
        allocated_block_head = ab;
        return 2;
    }else if(ret == -1){
        //分配不成功
        printf("\e[0;31;1m 内存分配失败! \e[0m\n");
        free(ab);
        return -1;
    }
    return 3;
}

void rearrange_FF(){
    /*首次适应算法,空闲区大小按起始地址升序排序*/
    //这里使用冒泡排序方法
    if(free_block == NULL || free_block->next == NULL)
        return;
    FBT *t1,*t2,*head;
    head = free_block;
    for(t1 = head->next;t1;t1 = t1->next){
        for(t2 = head;t2 != t1;t2=t2->next){
            if(t2->start_addr > t2->next->start_addr){
                int tmp = t2->start_addr;
                t2->start_addr = t2->next->start_addr;
                t2->next->start_addr = tmp;

                tmp = t2->size;
                t2->size = t2->next->size;
                t2->next->size = tmp;
            }
        }
    }
}

/*按指定的算法整理内存空闲块链表*/
void rearrange(int algorithm){
    rearrange_FF();
}

//初始化进程 
void init_program(Prc prc[],int n)
{
    for(int i=0;i<n;++i){
        prc[i].size=PROCESS_SIZE;
        prc[i].pid=-1;
        sprintf(prc[i].process_name,"process-%02d",i+1);
    }
}

参考文献

https://blog.csdn.net/baidu_35085676/article/details/78502503

posted @ 2020-05-15 00:39  红心火柴  阅读(492)  评论(0编辑  收藏  举报