操作系统—实现可变式分区分配算法

news2024/10/7 20:37:02

文章目录

  • 实现可变式分区分配算法
  • 1.实验环境
  • 2.如何在xv6中实现分区分配算法?
    • (1).xv6的内存管理机制
    • (2).实现思路
  • 3.最佳适应算法
    • (1).基本思路
    • (2).步骤
    • (3).测试&Debug
  • 总结
  • 参考资料

实现可变式分区分配算法

1.实验环境

  因为这一次的实验仍然是在xv6中进行,因此本次实验依旧直接采用Lab中使用的xv6内核实验环境:
在这里插入图片描述

2.如何在xv6中实现分区分配算法?

(1).xv6的内存管理机制

  xv6中通过sbrk机制完成内存的分配,可以在sysproc.c中找到sbrk系统调用的定义:

uint64 sys_sbrk(void) {
  int addr;
  int n;

  if (argint(0, &n) < 0) return -1;
  addr = myproc()->sz;
  if (growproc(n) < 0) return -1;
  return addr;
}

  代码比较简单,它只接受一个整数n,之后会调用proc.c中定义的growproc函数:

// Grow or shrink user memory by n bytes.
// Return 0 on success, -1 on failure.
int growproc(int n) {
  uint sz;
  struct proc *p = myproc();

  sz = p->sz;
  if (n > 0) {
    if ((sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) {
      return -1;
    }
  } else if (n < 0) {
    sz = uvmdealloc(p->pagetable, sz, sz + n);
  }
  p->sz = sz;
  return 0;
}

  这串代码的阅读难度就相对要大一点,首先是从当前进程中读取出内存栈的大小,参数n的正负决定了内存是扩张还是收缩,对于扩张的情况,growproc会尝试调用uvmalloc完成扩张n字节的操作,而uvmalloc的函数定义可以在vm.c当中找到(uvdealloc也可以找到):

// Allocate PTEs and physical memory to grow process from oldsz to
// newsz, which need not be page aligned.  Returns new size or 0 on error.
uint64 uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz) {
  char *mem;
  uint64 a;

  if (newsz < oldsz) return oldsz;

  oldsz = PGROUNDUP(oldsz);
  for (a = oldsz; a < newsz; a += PGSIZE) {
    mem = kalloc();
    if (mem == 0) {
      uvmdealloc(pagetable, a, oldsz);
      return 0;
    }
    memset(mem, 0, PGSIZE);
    if (mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_W | PTE_X | PTE_R | PTE_U) != 0) {
      kfree(mem);
      uvmdealloc(pagetable, a, oldsz);
      return 0;
    }
  }
  return newsz;
}
// Deallocate user pages to bring the process size from oldsz to
// newsz.  oldsz and newsz need not be page-aligned, nor does newsz
// need to be less than oldsz.  oldsz can be larger than the actual
// process size.  Returns the new process size.
uint64 uvmdealloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz) {
  if (newsz >= oldsz) return oldsz;

  if (PGROUNDUP(newsz) < PGROUNDUP(oldsz)) {
    int npages = (PGROUNDUP(oldsz) - PGROUNDUP(newsz)) / PGSIZE;
    uvmunmap(pagetable, PGROUNDUP(newsz), npages, 1);
  }

  return newsz;
}

  这里首先研究uvmalloc函数,它首先确定新的内存大小要大于等于之前的内存大小,之后通过riscv.h当中的宏函数PGROUNDUP将oldsz向上转换成页表大小整倍数的内存大小:

