MIT 6s081 lab8:locks

lab8: locks

作业地址:Lab: locks (mit.edu)

Memory allocator (moderate)

kalloc和kfree的多次调用,多次获取kmem锁,避免race-condition出现,但降低了内存分配的效率,本实验的目的:修改内存分配的程序,提升内存分配的效率。

一个可行的方式是:每个CPU的内存分配和释放都是独立的,只需要给每个CPU分配一把锁,这样,每个CPU的内存分配释放都是独立的,进而提升内存分配的效率。当某个cpu 申请内存但没有空闲内存时,能够从其他CPU的空闲内存中“窃取内存”。

1、首先为每个CPU分配一个全局的内存空闲列表和锁,并完成初始化

struct {struct spinlock lock;struct run *freelist;
} kmem[NCPU];
void
kinit()
{char kmem_name[10];for(int i = 0; i < NCPU; i++) {snprintf(kmem_name, 10, "kmem%d", i);printf("init lock: %s\n", kmem_name);initlock(&kmem[i].lock, kmem_name);}freerange(end, (void*)PHYSTOP);
}

2、修改kfree,获取当前cpu id,对当前cpu的内存空闲链表释放一页

void
kfree(void *pa)
{struct run *r;if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)panic("kfree");push_off(); // 关中断int current_cpu = cpuid();pop_off(); // 开中断// Fill with junk to catch dangling refs.memset(pa, 1, PGSIZE);r = (struct run*)pa;acquire(&kmem[current_cpu].lock);r->next = kmem[current_cpu].freelist;kmem[current_cpu].freelist = r;release(&kmem[current_cpu].lock);
}

3、修改kalloc,获取当前cpu id, 如果当前cpu有空闲内存,则直接分配,如果没有,则尝试从其他cpu的空闲内存中“窃取”。注意及时获取和释放对应cpu的锁

void *
kalloc(void)
{struct run *r;push_off(); // 关中断int current_cpu = cpuid();pop_off(); // 开中断acquire(&kmem[current_cpu].lock);r = kmem[current_cpu].freelist;if(r) // 有空间,就直接给kmem[current_cpu].freelist = r->next;else{ //当前CPU的内存空闲列表没空间了,从其他cpu那里偷,可能会触发竞争for(int i = 0; i < NCPU; i++){if(i == current_cpu) continue; // 不包括自己acquire(&kmem[i].lock); // 获取锁r = kmem[i].freelist; if(r) // 别的cpu有空间{kmem[i].freelist = r->next; //修改别的cpu的空闲列表release(&kmem[i].lock); //释放锁break;}release(&kmem[i].lock); //释放锁}}release(&kmem[current_cpu].lock);if(r)memset((char*)r, 5, PGSIZE); // fill with junkreturn (void*)r;
}

Buffer cache (hard)

多进程同时使用文件系统,原本的bcache.lock会发生严重的锁竞争,bcache.lock用于保护磁盘区块缓存,xc6原本的设计中,多进程不能同时申请、释放磁盘缓存。

原本的设计是使用双向链表存储区块buf,每次尝试begt时,遍历链表,如果目标块已经在缓冲中,则将引用计数加1,并返回该缓存;若不存在,则选择一个最近最久未使用的(LRU),且引用计数为0的Buf块进行替换,并返回。

优化思路:建立一个blockno到buf的hash table,为每个桶单独加锁(降低锁的粒度),当两个进程同时访问的块哈希到同一个桶时,才会发生竞争,当桶中的空闲Buf不足时,从其他桶中获取Buf,并采用时间戳(全局ticks)的优化方式替换原本的双向链表。

1、为struct buf添加字段:uint prev_use_time,struct buf * next

struct buf {int valid;   // has data been read from disk?int disk;    // does disk "own" buf?uint dev;uint blockno;struct sleeplock lock;uint refcnt;uchar data[BSIZE];uint prev_use_time; // 记录上一次使用的时间,时间戳struct buf * next; // 记录下一个节点
};

2、修改全局bcache,分配prime个桶,并为每个桶分配一个锁,同时采用单链表的方式维护哈希表

#define prime     13
#define NBUCKET   prime 
#define GET_KEY(dev, blockno) ((blockno) % prime)struct {struct buf hash_table[NBUCKET]; // 申请prime个哈希表(通过单链表维护),总共的BUF数量为NBUFstruct spinlock lock_bucket[NBUCKET]; // 为每个桶分配一个锁struct buf buf[NBUF];} bcache;

3、初始化,初始化所有的锁,并将所有的Buf放入第一个桶,便于后续其他桶中没有buf时,进行“窃取”。

