6.1810: Operating System Engineering 2023 <Lab7 lock: Parallelism/locking>

一、本节任务

二、要点 

2.1 文件系统(file system) 

xv6 文件系统软件层次如下:

通过路径树我们可以找到相应的文件:

fd(文件描述符)是进程用来标识其打开的文件的手段,每个进程有自己的文件打开表,并且系统会维护一个全局文件打开表(系统中所有打开的文件都保存在这个全局文件打开表中)。

进程通过 fd 将文件作为一系列字节来访问,每一个 fd 都有一个光标(cursor)来指向文件的当前访问位置:

read() 和 write() 系统调用都会使光标前进: 

也有一些特殊的文件,比如 pipe() 创建出来的缓冲区,其本质是一个文件,不同的进程使用 fd 对缓冲区文件进行读和写,从而实现进程间的通信: 

inode(index node)索引节点用来记录一个文件的详细信息,包括:

  • 记录文件的大小以及数据在磁盘上的存储位置;
  • 记录文件的硬链接数量(和打开 fd 数量);

inode 可以是指磁盘上的 inode,也可能指内存上的 inode 副本(读写 inode 时会先将其读入内存中)。磁盘上的 inode 被打包到一个称为 inode 块的相邻磁盘区域中,每个 inode 大小都一样,所以通过一个索引 i 能很容易找到相应的索引节点,这个索引 i 也被称为 i-number 或 inode number。

address1~12 为直接索引,指向的为数据块,而 indrect 为间接索引,指向的为 indirect block,indirect block 里面的地址才指向数据块,这种方式可以增加文件的最大大小,并且减少 dinode 结构体的大小。 

数据存储的位置? 

磁盘访问是十分低效的,我们可以将 RAM 的一部分作为磁盘的 cache,每次访问磁盘会将连续的部分内容复制到 RAM 上,当再次访问的时候就不需要去访问磁盘,而是访问复制到 RAM 上的内容即可,其替换策略为 LRU(least recently used)。buffer cache 有两个作用:

  1. 同步对磁盘块的访问,以确保只有一个块的副本在内存中,并且一次只有一个内核线程使用该副本;
  2. 缓存常用的块,以便不需要从慢速磁盘上重新读取它们。

相关代码在 bio.c 中,其中主要的接口为 bread() 和 bwrite():

  • bread():获取一个能在内存中读写的磁盘块的副本,使用 buf(buf.h)结构体来管理这个副本;
  • bwrite():后者将修改后的缓冲区写入磁盘上的对应块;
  • brelse():内核线程在使用完后这块缓冲区后需要使用 brelse() 来释放它;

磁盘布局: 

  

xv6 文件系统在磁盘上的结构如下图。xv6将磁盘划分为几个部分,文件系统不使用 block0(它用来保存引导扇区)。block1 称为超级块(superblock),它包含有关文件系统的元数据(如块中的文件系统大小、数据块的数量、inode 的数量和 log 中的块数)。从 block2 开始的块保持 log,log 之后是 inodes,每个块有多个 inode。在这些之后,位图(bitmap)块跟踪数据库的使用情况。其余的块是数据块,每个块要么在 bitmap 中标记为空闲,要么保存文件或目录的内容。超级块由一个称为 mkfs 的单独程序填充,该程序构建一个初始文件系统。 

Loging layer 可以帮助我们实现崩溃恢复(crash recovery)。出现这个问题的原因是,许多文件系统操作涉及对磁盘的多次写入操作,在某个写入操作后的崩溃可能会使磁盘上的文件系统处于不一致的状态。根据磁盘写入的顺序,崩溃可能会使 inode 留下对标记为 free 的块的引用(os 可能会将该 free 块分配给其他文件,这样就导致有两个文件共享同一个块,可能会导致严重的安全问题),也可能会留下已分配但未被引用的块。