#define PGROUNDUP(sz)  (((sz)+PGSIZE-1) & ~(PGSIZE-1))

  这个函数本身并不难理解,例如现在的PGSIZE被定为4096,则当sz为4094字节的时候,PGROUNDUP的结果就是:4096,而当sz为4097字节的时候结果是8192,所以它的行为的确是这样:先算出需要页表的数量,然后向上取整得到真正的字节数。
  这样做的目的也是显然的:因为内存分配是依靠kalloc每次分配一个页表完成的,因此如果不转换成对应的大小,后续操作会不太方便
  在这之后,uvmalloc的操作就很明确了:利用循环的方式,每一次都利用kalloc函数分配一页(4096字节)内存,每次都会尝试进行一次页表到物理内存的映射,如果成功,则继续下一轮循环继续分配,直到分配到最后达到了新的内存大小为止
  uvdealloc的操作更加简单,对于确实需要减少分配的情况,在计算出需要减少分配的页表的数量之后,使用uvunmap取消页表到物理内存的映射关系即可

  因此growproc的行为到这里也就明确了:利用uvmalloc和uvdealloc两个函数完成对于当前进程内存(本质是页表)的收缩和扩张,从而完成用户内存的变化。

  之后就可以回到sys_sbrk这个系统调用了,它的思路相当简单:利用growproc函数完成用户进程内存的扩张

(2).实现思路

  前面的分配过程基本上都与希望实现的几个算法无关,不过比较有必要注意的是sbrk,实际上xv6的动态内存分配应该是基于sbrk实现的,malloc的代码如下:

void *malloc(uint nbytes) {
  Header *p, *prevp;
  uint nunits;

  nunits = (nbytes + sizeof(Header) - 1) / sizeof(Header) + 1;
  if ((prevp = freep) == 0) {
    base.s.ptr = freep = prevp = &base;
    base.s.size = 0;
  }
  for (p = prevp->s.ptr;; prevp = p, p = p->s.ptr) {
    if (p->s.size >= nunits) {
      if (p->s.size == nunits)
        prevp->s.ptr = p->s.ptr;
      else {
        p->s.size -= nunits;
        p += p->s.size;
        p->s.size = nunits;
      }
      freep = prevp;
      return (void *)(p + 1);
    }
    if (p == freep)
      if ((p = morecore(nunits)) == 0) return 0;
  }
}

  它会以一个链表查找的方式一个个遍历查找,直到找到一个和需要分配的空间大小匹配的块,然后完成分配等等工作,在没有办法找到可供分配的块的情况下,malloc函数会调用morecore函数进行空间的扩张:

static Header *morecore(uint nu) {
  char *p;
  Header *hp;

  if (nu < 4096) nu = 4096;
  p = sbrk(nu * sizeof(Header));
  if (p == (char *)-1) return 0;
  hp = (Header *)p;
  hp->s.size = nu;
  free((void *)(hp + 1));
  return freep;
}

  morecore函数做的事情是:每一次利用sbrk函数将用户内存(堆空间)至少扩大4096个Header大小,之后再利用free函数将这个分配的新空间加入到当前进程的空闲链表当中

  因此基于这个思考,我们或许只需要在umalloc.c当中实现一个基于最佳适配算法的malloc函数即可。

3.最佳适应算法

(1).基本思路

  最佳适应算法比较简单,它的基本思路就是从分区表中依次遍历,直到找到一个与需要分配的空间为最佳适配的大小。

(2).步骤

  在umalloc.c当中增加一个函数myalloc:

void* myalloc(uint nbytes) {
  Header *p, *prevp, *best, *prevb;
  uint nunits;

  nunits = (nbytes + sizeof(Header) - 1) / sizeof(Header) + 1;
  if ((prevp = freep) == 0) {
    base.s.ptr = freep = prevp = &base;
    base.s.size = 0;
  }
  best = 0;
  prevb = 0;
  for (p = prevp->s.ptr;; prevp = p, p = p->s.ptr) {
    if (p->s.size >= nunits) {
      if (p->s.size == nunits) {
        prevb = prevp;
        best = p;
        break;
      }
      else {
        if (p->s.size < best->s.size) {
            prevb = prevp;
            best = p;
        }
    }
    if (p == freep)
      if ((p = morecore(nunits)) == 0) return 0;
  }
  if (best->s.size != nunits) {
    best->s.size -= nunits;
    best += p->s.size;
    p->s.size = nunits;
  }
  freep = prevb;
  return (void *)(best + 1);
}

  它的实现与malloc几乎完全一致,只是在搜索可用空间块的时候每次多维护一个best和prevb指针,用于找到能够与需要分配大小(对齐后的大小)最接近的块
  如果myalloc找到了与需要空间正好相等的大小,则直接中止循环,否则会继续尝试寻找更优的一块内存空间,在循环结束之后,如果待分配的空间大于需要的大小,则会进行一次切割,从最佳分配的块中切割出相等大小的内存,之后再将块的头部信息记录到freep当中,然后返回分配后空间的地址。

(3).测试&Debug

  首先在user.h中添加刚才增加的myalloc函数的原型:
在这里插入图片描述
  接下来可以编写一个用户态程序myalloctest来进行测试:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[]) {
  if (argc != 2) {
    printf("Assertion: must have one argument\n");
    exit(1);
  }
  int arg = atoi(argv[1]);
  if (arg <= 0) {
    printf("Assertion: size cannot be less equal than 0\n");
    exit(2);
  }
  uint sz = (uint)arg;
  printf("==myalloctest==\n");
  char* addr = (char*)myalloc(sz);
  memset(addr, 'a', sz);
  addr[sz-1] = 0;
  printf("%s\n", addr);
  free(addr);
  exit(0);
}

  在Makefile文件中添加用户态程序:

  然后尝试运行,但是失败了:
在这里插入图片描述
  这种情况下连字符串都不会打印出来,但是当测试的字节数特别大的时候,这个程序能成功打印出应该是和分配字节数相匹配个数的a,但是最终程序会卡住,所以我或许要从usertrap的这个报错入手解决。

  这个报错和上次的自定义调度算法很像,都是在usertrap处理用户trap的时候发生了无法识别的外部中断或软中断,而实际上唯一能触发usertrap的就只有myalloc函数,因为myalloc其中可能会调用morecore,而之后会调用sbrk系统调用。

  经过一些printf测试之后发现,p和best在没有任何额外赋值的情况下总是一样的,此时大概可以确定:myalloc在持续使用morecore尝试分配更多内存,由此才导致了异常

  因此之后我尝试将代码修改成了下面这样:

void* myalloc(uint nbytes) {
  Header *p, *prevp, *best, *prevb;
  uint nunits;

  nunits = (nbytes + sizeof(Header) - 1) / sizeof(Header) + 1;
  if ((prevp = freep) == 0) {
    base.s.ptr = freep = prevp = &base;
    base.s.size = 0;
  }
  best = 0;
  prevb = 0;
  for (p = prevp->s.ptr;; prevp = p, p = p->s.ptr) {
    if (p->s.size >= nunits) {
      if (best == 0) {
        prevb = prevp;
        best = p;
        continue;
      }

      if (p->s.size == nunits) {
        prevb = prevp;
        best = p;
        break;
      }
      else {
        if (p->s.size < best->s.size) {ß
            prevb = prevp;
            best = p;
        }
      }
    }
    if (best == 0 && p == freep) {
      if ((p = morecore(nunits)) == 0) return 0;
    }
    else if (p == freep) break;
  }
  if (best->s.size != nunits) {
    best->s.size -= nunits;
    best += p->s.size;
    p->s.size = nunits;
  }
  freep = prevb;
  return (void *)(best + 1);
}

  与之对应的,将myalloctest函数修改为下面的样子:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[]) {
  if (argc != 2) {
    printf("Assertion: must have one argument\n");
    exit(1);
  }
  int arg = atoi(argv[1]);
  if (arg <= 0) {
    printf("Assertion: size cannot be less equal than 0\n");
    exit(2);
  }
  uint sz = (uint)arg;
  printf("==myalloctest==\n");
  char* addr = (char*)myalloc(sz);
  memset(addr, 'a', sz);
  addr[sz-1] = 0;
  printf("%s\n", addr);
  free(addr);
  exit(0);
}

  经过运行测试之后发现,打印的步骤已经能够顺利完成了,但是内存的释放部分还不正常:
在这里插入图片描述
  打印的字符数量符合一开始输入的数字,但是在之后进行free的时候就会卡住,这里可以看一下free的代码:

void free(void *ap) {
  Header *bp, *p;

  bp = (Header *)ap - 1;
  for (p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr)
    if (p >= p->s.ptr && (bp > p || bp < p->s.ptr)) break;
  if (bp + bp->s.size == p->s.ptr) {
    bp->s.size += p->s.ptr->s.size;
    bp->s.ptr = p->s.ptr->s.ptr;
  } else
    bp->s.ptr = p->s.ptr;
  if (p + p->s.size == bp) {
    p->s.size += bp->s.size;
    p->s.ptr = bp->s.ptr;
  } else
    p->s.ptr = bp;
  freep = p;
}

  里面的确有一个for循环,或许就是在这里被卡住了,或许是因为我采用了malloc的free函数,在这个情况下可能会导致free需要的freep指针发生紊乱,因此可以直接仿照free写出myalloc对应的myfree函数:

void myfree(void *ap) {
  Header *bp, *p;

  bp = (Header *)ap - 1;
  for (p = freeb; !(bp > p && bp < p->s.ptr); p = p->s.ptr)
    if (p >= p->s.ptr && (bp > p || bp < p->s.ptr)) break;
  if (bp + bp->s.size == p->s.ptr) {
    bp->s.size += p->s.ptr->s.size;
    bp->s.ptr = p->s.ptr->s.ptr;
  } else
    bp->s.ptr = p->s.ptr;
  if (p + p->s.size == bp) {
    p->s.size += bp->s.size;
    p->s.ptr = bp->s.ptr;
  } else
    p->s.ptr = bp;
  freeb = p;
}

  修改myalloctest.c如下:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[]) {
  if (argc != 2) {
    printf("Assertion: must have one argument\n");
    printf("==myalloctest failed==\n");  
    exit(1);
  }
  int arg = atoi(argv[1]);
  if (arg <= 0) {
    printf("Assertion: size cannot be less equal than 0\n");
    printf("==myalloctest failed==\n");
    exit(2);
  }
  uint sz = (uint)arg;
  printf("==myalloctest==\n");
  char* addr = (char*)myalloc(sz);
  memset(addr, 'a', sz);
  addr[sz-1] = 0;
  printf("%s\n", addr);
  myfree(addr);
  printf("==myalloctest success==\n");
  exit(0);
}

  之后再运行,就正常了:
在这里插入图片描述

  但我发现我实际上忘记把myalloc的最后一步改成freeb,当我改了之后,myfree处的死循环,它又出现了:
在这里插入图片描述
  结果还是没有成功,只能说:我只实现了按照最佳适应算法进行分配内存,但是没有实现对应的free函数,内存不能正常完成释放

总结

  实现分区分配算法算是一个比较困难的工作了,因为网络上的资料其实非常少,大部分能搜得到的内容都是借助编程语言模拟实现的算法,并不是真实在内核中实现的,因此做这个作业的确是花费了比较多的功夫。

  不过好在结果是好的,虽然到最后我也没有在内核中完全成功实现这个算法(free没有实现),但是整个作业过程当中我已经基本明白了xv6内核的内存管理机制,不过这应该也是因为我对内存管理的各种机制不了解,因此在未来学习完了内存管理之后,我应该还会继续尝试修复这些问题,完成一对真实能运行的myalloc-myfree函数。

