【MIT 6.S081】2020, 实验记录(8),Lab: locks

目录

    • Task 1:Memory allocator (moderate)</font>
    • Task 2:Buffer cache (hard)</font>

Task 1:Memory allocator (moderate)

这个任务就是练习将一把大锁拆分为多个小锁,同时可以更加深入地理解 memory allocator 运行的原理。

task 的内容是:原来的 memory allocator(kernel/kalloc.c)在分配内存和释放内存时只有一把大锁(也就是原来代码中的 kmem.lock),一把大锁带来的问题就是锁竞争的问题严重。为了减少锁的竞争,现在需要将这把大锁拆成多个小锁,每个 CPU 都有一把锁和一个 freelist,当需要分配内存时,优先从本 CPU 自己所对应的 freelist 中分配,当自己的 freelist 为空时,才会去其他 CPU 的 freelist 中寻找空闲的 page。

所以我们需要做的就是,为每个 CPU 设置一个 lock,并将原来的 freelist 拆分成多个,并分给所有 CPU 来用,从而提高并发度。

这里我采用的思路是,假如一共有 NCPU 个 CPU,那按照内存页号平分给这些 CPU。比如有 15 个内存页,那 0 ~ 4 就分给第一个 CPU 的 freelist,5 ~ 9 就分给第二个 CPU 的 freelist,10 ~ 14 就分给第三个 CPU 的 freelist。这样,根据一个物理地址,我们就知道这是应该放在哪个 freelist 中。

代码实现过程(所有修改均在 kernel/kalloc.c 中):

首先需要为每个 CPU 分配一个 lock 和 freelist,所以将之前的结构体 kmem 改为了一个数组:

kmems
这样 i 号 CPU 就对应 kmems[i]

然后我们在 kinit() 中初始化各个 lock:

kinit
为了平分所有内存页,我们应该知道每个 CPU 能分到多少个内存页,也就是总内存页的数量除以 CPU 的数量,用一个变量 freelist_size 来表示:

freelist_size
然后在 freerange() 函数中遍历所有内存 page 时计算出 freelist_size

freerange
有了 freelist_size,那我们就可以根据一个物理地址计算出这个内存属于哪个 freelist 了,这个转换的逻辑封装为函数:

// 计算物理地址 pa 应该由哪个 kmem 的 freelist 来管
int kmem_number(void* pa) {return ( (uint64)pa - (uint64)end ) / PGSIZE / freelist_size;
}

修改 kfree() 函数的实现,在归还某个内存 page 时,需要先计算出这个 page 属于哪个 freelist,然后再将其加入到那个 freelist 中:

kfree 实现

接下来修改 kalloc() 的实现,在这里我们分配时,优先从当前 CPU 自己的 freelist 中寻找一个空闲 page,当自己没有空闲 page 时,需要再从其他人手中找一个出来,我们将这个寻找存在空闲 page 的 freelist 的逻辑封装为 find_freelist() 函数,它寻找含有空闲 page 的 freelist 所在的 kmem:

struct Kmem*
find_freelist()
{int cpu_id = cpuid();struct Kmem* kmem = kmems + cpu_id;// 先检查自己的 freelist 是否能够分配acquire(&kmem->lock);if (kmem->freelist) {return kmem;}release(&kmem->lock);// 如果自己的 freelist 空了的话,就从通过**遍历**来从其他人那里找for (int i = 0; i < NCPU; i++) {if (i == cpu_id) {continue;}kmem = kmems + i;acquire(&kmem->lock);if (kmem->freelist) {return kmem;}release(&kmem->lock);}return 0;
}

如果没找到就返回 0。

注意 find_freelist() 在实现时有个大坑!因为它在遍历各个 CPU 所对应的 freelist 时需要加锁,这种依次加锁很可能产生死锁的问题,为了规避死锁,我们应该按一定的顺序来依次加锁解锁,不可以产生交叉(比如一个进程先加了 A 再准备加 B,另一个进程有可能先加了 B 再准备加 A),在这里,我们决定在遍历时,无论自己的 cpu id 是多少,都从 0 号 kmem 开始遍历,而不是从自己的 cpu 号开始进行环形遍历。

有了这个函数,kalloc() 就好实现了:

kalloc

Task 2:Buffer cache (hard)

这个题目依然也是需要将一把大锁拆分成小锁从而提高并发度的任务。

bcache 用来缓存文件系统的 block,一个 bcache 由多个 buf 组成,每个 buf 可以存放一个 block,原有实现将这些 buf 串联成一个 linked list,并利用这个 linked list 来使用 LRU 算法淘汰出无用的 buf cache。