xv6 通过一种简单的日志记录形式解决了文件系统操作过程中的崩溃问题。xv6 系统调用不会直接写磁盘上的文件系统数据结构,相反,它在磁盘上的日志中写入了它希望进行的所有磁盘写操作的描述,一旦系统调用在日志中记录了它所有的写入操作,它就会将一个特殊的提交记录写入磁盘,这表明该日志中包含了一个完整的操作。此时,系统调用才开始执行写磁盘上的文件系统数据结构操作。在这些写入操作完成之后,系统调用再将磁盘上的此次日志删除。

如果系统崩溃并重新启动,则文件系统代码将从崩溃中恢复。如果日志被标记为包含完整操作,则恢复代码将写入复制到磁盘文件系统中所属的位置。如果日志未标记为包含完整的操作,则恢复代码将忽略该日志。最后恢复代码再将日志删除。 所以,logging layer 能保证写操作要么执行完,要么不执行,避免出现数据不一致的情况。

系统调用一开始只会写 buffer cache, 而不是磁盘。

当系统调用完成后 —— commit:

  1. 将所有的更新的块写到磁盘的 log 上(在 log 上保存了更新块的副本);
  2. 将磁盘上的 log 设置为 committed;
  3. 然后再将更新的块写到要写的位置;
  4. 删除 log 上的记录;

当崩溃发生,reboot 后:

如果 log 被设置为 committed,将更新的块从磁盘的 log 上写到要写的位置。

三、Lab lock: Parallelism/locking

在多核机器上,锁的争用使得其性能变差,在本实验中,我们会重新设计部分代码以增加并行性。提高并行性通常需要改变数据结构和锁定策略,以减少争用。

3.1 Memory allocator (moderate)

这部分需要为每个 CPU 维护一个 freelist(kernel/kalloc.c),每个 freelist 都有自己的锁。不同 CPU 上的分配和释放可以并行运行,因为每个 CPU 将对不同的 freelist 进行操作。当一个 CPU 上的 freelist 为空,而另外一个 CPU 的 freelist 还有空闲页面时,没有空闲页面的 CPU 必须 “窃取” 另一个 CPU 的 freelist 的一部分。窃取可能会引入锁争用,但争用频率要比单个 freelist 要小很多。

首先将 kmem 变成一个数组,大小为 CPU 的个数: 

struct {struct spinlock lock;struct run *freelist;
} kmem[NCPU];

在 kinit() 函数中初始化每个结构体的锁:

void
kinit()
{for(int i = 0; i < NCPU; i++){char name[9] = {0};snprintf(name, 8, "kmem-%d", i);initlock(&kmem[i].lock, "kmem");}freerange(end, (void*)PHYSTOP);
}

使用 kfree() 来释放的页面,要注意释放的页面放到当前 CPU 的 freelist 中:

void
kfree(void *pa)
{struct run *r;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();int id = cpuid();acquire(&kmem[id].lock);r->next = kmem[id].freelist;kmem[id].freelist = r;release(&kmem[id].lock);pop_off();
}

使用 kalloc() 分配页面时,需要注意的情况就是当前 CPU 的 freelist 里面没有空页面可用,这时候需要去其他 CPU 的 freelist 偷一个页面过来:

static struct run *steal_free_block(int id)
{struct run *p = 0;for(int i = 0; i < NCPU; i++){if(i != id){acquire(&kmem[i].lock);p = kmem[i].freelist;if(p){kmem[i].freelist = p->next;p->next = 0;release(&kmem[i].lock);break;}release(&kmem[i].lock);}}return p;
}void *
kalloc(void)
{struct run *r;push_off();int id = cpuid();acquire(&kmem[id].lock);r = kmem[id].freelist;if(r){kmem[id].freelist = r->next;}release(&kmem[id].lock);if(!r){r = steal_free_block(id);}pop_off();if(r)memset((char*)r, 5, PGSIZE); // fill with junkreturn (void*)r;
}

3.2 Buffer cache (hard)

在 kenel/bio.c 中的 bcache 结构体中的 lock 的竞争十分激烈,每次访问 bcache 结构体都需要请求这个锁,所以需要我们使用一些策略来减少竞争。

