6.s081 学习实验记录(九)lock parallelism

文章目录

    • 一、Memory allocator
      • 简介
      • 提示
      • 实验代码
      • 实验结果
    • 二、Buffer cache
      • 简介
      • 提示
      • 实验代码
      • 实验结果

该实验将重构某些代码以提高并发度。

首先切换到lock分支:

  • git fetch
  • git checkout lock
  • make clean

一、Memory allocator

简介

user/kalloctest 这个程序会对xv6的内存分配器进行压力测试,该测试中三个进程会扩大缩小其地址空间(使用 kalloc、kfree),而这会导致 kmem.lock 的竞争。该测试会在 acquire 中打印出为了获取锁而循环等待的次数,这可以作为锁竞争的粗略指标。在未进行任何优化前,打印如下图:
在这里插入图片描述
这里竞争严重的问题原因是空闲内存由一个链表来维护,多个核情况下存在并发竞争,需要锁来保护。因此,一个简单的思想是每个核都维护一个空闲链表,每个核的空闲链表拥有自己专门的锁来保证并发安全。需要注意的是,我们要处理一个核的空闲链表已经没有内存了,但另外一个核的空闲链表还存在空闲内存的情况,即需要有内存窃取机制。

提示

  • 你可以使用kernel/param.h中定义的常量NCPU
  • freerange函数返回所有的空闲内存给运行 freerange 的cpu
  • 可以使用 cpuid 来获取当前cpu的id,但是需要关闭中断,可以使用push_off()pop_off()控制中断的开关
  • 可以使用xv6的race detector来辅助定位问题,其可以帮助检查内存是否重复分配,如果存在内存分配竞争,其将会打印如下内容:
    • make clean
    • make KCSAN=1 qemu
    • kalloctest
      在这里插入图片描述
  • 可以将 race detector 打印的内容通过 riscv64-linux-gnu-addr2line -e kernel/kernel 转换成对应的函数名
    在这里插入图片描述

实验代码

本实验主要修改内存申请释放模块,即主要修改kalloc.c文件。

  • 将空闲链表分为每个cpu一个,且每个cpu一个锁
  • 修改 kinit() 初始化每个cpu的空闲链表
  • 修改kalloc()分配内存时通过 cpuid() 获取当前cpu id,从自己的空闲链表中获取内存;kfree()同理
  • kalloc()中需要添加内存盗取逻辑

修改空闲链表每个cpu一个

struct {
  struct spinlock lock;
  struct run *freelist;
} kmem[NCPU];

更改初始化逻辑

void
kinit()
{
  char name[16];
  for(int i = 0; i < NCPU; i++){
    snprintf(name, sizeof(name), "kmem_cpu%d", i);
    initlock(&kmem[i].lock, name);
  }
  freerange(end, (void*)PHYSTOP);
}

更改分配逻辑

void *
kalloc(void)
{
  struct run *r;
  int cpu = -1;

  push_off(); //关中断
  cpu = cpuid();
  pop_off(); // 开中断

  acquire(&kmem[cpu].lock);
  r = kmem[cpu].freelist;
  if(r){
    kmem[cpu].freelist = r->next;
  } else {
    // 内存窃取
    struct run* tmp;
    for (int i = 0; i < NCPU; ++i) {
      if (i == cpu) continue;
      acquire(&kmem[i].lock);
      tmp = kmem[i].freelist;
      if (tmp == 0) {
        release(&kmem[i].lock);
        continue;
      } else {
        // 窃取一个页面
        kmem[cpu].freelist = kmem[i].freelist;
        kmem[i].freelist = tmp->next;
        tmp->next = 0;
        release(&kmem[i].lock);
        break;
      }
    }
    r = kmem[cpu].freelist;
    if (r)
      kmem[cpu].freelist = r->next;
  }
  release(&kmem[cpu].lock);

  if(r)
    memset((char*)r, 5, PGSIZE); // fill with junk
  return (void*)r;
}

更改回收逻辑