task 的内容是,原有的 bcache 在分配 block cache 时会使用一把大锁 bcache.lock,无论是寻找 cache、分配新 cache 等都是加这一把锁,从而导致 bcache 部分具有严重的锁竞争。在之前的实现中,所有的 block buf 被串联成 linked list,现在我们需要将其改成 hash table 的形式,这个 hash table 由多个 buckets 组成,每个 bucket 有一个 buf 指针的 linked list 并对应一个 lock,bucket 记录了 block 号哈希值正好映射到这个 bucket 号的所有 buf 的指针,图解如下:

buf 数组图解
buf 数组声明在 bcache 中,同时有一个 hash table 使用拉链法来记录了 bucketno -> bufs 的映射。所以,对于一个 block,对其 block 号进行取模得到 bucket 号,再从 hash table 的这个 bucket 中存储的 linked list 寻找出一个 buf 来做 block 的缓存。

代码实现如下:

首先将 bcache 结构体中的 head 删掉,只保留 lock 和 buf 数组:

bcache

之后初始化一个 hash table,也就是 buckets 数组,bucket 数量选择质数 13:

hash table

在初始化函数 binit() 中,实现对 bcache 和 hash table 中的锁和相关指针的初始化:

binit

然后我们实现一个辅助函数 replace_buffer() 用来在 buffer 中填充我们新的 block 缓存的相关信息:

replace_buffer

在实现一个辅助函数 bucket_add() 用来向一个 bucket 加入一个 buffer,这里的实现是将其加到链表的第一个元素上(也就是 head 的 next):

bucket_add

然后就是实现 bget() 函数,也就是传入一个 block 信息,让我们找到一个存放这个 block 的 buf cache,这里分两种情况:

  • case 1:已经在 cache 中,所以我们需要找到 block 所在的 bucket,并从中找到存放这个 block 缓存的 buf
  • case 2:cache 中没有这个 block 的缓存,需要我们从现有的 cache 中根据 LRU 策略淘汰出不用的 buf 作为新 block 的缓存

这个关键函数的代码实现如下,已经做了详细的注释:

// 在一个 buffer 中填入缓存块的信息
void
replace_buffer(struct buf* buffer, uint dev, uint blockno, uint tick) {buffer->dev = dev;buffer->blockno = blockno;buffer->tick = tick;buffer->valid = 0;  // 表示数据还未写入 buffer 的 data 字段中buffer->refcnt = 1;
}void
bucket_add(struct BcacheBucket *bucket, struct buf *buffer) {buffer->next = bucket->head.next;bucket->head.next->prev = buffer;bucket->head.next = buffer;buffer->prev = &bucket->head;
}// Look through buffer cache for block on device dev.
// If not found, allocate a buffer.
// In either case, return locked buffer.
static struct buf*
bget(uint dev, uint blockno)
{struct buf *b;int bucketNo = blockno % N_BUCKETS;  // 根据 blockno 计算 hash table 的 bucket 序号struct BcacheBucket *bucket = hash_table + bucketNo;acquire(&bucket->lock);// 检查这个 block 是否存在于 cache 中for (b = bucket->head.next; b != &bucket->head; b = b->next) {  // 遍历这个 bucket 的链表// 如果没找到:if (b->dev != dev || b->blockno != blockno) {continue;}// 如果找到了:b->tick = ticks;b->refcnt++;release(&bucket->lock);acquiresleep(&b->lock);return b;}// 如果 bucket 中没有找到 cache,则需要从 kcache 中找一块未使用的 bufferacquire(&bcache.lock);struct buf* victim = 0;  // 根据 LRU 策略所决定淘汰的 bufferfor (b = bcache.buf; b < bcache.buf + NBUF; b++) {  // 遍历所有 buffer,寻找一个未使用的 buffer// 如果 buffer 不能使用:if (b->refcnt != 0) {continue;}// 如果 buffer 可以使用,则根据时间戳来决定是否将它作为 victimelse {if (victim == 0 || victim->tick > b->tick) {victim = b;}}}// 是否能够找到 victim?if (victim == 0) {panic("bget: no buffers");}// 将 victim 的 buffer 中填入数据,并将其移动到 bucket 中if (victim->tick == 0) {  // 如果 victim 还未加入到 hash table 中replace_buffer(victim, dev, blockno, ticks);bucket_add(bucket, victim);} else if ((victim->blockno % N_BUCKETS) != bucketNo) {  // 如果 victim 之前所在的 bucket 与现在需要加入的 bucket 不同的话struct BcacheBucket *old_bucket = &hash_table[victim->blockno % N_BUCKETS];acquire(&old_bucket->lock);replace_buffer(victim, dev, blockno, ticks);victim->prev->next = victim->next;victim->next->prev = victim->prev;release(&old_bucket->lock);bucket_add(bucket, victim);} else {  // 如果 victim 之前就是在现在需要加入的 bucket 的话replace_buffer(victim, dev, blockno, ticks);}// 释放掉相关的 lockrelease(&bcache.lock);release(&bucket->lock);acquiresleep(&victim->lock);return victim;
}

