【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 中找一块未使用的 buffer
  acquire(&bcache.lock);
  struct buf* victim = 0;  // 根据 LRU 策略所决定淘汰的 buffer
  for (b = bcache.buf; b < bcache.buf + NBUF; b++) {  // 遍历所有 buffer,寻找一个未使用的 buffer
    // 如果 buffer 不能使用:
    if (b->refcnt != 0) {
      continue;
    }
    // 如果 buffer 可以使用,则根据时间戳来决定是否将它作为 victim
    else {
      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);
  }

  // 释放掉相关的 lock
  release(&bcache.lock);
  release(&bucket->lock);
  acquiresleep(&victim->lock);

  return victim;
}

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

在这里插入图片描述

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

kcachetest 通过

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

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

相关文章

镭雕机:如何利用激光技术实现高质量的产品标记

镭雕机是一种利用激光技术实现高质量产品标记的设备。它通过激光束在各种不同的物质表面进行精确的打标&#xff0c;可以产生永久性的标记效果&#xff0c;这些标记不仅精美&#xff0c;而且具有高度的精度和清晰度。以下是镭雕机如何利用激光技术实现高质量产品标记的详细过程…

【LeetCode】84. 柱状图中最大的矩形(困难)——代码随想录算法训练营Day60

题目链接&#xff1a;84. 柱状图中最大的矩形 题目描述 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;heights [2,1,5,…

<Senior High School Math>: inequality question