void
kfree(void *pa)
{
  struct run *r;
  int cpu = -1;

  if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");

  // Fill with junk to catch dangling refs.
  memset(pa, 1, PGSIZE);

  r = (struct run*)pa;

  push_off();
  cpu = cpuid();
  pop_off();

  acquire(&kmem[cpu].lock);
  r->next = kmem[cpu].freelist;
  kmem[cpu].freelist = r;
  release(&kmem[cpu].lock);
}

实验结果

  • kalloctest
    在这里插入图片描述

  • usertests sbrkmuch
    在这里插入图片描述

  • usertests -q
    在这里插入图片描述

二、Buffer cache

简介

当多个进程密集的访问文件系统时,它们可能会争夺 bcache.lock ,该锁保护 kernel/bio.c 中磁盘块缓存。bcachetest 将创建多个重复读取不同文件的进程,以使得产生 bcache.lock 的争用,其输出可能如下(在未完成实验之前):
在这里插入图片描述
bcache.lock 保护了很多临界区,包括:the list of cached block buffers, the reference count (b->refcnt) in each block buffer, and the identities of the cached blocks (b->dev and b->blockno).

我们需要修改锁的粒度,实现所有锁在acquire()中的打印接近于0,即每个锁的获取几乎不需要等待。修改 bget() 和 brelse() 函数,使得 bcache 中不同块的并发查找和释放不太可能发生冲突。

必须保证每个块在bcache中最多一份缓存。

完成该实验后,运行下列命令打印如下:
在这里插入图片描述

提示

  • 所有锁的名称必须用bcache开头,并使用initlock() 初始化这些锁
  • 可以采用给每个hash桶一个锁的方式来实现
  • 可以扩大hash桶来减少桶内元素的竞争
  • 阅读 xv6-book 的 section 8.1-8.3,了解xv6的块缓存
  • hash桶的大小可以采用质数(例如13)来减少冲突,hash表的大小可以是固定,不用动态扩容
  • hash表中寻找buffer缓存和当搜寻不到,为该块分配缓存时的操作必须是原子的
  • 删除所有bcache中的buffer list(bcache.head),并且不使用LRU算法,这样 relse() 可以不用获取 bcache 锁bget()中可以选择任意一个 refcnt == 0的块。

实验代码

  • 主要修改 bio.c,该实验由于时间原因,取网上答案且未验证
    重新定义bcache,包含13个hash桶,每个桶一个锁,hash算法使用最简单的blockno % NBUCKET
#define NBUCKET 13
struct {
  struct spinlock lock;
  struct buf head[NBUCKET];
  struct buf hash[NBUCKET][NBUF];
  struct spinlock hashlock[NBUCKET]; // lock per bucket
} bcache;
void
binit(void)
{
  struct buf *b;

  initlock(&bcache.lock, "bcache");
  for (int i = 0; i < NBUCKET; i++) {
    initlock(&bcache.hashlock[i], "bcache");

    // Create linked list of buffers
    bcache.head[i].prev = &bcache.head[i];
    bcache.head[i].next = &bcache.head[i];
    for(b = bcache.hash[i]; b < bcache.hash[i]+NBUF; b++){
      b->next = bcache.head[i].next;
      b->prev = &bcache.head[i];
      initsleeplock(&b->lock, "buffer");
      bcache.head[i].next->prev = b;
      bcache.head[i].next = b;
    }
  }
}

static struct buf*
bget(uint dev, uint blockno)
{
  struct buf *b;

  uint hashcode = blockno % NBUCKET;
  acquire(&bcache.hashlock[hashcode]);

  // Is the block already cached?
  for(b = bcache.head[hashcode].next; b != &bcache.head[hashcode]; b = b->next){
    if(b->dev == dev && b->blockno == blockno){
      b->refcnt++;
      release(&bcache.hashlock[hashcode]);
      acquiresleep(&b->lock);
      return b;
    }
  }

  // Not cached.
  // Recycle the least recently used (LRU) unused buffer.
  for(b = bcache.head[hashcode].prev; b != &bcache.head[hashcode]; b = b->prev){
    if(b->refcnt == 0) {
      b->dev = dev;
      b->blockno = blockno;
      b->valid = 0;
      b->refcnt = 1;
      release(&bcache.hashlock[hashcode]);
      acquiresleep(&b->lock);
      return b;
    }
  }
  panic("bget: no buffers");
}