参考资料

  • [CSDN]-xv6源码解析(三)——内存管理
  • [CSDN]-系统调用与内存管理(sbrk、brk、mmap、munmap)
  • [GitHub]-Vikalloc_xv6_Operating_System
  • [geeksforgeeks]-Program for Best Fit algorithm in Memory Management
  • [geeksforgeeks]-Best-Fit Allocation in Operating System

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1600983.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【AIGC】AIGC在虚拟数字人中的应用:塑造未来互动体验的革新力量

&#x1f680; &#x1f680; &#x1f680;随着科技的快速发展&#xff0c;AIGC已经成为引领未来的重要力量。其中&#xff0c;AIGC在虚拟数字人领域的应用更是引起了广泛关注。虚拟数字人作为一种先进的数字化表达形式&#xff0c;结合了3D建模、动画技术、人工智能等多种先进…

PaddleOCR训练自己模型(2)----参数配置及训练

一、介绍 paddleocr分为文字定位(Det)和文字识别(Rec)两个部分 二、定位模型训练 &#xff08;1&#xff09;Det预训练模型下载&#xff1a;https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_det_train.tar &#xff08;2&#xff09;下载完之后&#xff0c;…

女上司问我:误删除PG百万条数据,可以闪回吗?

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验 擅长主流数据Oracle、MySQL、PG、openGauss运维 备份恢复&#xff0c;安装迁移&#xff0c;性能优化、故障应急处理等可提供技术业务&#xff1a; 1.DB故障处理/疑难杂症远程支援 2.Mysql/PG/Oracl…

32. BI - 依据淘宝的用户行为,从 0 开始实现一个简单的移动推荐系统

本文为 「茶桁的 AI 秘籍 - BI 篇 第 32 篇」 Hi, 你好。我是茶桁。 今天咱们要来完成一个简单的推荐系统的建立。 之前的课程里给大家讲了两种模型&#xff0c;也希望大家对模型的概念以及使用场景会有些了解。不光是推荐系统&#xff0c;在生物、心理学、社交网络等等里面都…

LangChain-Chatchat 开源知识库来了

LangChain-Chatchat 开源知识库来了 LangChain-Chatchat 架构设计LangChain-ChatChat 具体实现过程 一键本地离线部署软件环境硬件环境支持三种部署方式 LangChain-Chatchat 是基于 ChatGLM 等大语言模型与 LangChain 等应用框架实现&#xff0c;开源、可离线部署的 RAG 检索增…

安全特低电压 SELV(Safety Extra Low Voltage,缩写SELV) 是不接地系统的安全特低电压

SELV LED驱动器 市场上有很多LED灯是非隔离的&#xff0c;甚至还有灯条要100多伏特电压才能点亮的&#xff0c;安全吗&#xff1f; 国外多数LED驱动器标注了SELV&#xff0c;为什么&#xff1f; 安全特低电压 SELV(Safety Extra Low Voltage&#xff0c;缩写SELV) 是不接地系…

通过adb 命令打印安装在第三方模拟器上的log

1&#xff0c;环境&#xff1a;Windows 11 &#xff0c;第三方模拟器 网易的MuMu 步骤&#xff1a; 1&#xff0c;打开cmd&#xff0c;输入 adb connect 172.0.0.1:7555 2&#xff0c;在cmd&#xff0c;再次输入adb logcat 回车

MongoDB的CURD(增删改查操作)

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 ✈️如果喜欢这篇文章的话 &#x1f64f;大大们可以动动发财的小手&#x1f449;&#…

初步学习node.js文件模块

环境已安装好&#xff1b; 写一个read1.js如下&#xff1b; var fs require("fs"); var data ;// 创建一个流 var stream1 fs.createReadStream(test1.jsp); stream1.setEncoding(UTF8);// 绑定data事件 stream1.on(data, function(mydata) {data mydata; });/…

竞赛 基于GRU的 电影评论情感分析 - python 深度学习 情感分类

文章目录 1 前言1.1 项目介绍 2 情感分类介绍3 数据集4 实现4.1 数据预处理4.2 构建网络4.3 训练模型4.4 模型评估4.5 模型预测 5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于GRU的 电影评论情感分析 该项目较为新颖&#xff0c;适合作为竞…

