【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.rhkb.cn/news/276086.html

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

相关文章

R语言深度学习-3-过拟合问题(无监督正则化/Lasso回归/岭回归/集成和平均算法)

本教程参考《RDeepLearningEssential》 我们从上一个教程看到&#xff0c;我们看到在我们训练迭代或者训练更大神经网络的时候&#xff0c;往往会产生过拟合&#xff0c;而且越来越严重&#xff0c;它可能会把训练它的数据拟合的很好&#xff0c;但是未必能把新数据做的很好。…

HSE化工应急安全生产管理平台:衢州某巨大型化工企业的成功应用

在化工行业中&#xff0c;安全生产一直是至关重要的议题。为了提高生产安全性、降低成本并提升企业形象&#xff0c;衢州某巨大型化工企业引入了HSE化工应急安全生产管理平台&#xff0c;取得了显著的改善和获益。 该平台的核心功能包括风险管理和应急预案制定。通过对化工生产…

KubeSphere 社区双周报|2024.02.29-03.14

KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书、新增的讲师证书以及两周内提交过 commit 的贡献者&#xff0c;并对近期重要的 PR 进行解析&#xff0c;同时还包含了线上/线下活动和布道推广等一系列社区动态。 本次双周报涵盖时间为&#xff1a;2024.02.29-03.14…

3D全景:为各行业提供更真实的交互体验

近年来&#xff0c;随着科技的不断发展&#xff0c;3D全景技术逐渐融入到了我们的日常生活中来。3D全景技术的应用落地&#xff0c;为广大用户提供了全新的视觉体验&#xff0c;让人们能够更加真实、直观地感受各行业的场景。 3D全景的优势就在于真实感和互动性&#xff0c;可以…

<JavaEE> 了解网络层协议 -- IP协议

目录 初识IP协议 什么是IP协议&#xff1f; IP协议中的基础概念 IP协议格式 图示 4bit版本号&#xff08;version&#xff09; 4bit头部长度&#xff08;headerlength&#xff09; 8bit服务类型&#xff08;TypeOfService&#xff09; 16bit总长度&#xff08;total l…

jenkins+maven+gitlab自动化构建打包、部署

Jenkins自动化部署实现原理 环境准备 1、jenkins已经安装好 docker安装jenkins 2、gitlab已经安装好 docker安装gitlab 一、Jenkins系统配置 1.Global Tool Configuration 任务构建所用到的编译环境等配置&#xff0c;配置参考&#xff1a; jdk配置&#xff08;jenkins自带…

多维时序 | MATLAB实现BiTCN-selfAttention自注意力机制结合双向时间卷积神经网络多变量时间序列预测

多维时序 | MATLAB实现BiTCN-selfAttention自注意力机制结合双向时间卷积神经网络多变量时间序列预测 目录 多维时序 | MATLAB实现BiTCN-selfAttention自注意力机制结合双向时间卷积神经网络多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.M…

SpringCloud(22)之Sentinel实战应用

一、Sentinel核心库 sentinel主页&#xff1a;主页 alibaba/Sentinel Wiki GitHub 1.1 Sentinel介绍 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&…

C# wpf 使用GDI实现截屏

wpf截屏系列 第一章 使用GDI实现截屏&#xff08;本章&#xff09; 第二章 使用GDI实现截屏 第三章 使用DockPanel制作截屏框 第四章 实现截屏框热键截屏 第五章 实现截屏框实时截屏 第六章 使用ffmpeg命令行实现录屏 文章目录 wpf截屏系列前言一、导入gdi32方法一、NuGet获取…

88. 合并两个有序数组 (Swift版本)

题目 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&#xff1a;最终&#xff0c;合并…

Python数据分析-5

1.时间序列 2.pandas重采样 重采样&#xff1a;指的是将时间序列从一个频率转化为另一个频率进行处理的过程&#xff0c;将高频率数据转化为低频率数据为降采样&#xff0c;低频率转 化为高频率为升采样。 统计出911数据中不同月份电话次数的变化情况&#xff1a…

