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

news2024/11/26 10:04:20

文章目录

    • 一、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.coloradmin.cn/o/1456852.html

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

相关文章

企事业单位 | 公司办公终端、电脑文件数据\资料防泄密软件系统——防止核心数据资料外泄!

天锐绿盾是一款专门设计用于防止公司文件数据泄露的软件。 PC端&#xff1a;https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 以下是该软件的几个关键特点&#xff1a; 文件加密&#xff1a;天锐绿盾使用先进的加密技术&#xff0c;对存储在电脑…

SpringIOC之support模块SimpleThreadScope

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

Dubbo框架admin搭建

Dubbo服务监控平台&#xff0c;dubbo-admin是图形化的服务管理界面&#xff0c;从服务注册中心获取所有的提供者和消费者的配置。 dubbo-admin是前后端分离的项目&#xff0c;前端使用Vue&#xff0c;后端使用springboot。因此&#xff0c;前端需要nodejs环境&#xff0c;后端需…

上百份信任印记,见证我们与客户共赴的数智化征程

回看2023&#xff0c;这注定是不平凡的一年&#xff01; 全国经济加快复苏&#xff0c;中国作为世界经济增长的压舱石&#xff0c;以5.2%的GDP增长成为全球经济发展的稳定力量。 国务院印发《数字中国建设整体布局规划》&#xff0c;从政府到央国企&#xff0c;从行业领头羊到…

【Git】上传本地文件到Git(以Windows环境为例)

Git 的下载参考&#xff1a;Git 安装及配置 一、Git 上传的整体流程 1、工作区 > 本地仓库 将本地文件上传到Git&#xff0c;需要先上传到本地仓库&#xff0c;然后再上传到远程仓库。要上传文件到本地仓库&#xff0c;不是直接拷贝进去的&#xff0c;而是需要通过命令一步…

LLM应用开发与落地:chroma的近似搜索问题

背景 最近开始测试一个游戏客户的RAG模块&#xff0c;发现一个向量数据库中大家容易忽略的一个点&#xff1a;近邻搜索算法。一开始我们选择的是chroma作为向量数据库&#xff0c;因为chroma的用户接口和设计非常简单&#xff0c;而我偏向于简单。创建collection时设置的距离计…

stable-video-diffusion 图生视频模型diffusers使用案例

T4卡16g运行: 参考:https://huggingface.co/docs/diffusers/main/en/using-diffusers/text-img2vid 案例用的google colab T4显卡运行 安装包:pip install diffusers accelerate 代码 import torch from diffusers import StableVideoDiffusionPipeline from diffusers.uti…

SPSSAU【文本分析】|我的项目

文本分析之我的项目 SPSSAU提供文本分析模块&#xff0c;其单独针对文本数据进行研究和分析使用&#xff0c;其包括词云分析、文本情感分析、文本聚类分析、社会网络关系分析、LDA主题分析、新词发现和我的词库等功能。使用SPSSAU进行文本分析时&#xff0c;涉及下述内容。分别…

【刷刷刷,爽!】leetcode198. 打家劫舍

题目如上&#xff01; 这是一道非常非常标准的初级动规题。属于走楼梯的进阶版。所以我们尝试把他变成走楼梯。 怎么变&#xff1f;或者说是怎么看成走楼梯。 答案是&#xff01;&#xff01;&#xff01;&#xff01; 看最后一个数。 往往会最有灵感。 比如示例1中[1,2,3,4]&a…

docker 服务的启动命令

Docker 服务的启动命令主要涉及 Docker Daemon 的启动和管理。Docker Daemon 是在后台运行的服务进程&#xff0c;负责管理 Docker 容器的创建、运行、停止等操作。根据你使用的操作系统&#xff0c;启动 Docker 服务的命令可能有所不同。 对于 Linux 系统 使用 systemctl (适…

【二十四】【C++】多态