bget() 实现之后,剩下的几个函数就容易修改了,大致就是将原来对大锁的加解锁改成”寻找 buf 对应的 bucket 的小锁在加解锁“:

在这里插入图片描述

完成上述修改后,即可通过测试:

kcachetest 通过

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

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

相关文章

闲鱼月入过万的不密心法

大家好&#xff0c;我是周萝卜 再介绍一下我自己 【我叫】周萝卜 【坐标】江苏南京 【个人标签】互联网深度玩家/技术公众号运行者/个人成长/闲鱼知乎运营 【个人事件】 公众号累计4万粉丝&#xff0c;累计变现6位数 个人分享号&#xff1a;#公众号&#xff1a;萝卜的个…

docker小白第十二天

docker小白第十二天 docker network简介 docker不启动时默认的网络情况。 # 停止docker服务 systemctl stop docker.socket systemctl stop docker # 查看docker镜像 docker images输入查看docker镜像命令后&#xff0c;显示未连接到docker服务器 docker启动时网络情况 sy…

【ArcGIS 脚本工具】修改多个布局的同一文本元素

规划编制的战线有多长&#xff0c;估计各位规划人已经深有体会了。 以村规为例&#xff0c;不服务个两年别想跑哈。其他规划编制时间更长&#xff0c;以至于图纸右下角的年月也是改了又改。 本着分散改不如统一改&#xff0c;手动改不如自动改的原则&#xff0c;小编制作了这个…

谷歌Gemma大模型本地部署以及线上访问流程

1.谷歌开发出强大的 AI 模型 Gemma&#xff0c;该模型可以在笔记本电脑和台式机上运行&#xff0c;这真是太棒了&#xff01;开源的 Gemma 模型将使研究人员和开发人员能够更轻松地访问和利用其功能&#xff0c;从而为人工智能领域带来更多创新。【【【【本地安装】】】 下载安…

软件设计师16--段页式存储

软件设计师16--段页式存储 考点1&#xff1a;页式存储存储管理 - 页式存储组织存储管理 - 页面置换算法例题&#xff1a; 考点2&#xff1a;段式存储存储管理 - 段式存储组织例题&#xff1a; 考点1&#xff1a;页式存储 存储管理 - 页式存储组织 页式存储&#xff1a;将程序…

第二十三天-数据分析入门实战

目录 1.常用的数据获取网站 2.分析电信用户流失率 字段说明 1.读取数据 2.数据分析describe 3.数据验证 4.分析目的 1.整体流失情况&#xff1a;人数、比例、流失率 2.性别&#xff1a;人数、比例、流失率 3.老人&#xff1a;人数、比例、流失率 4.是否有配偶&#x…

【PHP+代码审计】PHP基础——浮点型和布尔型

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

PTA L2-012 关于堆的判断

将一系列给定数字顺序插入一个初始为空的小顶堆H[]。随后判断一系列相关命题是否为真。命题分下列几种&#xff1a; x is the root&#xff1a;x是根结点&#xff1b;x and y are siblings&#xff1a;x和y是兄弟结点&#xff1b;x is the parent of y&#xff1a;x是y的父结点…

【你也能从零基础学会网站开发】Web建站之javascript入门篇 简单认识一下正则表达式

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;程序猿、设计师、技术分享 &#x1f40b; 希望大家多多支持, 我们一起学习和进步&#xff01; &#x1f3c5; 欢迎评论 ❤️点赞&#x1f4ac;评论 &#x1f4c2;收藏 &#x1f4c2;加关注 为什么使用正则…

1.MongoDB的特点与应用场景

什么是 MongoDB &#xff1f; MongoDB 是基于 C 开发的 NOSQL 开源文档数据库 &#xff0c;是最像关系型数据库的 nosql&#xff0c;功能也是最丰富的 nosql&#xff0c;它具有所以的可伸缩性&#xff0c;灵活性&#xff0c;高性能&#xff0c;高扩展性的优势。 大致有如下特…

基于51单片机的用电量检测系统[proteus仿真]

基于51单片机的用电量检测系统[proteus仿真] 用电检测系统这个题目算是课程设计和毕业设计中常见的题目了&#xff0c;本期是一个基于51单片机的用电量检测系统 需要的源文件和程序的小伙伴可以关注公众号【阿目分享嵌入式】&#xff0c;赞赏任意文章 2&#xffe5;&#xff…

8分SCI | 揭示随机森林的解释奥秘:探讨LIME技术如何提高模型的可解释性与可信度!

一、引言 Local Interpretable Model-agnostic Explanations (LIME) 技术作为一种局部可解释性方法&#xff0c;能够解释机器学习模型的预测结果&#xff0c;并提供针对单个样本的解释。通过生成局部线性模型来近似原始模型的预测&#xff0c;LIME技术可以帮助用户理解模型在特…