void
binit(void)
{// 初始化桶的锁char bucket_lock_name[10];for(int i = 0; i < NBUCKET; i++) {snprintf(bucket_lock_name, 10, "bcache%d", i);initlock(&bcache.lock_bucket[i], bucket_lock_name);bcache.hash_table[i].next = 0;}// 把所有的buf放入第一个桶中,类似上一个实验把所有的空闲内存放在第一个cpu中struct buf * b;for(int i = 0; i < NBUF; i++) {b = &bcache.buf[i];b->prev_use_time = 0;b->refcnt = 0;initsleeplock(&b->lock, "buffer");b->next = bcache.hash_table[0].next;bcache.hash_table[0].next = b;}}

4、 设计bget函数。(这是本实验的核心部分)

首先获取blockno哈希得到的桶的下标key,在这个桶中查找,如果命中了,就直接返回。

没有命中:

1、首先在当前的桶中查找引用计数为0的最近最久没有使用的Buf,如果有,则直接修改对应的buf,返回。

2、从0开始遍历其他桶,在每个桶中查找引用计数为0的最近最久没有使用的Buf,如果有,则先将这个buf从原本的桶中删去,再将这个buf添加到桶key,并修改buf的内容,返回。

这种设计方式,其实并不是真正意义上的LRU,因为并没有遍历全部的buf去寻找引用计数次数为0的最近最久没有使用的BUF。

static struct buf*
bget(uint dev, uint blockno)
{struct buf *b;// Is the block already cached?int key = GET_KEY(dev, blockno); // 找到桶的下标acquire(&bcache.lock_bucket[key]); //获取这个桶的锁// 遍历key对应的桶查询for(b = bcache.hash_table[key].next; b; b = b->next) {if(b->dev == dev && b->blockno == blockno) { // 命中b->refcnt++;      release(&bcache.lock_bucket[key]); acquiresleep(&b->lock);return b;}}// 没有命中cache,先从当前的key对应的桶中寻找int mn = ticks, key_replace = -1;struct buf * b_prev_replace = 0;for(b = &bcache.hash_table[key]; b->next; b = b->next) {if(b->next->prev_use_time <= mn && b->next->refcnt == 0) {b_prev_replace = b;mn = b->next->prev_use_time;}}if(b_prev_replace) { // 在这个桶里找到了b = b_prev_replace->next;b->dev = dev;b->blockno = blockno;b->valid = 0;b->refcnt = 1;release(&bcache.lock_bucket[key]);acquiresleep(&b->lock);return b;}for(int i = 0; i < NBUCKET; i++) {if(i == key) continue;acquire(&bcache.lock_bucket[i]);mn = ticks;for(b = &bcache.hash_table[i]; b->next; b = b->next) {if(b->next->prev_use_time <= mn && b->next->refcnt == 0) {mn = b->next->prev_use_time;b_prev_replace = b;key_replace = i;}if(b_prev_replace && b_prev_replace->next && key_replace >= 0) { // 对bucket[i]中的buf寻找最近最少使用的buf,然后进行修改,这样其实就避免了环路的锁,但并不是真正意义上的LRUb = b_prev_replace->next;// 从旧的桶中删去b_prev_replace->next = b->next;// 在新的桶中添加b->next = bcache.hash_table[key].next;bcache.hash_table[key].next = b;b->dev = dev;b->blockno = blockno;b->valid = 0;b->refcnt = 1;release(&bcache.lock_bucket[i]);release(&bcache.lock_bucket[key]);acquiresleep(&b->lock);// printf("new buf :%d\n", blockno);return b;}}release(&bcache.lock_bucket[i]);}printf("no buffers: %d\n", blockno);release(&bcache.lock_bucket[key]);panic("bget: no buffers");
}

4、 修改brelse函数,修改b的引用计数

void
brelse(struct buf *b)
{if(!holdingsleep(&b->lock))panic("brelse");releasesleep(&b->lock);int key = GET_KEY(b->dev, b->blockno);acquire(&bcache.lock_bucket[key]);b->refcnt--;if (b->refcnt == 0) {// no one is waiting for it.// 更新时间戳b->prev_use_time = ticks;}release(&bcache.lock_bucket[key]);}

5、修改bpin和bunpin

void
bpin(struct buf *b) {int key = GET_KEY(b->dev, b->blockno);acquire(&bcache.lock_bucket[key]);b->refcnt++;release(&bcache.lock_bucket[key]);
}void
bunpin(struct buf *b) {int key = GET_KEY(b->dev, b->blockno);acquire(&bcache.lock_bucket[key]);b->refcnt--;release(&bcache.lock_bucket[key]);
}

不足之处:其实本设计有可能出现环路等待的死锁问题:当两个进程在运行前都没有被缓存。

环路等待的死锁问题参考如下:

[mit6.s081] 笔记 Lab8: Locks | 锁优化 | Miigon’s blog

假设块号 b1 的哈希值是 2,块号 b2 的哈希值是 5
并且两个块在运行前都没有被缓存
----------------------------------------
CPU1                  CPU2
----------------------------------------
bget(dev, b1)         bget(dev,b2)|                     |V                     V
获取桶 2 的锁           获取桶 5 的锁|                     |V                     V
缓存不存在,遍历所有桶    缓存不存在,遍历所有桶|                     |V                     V......                遍历到桶 2|                尝试获取桶 2 的锁|                     |V                     V遍历到桶 5          桶 2 的锁由 CPU1 持有,等待释放
尝试获取桶 5 的锁|V
桶 5 的锁由 CPU2 持有,等待释放!此时 CPU1 等待 CPU2,而 CPU2 在等待 CPU1,陷入死锁!

解决方法可以参考上述这篇blog,比较复杂,后续有时间再解决。

实验对应的测试样例并没有出现这种隐晦的死锁情况,还是通过了。

== Test running kalloctest == 
$ make qemu-gdb
(68.1s) 
== Test   kalloctest: test1 == kalloctest: test1: OK 
== Test   kalloctest: test2 == kalloctest: test2: OK 
== Test kalloctest: sbrkmuch == 
$ make qemu-gdb
kalloctest: sbrkmuch: OK (7.7s) 
== Test running bcachetest == 
$ make qemu-gdb
(6.4s) 
== Test   bcachetest: test0 == bcachetest: test0: OK 
== Test   bcachetest: test1 == bcachetest: test1: OK 
== Test usertests == 
$ make qemu-gdb
usertests: OK (102.8s) (Old xv6.out.usertests failure log removed)
== Test time == 
time: OK 
Score: 70/70

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

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

相关文章

python 读写kafka

1. 安装pykafka pip install pykafka2. 生产者 from pykafka import KafkaClientdef get_kafka_producer(hosts, topics):client KafkaClient(hostshosts)print(client.topics)topic client.topics[topics]producer topic.get_producer()return producer 测试 hosts 19…

python基础语法看一篇就够了,全网最全python语法笔记汇总

前言 Python 是一种代表简单思想的语言&#xff0c;其语法相对简单&#xff0c;很容易上手。不过&#xff0c;如果就此小视 Python 语法的精妙和深邃&#xff0c;那就大错特错了。 如能在实战中融会贯通、灵活使用&#xff0c;必将使代码更为精炼、高效&#xff0c;同时也会极…

提前避坑Anzo Capital总结浮动差价的3个缺点

在交易中很多投资者倾向于选择浮动差价模式&#xff0c;这种模式的便利性就不言而喻了&#xff0c;但Anzo Capital需要提醒各位投资者&#xff0c;一定要知道浮动差价的3个缺点&#xff0c;在交易中提前避坑&#xff0c;下面Anzo Capital就和各位投资者一起总结浮动差价的这3…

leetcode 24两两交换链表中的节点

题目 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 思想 对于操作链表节点的时候&#xff0c;首先需要就是创建一个虚拟的…

前端动画特效分享(附在线预览)

分享几款不错的动画特效源码 其中有CSS动画、canvas动画、js小游戏等等 下面我会给出特效样式图或演示效果图 但你也可以点击在线预览查看源码的最终展示效果及下载源码资源 canvas爱心代码动画 爱心代码动画特效 由里向外不断的产生的小爱心形成一个巨大的爱心动画 以下图片…

前端工程记录:用RecordRTC实现对<video>标签的录像功能

项目需求&#xff1a;后端给一个mp4视频的链接&#xff0c;在前端播放&#xff0c;同时支持用户的录制视频操作。 一、技术选择 1. 毫无关系的getUserMedia 官方介绍文档&#xff1a;MediaDevices.getUserMedia() - Web API 接口参考 | MDN 在网上搜索“前端如何录像”&…

Maxwell数据同步(增量)

1. Maxwell简介 1.1 Maxwell概述 Maxwell 是由美国Zendesk公司开源&#xff0c;用Java编写的MySQL变更数据抓取软件。它会实时监控Mysql数据库的数据变更操作&#xff08;包括insert、update、delete&#xff09;&#xff0c;并将变更数据以 JSON 格式发送给 Kafka、Kinesi等流…

全链路压力测试:现代软件工程中的重要性

全链路压力测试不仅可以确保系统在高负载下的性能和稳定性&#xff0c;还能帮助企业进行有效的风险管理和性能优化。在快速发展的互联网时代&#xff0c;全链路压力测试已成为确保软件产品质量的关键步骤。 1、测试环境搭建 测试应在与生产环境尽可能相似的环境中进行&#xff…

windows使用redis-安装和配置

windows使用redis 安装和配置 下载安装方式一-使用压缩包安装解压到指定的文件Redis安装为Windows服务安装成功 方式二-MSI安装包安装完成 Redis配置远程访问1.修改配置文件redis.windows.conf2.修改完redis配置文件&#xff0c;必须重启redis 下载 先下载Redis for windows 的…

Java lambda表达式如何自定义一个toList Collector

匿名类&#xff1a; package l8;import java.util.*; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; import java.util.s…

PyTorch中的FX图

一.FX 图介绍 FX 图是 PyTorch 中的一个主要数据结构&#xff0c;用于在 FX 中间表示&#xff08;Intermediate Representation&#xff0c;IR&#xff09;中表示程序。FX 图由一系列节点组成&#xff0c;每个节点代表调用站点&#xff08;比如运算符、方法和模块&#xff09;。…

高级编程JavaScript。Notifications消息通知

- Notifications Notifications API 用于向用户显示通知。无论从哪个角度看&#xff0c;这里的通知都很类似 alert()对话框&#xff1a; 都使用 JavaScript API 触发页面外部的浏览器行为&#xff0c;而且都允许页面处理用户与对话框或通知弹层的交 互。不过&#xff0c;通知…

React 18 中的并发性

并发性是我们在 React 18 发布后获得的重大成就之一。由于此功能是完全选择加入的&#xff0c;并且 React 18 向后兼容以前的版本&#xff0c;因此您甚至可能没有注意到新功能。那么并发性是什么、它是如何工作的以及它如何改进您的应用程序的呢&#xff1f; 什么是并发 并发…

360度评估的应用场景和评估内容

360度评估是一种多元化的评估工具&#xff0c;它从多个角度获取对个体绩效、能力和行为的全面反馈。这种评估方法不仅涵盖了传统的上级评价&#xff0c;还包括同级、下级、自我评价以及客户或外部利益相关者的反馈。 一、360度评估的应用场景 员工绩效评估&#xff1a;360度评…

【PostgreSQL内核学习(二十二)—— 执行器(ExecutePlan)】

执行器&#xff08;InitPlan&#xff09; 概述ExecutePlan 函数ExecProcNode 函数 总结 声明&#xff1a;本文的部分内容参考了他人的文章。在编写过程中&#xff0c;我们尊重他人的知识产权和学术成果&#xff0c;力求遵循合理使用原则&#xff0c;并在适用的情况下注明引用来…

超详细!4小时开发一个SpringBoot+vue前后端分离博客项目!!

超详细&#xff01;4小时开发一个SpringBootvue前后端分离博客项目&#xff01;&#xff01; 前后端分离项目 文章总体分为2大部分&#xff0c;Java后端接口和vue前端页面&#xff0c;比较长&#xff0c;因为不想分开发布&#xff0c;真正想你4小时学会&#xff0c;哈哈。 先…

【自学笔记】01Java基础-07面向对象基础-04接口与内部类详解

记录学习Java基础中有关接口类和内部类的知识。 1 接口 interface 关键字用于定义接口类&#xff0c;接口类是一系列方法的声明&#xff0c;一般只有方法的特征没有方法的实现&#xff0c;因此可以被不同的类接入实现&#xff0c;而这些实现可以具有不同的行为&#xff08;功…

Graham扫描凸包算法

凸包&#xff08;Convex Hull&#xff09;是包含给定点集合的最小凸多边形。凸包算法有多种实现方法&#xff0c;其中包括基于递增极角排序、Graham扫描、Jarvis步进法等。下面&#xff0c;我将提供一个简单的凸包算法实现&#xff0c;基于Graham扫描算法。 Graham扫描算法是一…

多级缓存架构(一)项目初始化

文章目录 一、项目克隆二、数据库准备三、项目工程准备 一、项目克隆 克隆此项目到本地 https://github.com/Xiamu-ssr/MultiCache 来到start目录下&#xff0c;分别有以下文件夹 docker&#xff1a;docker相关文件item-service&#xff1a;springboot项目 二、数据库准备 …

Ncast盈可视高清智能录播系统busiFacade RCE漏洞(CVE-2024-0305)

产品介绍 Ncast盈可视高清智能录播系统是一套新进的音视频录制和播放系统&#xff0c;旨在提供高质量&#xff0c;高清定制的录播功能。 漏洞描述 广州盈可视电子科技有限公司的高清智能录播系统存在信息泄露漏洞(CVE-2024-0305)&#xff0c;攻击者可通过该漏洞&#xff0c;…