( 1 ) . o m i t (1). omit (1).omit ( 2 ) . ( a 2 − b 2 ) ( x 2 a 2 − y 2 b 2 ) ( x 2 y 2 ) − ( a 2 y 2 b 2 b 2 x 2 a 2 ) ≤ x 2 y 2 − 2 x y ( x − y ) 2 (2). (a^2-b^2)(\frac{x^2}{a^2} - \frac{y^2}{b^2})(x^2y^2)-(\frac{a^2y^2}{b^2}\frac{b^2x^2}{a^…

指针的函数传参的详细讲解(超详细)

如果对指针基础知识已经有可以直接跳到 函数的指针传参与解引用&#xff0c;哪里不明白可以评论&#xff0c;随时解答。 目录 所以就有了一句话&#xff1a;指针就是地址&#xff0c;地址就是指针 对于指针在C语言中&#xff0c;指针类型就是数据类型&#xff0c;是给编译器…

第四百零二回

文章目录 知识回顾示例代码经验总结 我们在上一章回中介绍了MethodChannel的使用方法&#xff0c;本章回中将介绍EventChannel的使用方法.闲话休提&#xff0c;让我们一起Talk Flutter吧。 知识回顾 我们在前面章回中介绍了通道的概念和作用&#xff0c;并且提到了通道有不同的…

【C++ 学习】程序内存分布

文章目录 1. C 内存分布的引入 1. C 内存分布的引入 ① 栈又叫堆栈&#xff1a;非静态局部变量/函数参数/返回值等等&#xff0c;栈是向下增长的。 ② 内存映射段&#xff1a;是高效的I/O映射方式&#xff0c;用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存…

Day16 面向对象进阶——接Day15

Day16 面向对象进阶——接Day15 文章目录 Day16 面向对象进阶——接Day15一、抽象类及抽象方法二、接口三、多态四、对象转型五、内部类 一、抽象类及抽象方法 //抽象类 public abstract class 类名{//抽象方法public abstract void method(); }1、抽象方法交给非抽象的子类去…

Selenium 学习(0.20)——软件测试之单元测试

我又&#xff08;浪完&#xff09;回来了…… 很久没有学习了&#xff0c;今天忙完终于想起来学习了。没有学习的这段时间&#xff0c;主要是请了两个事假&#xff08;5工作日和10工作日&#xff09;放了个年假&#xff08;13天&#xff09;&#xff0c;然后就到现在了。 看了下…

Python 界面逻辑分离示例

本示例使用的发卡设备&#xff1a;https://item.taobao.com/item.htm?id615391857885&spma1z10.5-c.w4002-21818769070.11.6cc85700Robi3x 一、Python 安装PyQt5&#xff0c;运行 Qt Designer 新建窗体文件&#xff0c;在窗体中拖放控件 完成界面设计&#xff0c;保存为…

slowfast network

SlowFast Networks for Video Recognition_slowfast networks for video recognition 复现过程-CSDN博客https://blog.csdn.net/karen17/article/details/95936983?ops_request_misc%257B%2522request%255Fid%2522%253A%2522171041325416800184121120%2522%252C%2522scm%2522%…

AJAX 04 回调函数地狱和 Promise 链式调用、async 和 await、事件循环

AJAX 学习 AJAX 04 进阶01 同步代码和异步代码02 回调函数地狱和 Promise 链式调用(1) 回调函数地狱(2) Promise 链式调用(3) Promise 链式应用 03 async 和 await(1) async 和 await 使用(2) async函数和await捕获错误 04 事件循环-EventLoop(1) 事件循环(2) 事件循环练习(3) …

八数码(C++)

原题在这里P1379 八数码难题 思路&#xff1a; 本题的思路很有意思&#xff0c;首先我们知道0是可以和上下左右交换位置的&#xff08;前提是不出边界&#xff09; 不难看出我们可以把这个二维数组给转化为一个相对应的字符串来表示当前的状态&#xff0c;每进行一次&#xff…

Siamese Network(孪生神经网络)详解

Siamese和Chinese有点像。Siam是古时候泰国的称呼&#xff0c;中文译作暹罗。Siamese也就是“暹罗”人或“泰国”人。Siamese在英语中是“孪生”、“连体”的意思&#xff0c;这是为什么呢&#xff1f;十九世纪泰国出生了一对连体婴儿&#xff0c;当时的医学技术无法使两人分离…

Python二级备考

考试大纲如下&#xff1a; 基本要求 考试内容 考试方式 比较希望能直接刷题&#xff0c;因为不懂的比较多可能会看视频。 基础操作刷题&#xff1a; 知乎大头计算机1-13题 import jieba txtinput() lsjieba.lcut(txt) print("{:.1f}".format(len(txt)/len(ls)…

代码随想录训练营Day23:● 669. 修剪二叉搜索树 ● 108.将有序数组转换为二叉搜索树 ● 538.把二叉搜索树转换为累加树 ● 总结篇

669. 修剪二叉搜索树 题目链接 https://leetcode.cn/problems/trim-a-binary-search-tree/description/ 题目描述 思路 public TreeNode trimBST(TreeNode root, int low, int high) {if(rootnull) return null;//当前节点的值比区间的最小值小&#xff0c;说明需要删除&am…

goctl-swagger 生成json接口文件

参考&#xff1a; GitHub - dyntrait/goctl-swagger: 通过 api 文件生成 swagger 文档 GitHub - Bluettipower/goctl-swagger 一:编译 执行go install 前一般需要设置环境&#xff0c;不然资源经常会下载不下载 go env -w GOPROXYhttps://goproxy.cn,direct 执行完 go in…

Linux操作系统——常见指令(1)

今天分享一下Linux操作系统常见一些指令。今天介绍 ls pwd cd touch mkdir rmdir rm这几个指令。 ls指令 语法 ls 选项 目录或者文件 功能 对于目录&#xff0c;该命令列出该目录下的所有子目录和文件&#xff0c;对于文件&#xff0c;将列出文件名以及其他信息。 我们常用…

JavaScript基础(超详细)

目录 1.JavaScript概述 2.JavaScript的组成及其基本结构 1.JavaScript的组成 1.ECMAScript ECMAScript是一种由Ecma国际[前向为欧洲计算机制造商协会(European Computer Manufacturers Associaiton)]通过ECMA-262标准化的脚本程序设计语言。其主要描述了JavaScript的语法…

视频素材哪里去找?分享五个高清素材网站

从事短视频以来&#xff0c;关于视频素材哪里去找&#xff1f;好多人都是无从下手&#xff0c;今天我把使用多年的视频素材网站&#xff0c;分享给大家。 无论你短视频你想在抖音还是自媒体或者小红书还是搞笑摄影还是视频素材剪辑&#xff0c;你想要的通通都有&#xff01; 蛙…

交换机/路由器的存储介质-华为

交换机/路由器的存储介质-华为 本文主要介绍网络设备的存储介质组成。 SDRAM&#xff08;同步动态随机存取内存&#xff09; 系统运行内存&#xff0c;相当于电脑的内存&#xff1b; NVRAM&#xff08;Non-Volatile Random Access Memory&#xff0c;非易失性随机访问存储器…
最新文章