void
brelse(struct buf *b)
{
  if(!holdingsleep(&b->lock))
    panic("brelse");

  releasesleep(&b->lock);

  uint hashcode = b->blockno % NBUCKET;
  acquire(&bcache.hashlock[hashcode]);
  b->refcnt--;
  if (b->refcnt == 0) {
    // no one is waiting for it.
    b->next->prev = b->prev;
    b->prev->next = b->next;
    b->next = bcache.head[hashcode].next;
    b->prev = &bcache.head[hashcode];
    bcache.head[hashcode].next->prev = b;
    bcache.head[hashcode].next = b;
  }

  release(&bcache.hashlock[hashcode]);
}

实验结果

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

安装 Windows 7

1.镜像安装 镜像安装:安装Windows 7 2.安装过程(直接以图的形式呈现) 等待安装成功即可

2.18作业

通过字符设备驱动分步注册过程实现LED驱动的编写&#xff0c;编写应用程序测试 头文件 #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct{unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }gpio…

Jetpack Compose 第 1 课:可组合函数

点击查看&#xff1a;Jetpack Compose 教程 点击查看&#xff1a;Composetutorial 代码 简介 Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API&#xff0c;可以帮助您简化并加快 Android 界面开发。 在本教程中&a…

视觉开发板—K210自学笔记(五)--按键控制LED

本期我们来遵循其他单片机的学习路线开始去用板子上的按键控制点亮LED。那么第一步还是先知道K210里面的硬件电路是怎么连接的&#xff0c;需要查看第二节的文档&#xff0c;看看开发板原理图到底是按键是跟哪个IO连在一起。然后再建立输入按键和GPIO的映射就可以开始变成了。 …

Instagram 账号被封怎么办?如何申诉拿回账号?

不知道各位在玩转海外社媒平台时有没有遇到过Instagram账号异常的情况&#xff0c;比如会出现账号受限、帖子发不出去、账号被封号等情况?Instagram账号如果被封不用马上弃用&#xff0c;我们可以先尝试一下申诉&#xff0c;看看能不能把账号解封。所以今天将会出一篇Instagra…

综合布线实训室建设方案2024

综合布线实训室概述 随着智慧城市的发展&#xff0c;人工智能、物联网、云计算、大数据等新鲜行业的兴起&#xff0c;网络布线系统是现代智慧城市、智慧社区、智能建筑、智能家居、智能工厂和现代服务业的基础设施和神经网络&#xff0c;实践表明网络系统的故障 70%发生在布线…

生成式 AI - Diffusion 模型的数学原理(4)

来自 论文《 Denoising Diffusion Probabilistic Model》&#xff08;DDPM&#xff09; 论文链接&#xff1a; https://arxiv.org/abs/2006.11239 Hung-yi Lee 课件整理 文章目录 一、 q &#xff08; x t ∣ x t − 1 &#xff09; q&#xff08;x_{t} \mid x_{t-1} &#xff…

JVM常见问题笔记分享

文章目录 1 JVM组成1.1 JVM由那些部分组成&#xff0c;运行流程是什么&#xff1f;1.2 什么是程序计数器&#xff1f;1.3 你能给我详细的介绍Java堆吗?元空间(MetaSpace)介绍 1.4 什么是虚拟机栈1.5 堆和栈的区别1.6 能不能解释一下方法区&#xff1f;1.5.1 概述1.5.2 常量池1…

C++题目打卡2.18

从今天开始我们又将讲4天题目。 题目列表 1.分配T4 2.组合T5 #分配T4 这里很明显是&#xff08;200 110&#xff09; - 330的差值最小。 我们先想到了一个想法就是输入时哪个堆大,加那个。 #include <bits/stdc.h> using namespace std; int main(){int n, ans1 0, …

Win11家庭版,鸿蒙DevEco 模拟器启动失败,成功解决了

本人电脑系统&#xff1a;Windows 11 家庭版 正常安装模拟器后&#xff0c;启动失败&#xff0c;查了各种方法&#xff0c;最终发现是电脑虚拟机未启动导致的。 官方给出的解决方法&#xff08;对我无效&#xff01;&#xff01;&#xff01;&#xff09;&#xff1a; 我的…