首先是 bcache 结构体需要使用 hash 桶 buf_table 来装入不同 blockno 的块,每个桶对应一个 lock: 

#define NBUCKET 13struct {struct spinlock lock[NBUCKET];struct buf buf[NBUF];// Linked list of all buffers, through prev/next.// Sorted by how recently the buffer was used.// head.next is most recent, head.prev is least.//struct buf head;struct buf buf_table[NBUCKET];
} bcache;

初始化每个桶和每个桶对应的锁: 

void
binit(void)
{struct buf *b;for(int i = 0; i < NBUCKET; i++){char name[9] = {0};snprintf(name, 8, "bcache-%d", i);initlock(&bcache.lock[i], name);bcache.buf_table[i].prev = &bcache.buf_table[i];bcache.buf_table[i].next = &bcache.buf_table[i];}int i;for(i = 0,b = bcache.buf; i < NBUF && b < bcache.buf+NBUF; i++,b++){int index = i % NBUCKET;b->next = bcache.buf_table[index].next;b->prev = &bcache.buf_table[index];initsleeplock(&b->lock, "buffer");bcache.buf_table[index].next->prev = b;bcache.buf_table[index].next = b;}
}

bget 函数会先根据 blockno 找到对应的 hash 桶,若块已被缓存到对应的桶中,则直接返回,若不存在则循环遍历各个桶找到空闲的块(b->refcnt == 0),若该块在其他桶中,则需要把该块放到当前 blockno 对应的桶中:

static struct buf*
bget(uint dev, uint blockno)
{struct buf *b;int index = blockno % NBUCKET;acquire(&bcache.lock[index]);// Is the block already cached?for(b = bcache.buf_table[index].next; b != &bcache.buf_table[index]; b = b->next){if(b->dev == dev && b->blockno == blockno){b->refcnt++;release(&bcache.lock[index]);acquiresleep(&b->lock);return b;}}release(&bcache.lock[index]);for(int i = index; ; i = (i+1) % NBUCKET){acquire(&bcache.lock[i]);for(b = bcache.buf_table[i].next; b != &bcache.buf_table[i]; b = b->next){if(b->refcnt == 0) {// change bucketif(i != index){b->next->prev = b->prev;b->prev->next = b->next;release(&bcache.lock[i]);acquire(&bcache.lock[index]);b->next = bcache.buf_table[index].next;b->prev = &bcache.buf_table[index];bcache.buf_table[index].next->prev = b;bcache.buf_table[index].next = b;}b->dev = dev;b->blockno = blockno;b->valid = 0;b->refcnt = 1;release(&bcache.lock[index]);acquiresleep(&b->lock);return b;}}release(&bcache.lock[i]);}panic("bget: no buffers");
}

下面几个函数根据 block 中的 blockno 来得到对应的桶: 

void
brelse(struct buf *b)
{if(!holdingsleep(&b->lock))panic("brelse");releasesleep(&b->lock);int index = b->blockno % NBUCKET;acquire(&bcache.lock[index]);b->refcnt--;release(&bcache.lock[index]);
}void
bpin(struct buf *b) {int index = b->blockno % NBUCKET;acquire(&bcache.lock[index]);b->refcnt++;release(&bcache.lock[index]);
}void
bunpin(struct buf *b) {int index = b->blockno % NBUCKET;acquire(&bcache.lock[index]);b->refcnt--;release(&bcache.lock[index]);
}

最后 kalloctest 和 bcachetest 还有 usertest 全部通过。

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

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

相关文章

程序员有哪些接s单的渠道?

这题我会&#xff01;程序员接单的渠道那可太多了&#xff0c;想要接到合适的单子&#xff0c;筛选一个合适的平台很重要。如果你也在寻找一个合适的接单渠道&#xff0c;可以参考以下这些方向。 首先&#xff0c;程序员要对接单有一个基本的概念&#xff1a;接单渠道可以先粗…