注册表让我重回80年代(狗头保命

现在是2024年4月16日23:09:07&#xff0c;今天之所以这么晚才睡&#xff0c;是因为遇到了一个很有意思的事情&#xff0c;以至于解决完之后&#xff0c;强挺困意&#xff0c;将其记录—— 缘由是想只用键盘操纵电脑&#xff0c;上面有写&#xff0c;那用winR就是家常便饭。只不…

贴片滚珠振动开关 / 振动传感器的用法

就是这种小东西&#xff1a; 上面的截图来自&#xff1a;https://item.szlcsc.com/3600130.html 以前写过一篇介绍这种东西内部的结构原理&#xff1a;贴片微型滚珠振动开关的结构原理。就是有个小滚珠会接通开关两边的电极&#xff0c;振动时滚珠会在内部蹦跳&#xff0c;开关…

基于Springboot的影城管理系统

基于SpringbootVue的影城管理系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页展示 电影信息 电影资讯 后台登录页 后台首页 用户管理 电影类型管理 放映…

RabbitMQ Stream插件使用详解

2.4版为RabbitMQ流插件引入了对RabbitMQStream插件Java客户端的初始支持。 RabbitStreamTemplateStreamListener容器 将spring rabbit流依赖项添加到项目中&#xff1a; <dependency><groupId>org.springframework.amqp</groupId><artifactId>sprin…

WebKit内核游览器

WebKit内核游览器 基础概念游览器引擎Chromium 浏览器架构Webkit 资源加载这里就不得不提到http超文本传输协议这个概念了&#xff1a; 游览器多线程HTML 解析总结 基础概念 百度百科介绍 WebKit 是一个开源的浏览器引擎&#xff0c;与之相对应的引擎有Gecko&#xff08;Mozil…

C# 字面量null对于引用类型变量和值类型变量

编译器让相同的字符串字面量共享堆中的同一内存位置以节约内存。 在C#中&#xff0c;字面量&#xff08;literal&#xff09;是指直接表示固定值的符号&#xff0c;比如数字、字符串或者布尔值。而关键字&#xff08;keyword&#xff09;则是由编程语言定义的具有特殊含义的标…

mysql 转pg 两者不同的地方

因项目数据库&#xff08;原来是MySQL&#xff09;要改成PostgreSQL。 项目里面的sql要做一些调整。 1&#xff0c;写法上的区别&#xff1a; 1&#xff0c;数据准备&#xff1a; 新建表格&#xff1a; CREATE TABLE property_config ( CODE VARCHAR(50) NULL…

PHP一句话木马

一句话木马 PHP 的一句话木马是一种用于 Web 应用程序漏洞利用的代码片段。它通常是一小段 PHP 代码&#xff0c;能够在目标服务器上执行任意命令。一句话木马的工作原理是利用 Web 应用程序中的安全漏洞&#xff0c;将恶意代码注入到服务器端的 PHP 脚本中。一旦执行&#xf…

免费ssl通配符证书申请教程

在互联网安全日益受到重视的今天&#xff0c;启用HTTPS已经成为网站运营的基本要求。它不仅保障用户数据传输的安全&#xff0c;提升搜索引擎排名&#xff0c;还能增强用户对网站的信任。通配符证书是一种SSL/TLS证书&#xff0c;用于同时保护一个域名及其所有下一级子域名的安…

【Qt】:界面优化(一:基本语法)

界面优化 一.基本语法1.设置指定控件样式2.设置全局控件样式3.从文件加载样式表4.使⽤Qt Designer编辑样式&#xff08;最常用&#xff09; 二.选择器1.概述2.子控件选择器3.伪类型选择器 三.盒模型 在网页前端开发领域中,CSS是一个至关重要的部分.描述了一个网页的"样式&…