MySQL之select查询

华子目录 SQL简介SQL语句分类SQL语句的书写规范SQL注释单行注释多行注释 select语句简单的select语句select的算数运算select 要查询的信息 from 表名;查询表字段查询常量查询表达式查询函数 查询定义别名as安全等于<>去重distinct连接字段concat 模糊查询运算符比较运算…

Linux网络----防火墙

一、安全技术和防火墙 1、安全技术 入侵检测系统&#xff08;Intrusion Detection Systems&#xff09;&#xff1a;特点是不阻断任何网络访问&#xff0c;量化、定位来自内外网络的威胁情况&#xff0c;主要以提供报警和事后监督为主&#xff0c;提供有针对性的指导措施和安…

代码随想录第33天|● 1005.K次取反后最大化的数组和 ● 134. 加油站 ● 135. 分发糖果

文章目录 1005.K次取反后最大化的数组和贪心思路&#xff1a;代码&#xff1a; 34. 加油站思路一&#xff1a;全局贪心代码&#xff1a; 思路二&#xff1a;代码&#xff1a; 135. 分发糖果思路&#xff1a;两边考虑代码&#xff1a; 1005.K次取反后最大化的数组和 贪心思路&am…

Stackoverflow(1)-根据RequestBody的内容来区分使用哪个资源

如果使用Spring&#xff0c;可以通过RequestBody将请求体的json转换为Java对象&#xff0c;但如果URI相同&#xff0c;而请求体的内容不同&#xff0c;应该怎么办&#xff1f;问题来源(stackoverflow)&#xff1a;Spring RequestBody without using a pojo?稍微研究了一下&…

手把手一起开发SV4E-I3C设备(三)

JEDEC DDR5 SPD Hub Devices例程 为进一步方便程序开发&#xff0c;使用vscode开发程序&#xff0c;IntrospectESP_23.3.0软件调用运行&#xff0c;如图所示&#xff0c;双击UTILITY区域的PythonModule&#xff0c;自动生成一个py文件&#xff0c;可以给该模块重命名。例如重命…

重学Java 16.面向对象.4.对象数组

一、对象数组 1.需求&#xff1a;定义一个长度为3的数组&#xff0c;存储3个person对象&#xff0c;遍历数组&#xff0c;将三个person对象中的属性值获取出来 public class Person {private String name;private int age;public Person(String name,int age) {this.name name…

Yii2项目使用composer异常记录

问题描述 在yii2项目中&#xff0c;使用require命令安装依赖时&#xff0c;出现如下错误提示 该提示意思是&#xff1a;composer运行时&#xff0c;执行了yiisoft/yii2-composer目录下的插件&#xff0c;但是该插件使用的API版本是1.0&#xff0c;但是当前的cmposer版本提供的…

单体工程结构

本文主要说明下单体项目的工程结构如何设计&#xff0c;目前业界存在两种主流的应用工程结构&#xff1a;一种是阿里推出的《Java开发手册》中推荐的&#xff0c;另外一种是基于DDD(领域驱动设计)推荐的。下面我们来看下两种工程结构是怎样的。 一、 基于阿里《Java开发手册》…

生成式 AI - Diffusion 模型的数学原理(3)

来自 论文《 Denoising Diffusion Probabilistic Model》&#xff08;DDPM&#xff09; 论文链接&#xff1a; https://arxiv.org/abs/2006.11239 Hung-yi Lee 课件整理 文章目录 一、图像生成模型本质上的共同目标二、最大似然估计三、和VAE的关联四、概率计算 一、图像生成模…

三维模型优化与可视化开发者服务

一站式服务开发者 1、极速流畅的浏览体验 无需安装插件&#xff0c;实现模型多端展示 最大支持100G模型&#xff0c;杜绝花、卡、闪 2、丰富易用的开发工具 无需掌握图形技术&#xff0c;实现模型轻量化和3D交互展示 提供丰富的SDK和API&#xff0c;简洁易用 老子云API 提供…
最新文章