Elasticsearch_8.11.4_kibana_8.11.4_metricbeat_8.11.4安装及本地部署_ELK日志部署

文章目录 Elasticsearch_8.11.4_kibana_8.11.4_metricbeat_8.11.4安装及本地部署_ELK日志部署分布式引擎Elasticsearch_8.11.4安装及本地部署系统环境要求1 Windows 安装 Elasticsearch下载完成后进行解压,进入 bin 目录,找到elasticsearch.bat脚本文件执行一键启动.启动都选允…

51单片机HC-SR04超声波测距lcd1602显示(程序+ad硬件设计+文档说明)

本帖主控使用STC89C52单片机&#xff0c;超声波测距采用HC-SR04模块&#xff0c;包含ad硬件设计和文档。 测距原理 超声波测距是通过不断检测超声波发射后遇到障碍物所反射的回波&#xff0c;从而测出发射和接收回波的时间差t,然后求出距SCt/2,式中的C为超声波波速。由于超声…

环保时代下的品牌全球化之路:绿色供应链的战略洞察

随着全球化的深入和消费者对可持续发展和环保的日益关注&#xff0c;品牌出海不仅需要考虑市场扩张和竞争力提升&#xff0c;还需要认真思考如何在全球供应链中构建绿色可持续的供应链体系。本文Nox聚星将和大家探讨品牌出海的绿色供应链建设&#xff0c;分析可持续发展和环保要…

机器学习扩散模型简介

一、说明 扩散模型的迅速崛起是过去几年机器学习领域最大的发展之一。在这本易于理解的指南中了解您需要了解的有关扩散模型的所有信息。 扩散模型是生成模型&#xff0c;在过去几年中越来越受欢迎&#xff0c;这是有充分理由的。仅在 2020 年代发布的几篇开创性论文就向世界…

MySQL系列之数据导入导出

前言 大数据与云计算作为当今时代&#xff0c;数据要素发展的“动力引擎”&#xff0c;已经走进了社会生活的方方方面。而背后承载的云服务或数据服务的高效运转&#xff0c;起了决定作用。 作为数据存储的重要工具&#xff0c;数据库的品类和特性也日新月异。从树型、网络型…

MySQL/Oracle 的 字符串拼接

目录 MySQL、Oracle 的 字符串拼接1、MySQL 的字符串拼接1.1 CONCAT(str1,str2,...) : 可以拼接多个字符串1.2 CONCAT_WS(separator,str1,str2,...) : 指定分隔符拼接多个字符串1.3 GROUP_CONCAT(expr) : 聚合函数&#xff0c;用于将多行的值连接成一个字符串。 2、Oracle 的字…

C#灵活控制多线程的状态(开始暂停继续取消)

ManualResetEvent类 ManualResetEvent是一个同步基元&#xff0c;用于在多线程环境中协调线程的执行。它提供了两种状态&#xff1a;终止状态和非终止状态。 在终止状态下&#xff0c;ManualResetEvent允许线程继续执行。而在非终止状态下&#xff0c;ManualResetEvent会阻塞线…

Python画球面投影图

天文学研究中&#xff0c;有时候需要画的并不是传统的XYZ坐标系&#xff0c;而是需要画一个形如这样子的球面投影图&#xff1a; 下面讲一下这种图怎么画 1. 首先要安装healpy包 pip install healpy 2. 然后导入包 如果之前安装过healpy&#xff0c;有的会提示不存在healpy…

【蓝桥杯日记】第一篇——如何搭建系统环境

目录 前言 环境相关文件 学生机环境-Web应用开发环境&#xff08;第十五届大赛&#xff09; 学生机环境-Java编程环境&#xff08;第十五届大赛&#xff09; 学生机环境-C/C编程环境&#xff08;第十五届大赛&#xff09; 学生机环境-Python编程环境 &#xff08;第十五届…

20240112让移远mini-PCIE接口的4G模块EC20在Firefly的AIO-3399J开发板的Android11下跑通【DTS部分】