PlantUML Integration 编写短信服务类图

PlantUML Integration 写一个类图&#xff0c;主要功能为 1、编写一个serviceSms短信服务类&#xff1b; 2、需要用到短信的地方统一调用基建层的服务即可&#xff1b; 3、可以随意切换、增加短信厂商&#xff0c;不需要更改场景代码&#xff0c;只需要更改application.yml 里面…

边缘计算与物联网的核心 —— 低功耗芯片

一、低功耗芯片 在边缘计算与物联网&#xff08;IoT&#xff09;中&#xff0c;低功耗芯片扮演了至关重要的角色&#xff0c;主要体现在以下几个方面&#xff1a; 延长设备寿命&#xff1a;物联网设备通常需要部署在难以更换电池或不方便进行频繁维护的环境中&#xff0c;比如…

学习使用postman软件上传文件发起api接口请求

学习使用postman软件上传文件发起api接口请求 设置headers头信息设置body 设置headers头信息 如图设置&#xff1a; KEY&#xff1a;Content-Type VALUE&#xff1a;multipart/form-data 设置body 设置需要上传的key对应的类型为File&#xff0c;上传类型 设置需要上传的文件…

物联网技术助力智慧城市转型升级:智能、高效、可持续

目录 一、物联网技术概述及其在智慧城市中的应用 二、物联网技术助力智慧城市转型升级的路径 1、提升城市基础设施智能化水平 2、推动公共服务智能化升级 3、促进城市治理现代化 三、物联网技术助力智慧城市转型升级的成效与展望 1、成效显著 2、展望未来 四、物联网技…

import gdal 报错

1.下载gdal https://www.lfd.uci.edu/~gohlke/pythonlibs/#gdal 2.安装正确版本 &#xff08;1&#xff09;查看python版本 python -v我的版本Python 3.7.9 建议下载 GDAL-3.4.2-cp37-cp37m-win_amd64.whl &#xff08;2&#xff09;放到Scripts文件夹下 执行 pip install GD…

CVPR2024 | 大核卷积新高度101x101,美团提出PeLK

https://arxiv.org/pdf/2403.07589.pdf 本文概述 最近&#xff0c;一些大核卷积网络以吸引人的性能和效率进行了反击。然而&#xff0c;考虑到卷积的平方复杂度&#xff0c;扩大内核会带来大量的参数&#xff0c;而大量的参数会引发严重的优化问题。由于这些问题&#xff0c;当…

网络编程套接字(4)——Java套接字(TCP协议)

目录 一、Java流套接字通信模型 二、TCP流套接字编程 1、ServerSocket ServerSocket构造方法&#xff1a; ServerSocket方法: 2、Socket Socket构造方法&#xff1a; Socket方法&#xff1a; 三、代码示例&#xff1a;回显服务器 1、服务器代码 代码解析 2、客户端…

机械女生,双非本985硕,目前学了C 基础知识,转嵌入式还是java更好?

作为单片机项目开发的卖课佬&#xff0c;个人建议&#xff0c;先转嵌入式单片机开发方向&#xff0c;哈哈。 java我也学过&#xff0c;还学过oracle、mysql数据库&#xff0c;只是当时没做笔记&#xff0c;找不好充分的装逼证据了。 从实习通过业余时间&#xff0c;学到快正式毕…

AI 大模型赋能手机影像,小米14 Ultra 让真实有层次

2月22日&#xff0c;小米龙年第一场重磅发布会&#xff0c;正式发布专业影像旗舰小米14 Ultra。 此前小米发布的两代 Ultra&#xff0c;在不同维度&#xff0c;引领了移动影像行业的走向。最新的小米14 Ultra 在定义的时候&#xff0c;我们反复在思考&#xff1a;怎么才能把移动…