多态的基本概念 多态是一种允许使用相同的接口来访问不同的底层形式&#xff08;类型&#xff09;的对象的能力。C中的多态主要通过以下两种方式实现&#xff1a; 编译时多态&#xff08;静态多态&#xff09;&#xff1a;通过函数重载和运算符重载实现。 运行时多态&#x…

基于数字双输入的超宽带Doherty功率放大器设计-从理论到ADS版图

基于数字双输入的超宽带Doherty功率放大器设计-从理论到ADS版图 参考论文: 高效连续型射频功率放大器研究 假期就要倒计时啦&#xff0c;估计是寒假假期的最后一个博客&#xff0c;希望各位龙年工作顺利&#xff0c;学业有成。 全部工程下载&#xff1a;基于数字双输入的超宽…

机器人初识 —— 定制AI

一、机器人设计难点 波士顿动力设计的机器人&#xff0c;尤其是其人形机器人Atlas和四足机器人Spot等产品&#xff0c;在技术上面临多重难点&#xff1a; 1. **动态平衡与稳定性**&#xff1a;双足或四足机器人在运动时需要维持极高的动态平衡&#xff0c;特别是在不平坦地面…

KMP算法简介以及相关例题的分析

一.KMP算法简介 KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的&#xff0c;称之为 Knuth-Morria-Pratt 算法&#xff0c;简称 KMP 算法。该算法相对于 Brute-Force&#xff08;暴力&#xff09;算法有比较大的改进&#xff0c;主要是消除了主串指针的回溯…

【Java面试】MongoDB

目录 1、mongodb是什么&#xff1f;2、mongodb特点什么是NoSQL数据库&#xff1f;NoSQL和RDBMS有什么区别&#xff1f;在哪些情况下使用和不使用NoSQL数据库&#xff1f;NoSQL数据库有哪些类型?启用备份故障恢复需要多久什么是master或primary什么是secondary或slave系列文章版…

【Vuforia+Unity】01实现单张多张图片识别产生对应数字内容

1.官网注册 Home | Engine Developer Portal 2.下载插件SDK&#xff0c;导入Unity 3.官网创建数据库上传图片&#xff0c;官网处理成数据 下载好导入Unity&#xff01; 下载好导入Unity&#xff01; 下载好导入Unity&#xff01; 下载好导入Unity&#xff01; 4.在Unity设…

unity C#中的封装、继承和多态简单易懂的经典实例

文章目录 封装 (Encapsulation)继承 (Inheritance)多态 (Polymorphism) C#中的封装、继承和多态是面向对象编程&#xff08;OOP&#xff09;的三大核心特性。下面分别对这三个概念进行深入解释&#xff0c;并通过实例来说明它们在实际开发中的应用。 封装 (Encapsulation) 实例…

11. Springboot集成Dubbo3(二)示例demo

目录 1、前言 2、注册中心 3、快速开始 3.1、添加dubbo3依赖 3.2、dubbo3-api ​编辑 3.3、dubbo3-server 3.3.1、添加依赖 3.3.2、实现IUserService 3.3.3、添加配置文件application.properties 3.3.4、修改Application启动类 3.3.5、出错解决 3.4、dubbo3-porta…

世界顶级名校计算机专业,都在用哪些书当教材?

前言 在当今信息化、数字化时代&#xff0c;计算机科学已成为全球最为热门和重要的学科之一。世界顶级名校的计算机专业&#xff0c;更是培养未来行业领袖和创新人才的重要基地。那么&#xff0c;这些名校的计算机专业究竟使用哪些教材呢&#xff1f;这些教材又具有哪些特色和…

智能化机械生产引擎:亿发制造ERP系统助帮助工厂真正把控车间管理

工厂的制造管理过程以车间管理为核心&#xff0c;而车间管理涉及到生产的下达、派工、汇报等复杂流程&#xff0c;几乎包含了生产的全过程。这种繁琐性使得车间管理变得异常困难&#xff0c;因此&#xff0c;引入一款专业的制造ERP软件成为解决难题的有效途径。 在制造业引入E…