20240112让移远mini-PCIE接口的4G模块EC20在Firefly的AIO-3399J开发板的Android11下跑通【DTS部分】 2024/1/12 16:20 https://blog.csdn.net/u010164190/article/details/79096345 [Android6.0][RK3399] PCIe 接口 4G模块 EC20 调试记录 https://blog.csdn.net/hnjztyx/artic…

【Linux】线程池实现

&#x1f4d7;线程池实现&#xff08;单例模式&#xff09; 1️⃣线程池概念2️⃣线程池代码样例3️⃣部分问题与细节&#x1f538;类成员函数参数列表中隐含的this指针&#x1f538;单例模式&#x1f538;一个失误导致的bug 4️⃣调用线程池完成任务 1️⃣线程池概念 线程池是…

【Linux驱动】设备树中指定中断 | 驱动中获得中断 | 按键中断实验

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《Linux驱动》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3c0;在设备树中指定中断&#x1f3c0;代码中获得中断&#x1f3c0;按键中断⚽驱动…

闪存剩下内容

1&#xff1a;通过Arduino IDE向闪存文件系统上传文件 1. 下载 Arduino-ESP8266闪存文件插件程序 2&#xff1a;使用闪存文件系统建立功能更加丰富的网络服务器 1&#xff1a;在网页中加载闪存文件系统中的图片、CSS和JavaScript index.html&#xff1a;ESP8266开发板建立的网…

SpringBoot+SSM项目实战 苍穹外卖(12) Apache POI

继续上一节的内容&#xff0c;本节是苍穹外卖后端开发的最后一节&#xff0c;本节学习Apache POI&#xff0c;完成工作台、数据导出功能。 目录 工作台Apache POI入门案例 导出运营数据Excel报表 工作台 工作台是系统运营的数据看板&#xff0c;并提供快捷操作入口&#xff0c…

初识OpenCV

首先你得保证你的虚拟机Ubuntu能上网 可看 http://t.csdnimg.cn/bZs6c 打开终端输入 sudo apt-get install libopencv-dev 回车 输入密码 回车 遇到Y/N 回车 OpenCV在线文档 opencv 文档链接 点zip可以下载&#xff0c;点前面的直接在线浏览&#xff0c;但是很慢 https…

单元测试:Testing leads to failure, and failure leads to understanding

单元测试的概念可能多数读者都有接触过。作为开发人员&#xff0c;我们编写一个个测试用例&#xff0c;测试框架发现这些测试用例&#xff0c;将它们组装成测试 suite 并运行&#xff0c;收集测试报告&#xff0c;并且提供测试基础设施&#xff08;断言、mock、setup 和 teardo…

JAVAEE初阶 文件IO(一)

这里写目录标题 一. 计算机中存储数据的设备1.1 CPU1.2 内存1.3 硬盘1.4 三种存储的区别 二.文件系统2.1 相对路径2.2 绝对路径2.3 .和..的含义2.4 例子2.5 everything工具 三.文件3.1 文本文件3.2 二进制文件 四. JAVA对于文件的API4.1 getParent getName getPath getAbsolute…

Jest单元测试:玩转代码的小捉迷藏!

Jest Jest 是什么&#xff1f; Jest 是一个流行的 JavaScript 测试框架&#xff0c;专注于简化和改进代码的测试流程。它由 Facebook 开发并维护&#xff0c;具有以下特点&#xff1a; 1、易用性&#xff1a;Jest 提供了一个简单而强大的测试框架&#xff0c;使得编写和运行测…

uniapp h5 发行后 微信第二次打开网址 页面白屏

发行后把网址给客户&#xff0c;第一次可以正常登录打开&#xff0c;第二次打开白屏 原因&#xff1a;第一次打开时没有token&#xff0c;所以跳转登录页&#xff0c;可以正常访问 第二次打开时有token&#xff0c;但是网址根目录没有配置默认页面&#xff0c;所以白屏 解决…