xv6源码分析 017

news2024/11/25 18:50:35

xv6源码分析 017

在buffer cache上面的就是logging层了,这一层主要的工作是维持每一个文件系统写入的操作的原子性。什么是原子性?通俗地来讲,原子性可以这样理解,如果一组操作(或者一个操作)在执行的时候不会被其他的操作打断并且,这一组操作只有全部操作完成之后才算作真正的完成;如果其中的一个操作执行出现错误或者异常了,那么前面执行的操作就会回滚(roll back),整一组操作就好像没有执行一样。在文件系统这个场景来看就是,对文件系统不产生任何的影响。

复用一下前面的图,
在这里插入图片描述

ok现在我们来看看代码。

在这里先简单说明一下,xv6中的logging并不支持并发,一次只能够提交一个事务(transaction)


logging中的结构体

首先是logheader

struct logheader {
    int n;
    int block[LOGSIZE];
};
  • LOGSIZE:表示一个文件所能够持有的最大的块数
  • block:用来跟踪文件在内存的日志块

然后是核心数据结构log

struct log {
  struct spinlock lock;
  int start;
  int size;
  int outstanding; // how many FS sys calls are executing.
  int committing;  // in commit(), please wait.
  int dev;
  struct logheader lh;
};
struct log log;
  • struct spinlock lock:锁,不说了
  • int start:文件的第一个日志块
  • int size:这个文件对应的日志块的总数
  • int outstanding:记录在本次事务中正在进行的了文件系统调用的数量
  • int commiting:表示当前的日志是否正在提交,如果是,那么其他要提交的日志需要等待
  • int dev:设备的id(外部存储设备)
  • struct logheader lh:跟踪一个文件所有日志块的引导块(就是上面介绍的)

然后是对应的操作接口

先介绍两个局部函数static void recover_from_log(void)static void commit()

static void recover_from_log(void)

这个函数是在提交事务的时候将事务从磁盘的日志存储区写到对应的磁盘块中(从磁盘到磁盘),这样的操作看似有些多余,但是却能够保证文件系统的完整性。

现在假设没有logging,我们的文件系统调用是直接写到磁盘上对应的块上的,这样的io我们称之为随机io,不仅效率低下,而且如果在操作执行到一半的时候突然发生意外,系统崩溃了或者断电了,现在文件系统是不完整,因为操作系统并不知道故障发生之前的操作执行到哪里,也不知道是否全部的操作都已经完成了,更不知道那些操作需要被撤销。

但是有了logging之后,系统在重启之后就能够进行recover,也就是下面这个函数,基于上面的情况,不同的是我们现在有了logging,所以文件系统会先检查日志储存区,将上面的日志重做(redo)一遍,如果发现有的日志没有被提交,文件系统就知道这个操作并没有完成应该撤销(即使操作完成了,但是没有提交一律视作未完成)。这样就能够保证文件系统的完整性了。

而且写日志是顺序io,能够大幅度地减少磁盘写操作的开销。

static void
recover_from_log(void)
{
  read_head();
  install_trans(); // if committed, copy from log to disk
  log.lh.n = 0;
  write_head(); // clear the log
}

static void commit()

看名字就知道,这个函数是用来提交事务的。注释很详细。

static void
commit()
{
  if (log.lh.n > 0) {
    write_log();     // Write modified blocks from cache to log
    write_head();    // Write header to disk -- the real commit
    install_trans(); // Now install writes to home locations
    log.lh.n = 0;
    write_head();    // Erase the transaction from the log
  }
}

从代码中不难看出,在提交的时候是直接将系统对buffer cache的修改一股脑地写到磁盘(日志存储区)上,然后调用write_head()真正地提交,写一个尾部标志到日志存储区的最后,这样一段事务的范围就能被确认,如果没有这个尾部,那么在崩溃恢复的时候,文件系统就把它当作未提交。

然后就是安装(install)了,最后将对应的日志清除,,因为已经持久化到磁盘上对应的扇区中了,所以也就没什么用了。

void initlog(int dev, struct superblock *sb)

  • int dev:设备id
  • struct superblock *sb:超级块,现在可以理解为文件系统层面的引导块,记录了所有文件的i节点的信息。
void
initlog(int dev, struct superblock *sb)
{
  if (sizeof(struct logheader) >= BSIZE)
    panic("initlog: too big logheader");

  initlock(&log.lock, "log");
  log.start = sb->logstart;
  log.size = sb->nlog;
  log.dev = dev;
  recover_from_log();
}

static void read_head(void)

从磁盘中读取头部日志块到内存中

log.devlog.start表明了读取一个文件的头部日志块

下面的(struct logheader *)(buf->data)表示读取了头部日志块中的数据并将其格式化成对应的struct logheader

最后是将logheader中的数据加载到log中,

static void
read_head(void)
{
  struct buf *buf = bread(log.dev, log.start);
  struct logheader *lh = (struct logheader *) (buf->data);
  int i;
  log.lh.n = lh->n;
  for (i = 0; i < log.lh.n; i++) {
    log.lh.block[i] = lh->block[i];
  }
  brelse(buf);
}

static void wrtie_head(void)

过程跟上面的基本相同,只是这个函数是将头部日志块中的数据写到磁盘中。

static void
write_head(void)
{
  struct buf *buf = bread(log.dev, log.start);
  struct logheader *hb = (struct logheader *) (buf->data);
  int i;
  hb->n = log.lh.n;
  for (i = 0; i < log.lh.n; i++) {
    hb->block[i] = log.lh.block[i];
  }
  bwrite(buf);
  brelse(buf);
}

begin_op(void)

这个函数的作用是将我们每一次文件系统调用的操作都进行日志记录。

函数的主体是一个大循环,里面是一个分支判断,我们分别来看看这些分支的作用:

第一个分支log.commit:如果当前的文件的日志正在提交(文件的事务正在提交),那么这个操作将进行睡眠,直到事务提交成功,因为xv6中没有数据库系统那样复杂的并发控制机制,所以只支持一个文件一次事务提交。

第二个分支log.lh.n + (log.outstanding+1)*MAXOPBLOCKS > LOGSIZE:如果当前文件的日志的大小已经超过了限制,也需要等待,直到事务提交刷新日志存储区

第三个分支:顺利执行outstanding递增。

void
begin_op(void)
{
  acquire(&log.lock);
  while(1){
    if(log.committing){
      // 1.
      sleep(&log, &log.lock);
    } else if(log.lh.n + (log.outstanding+1)*MAXOPBLOCKS > LOGSIZE){
      // 2.
      // this op might exhaust log space; wait for commit.
      sleep(&log, &log.lock);
    } else {
      // 3.
      log.outstanding += 1;
      release(&log.lock);
      break;
    }
  }
}

void end_op(void)

当一个文件系统调用结束时调用这个函数,(但是并不代表提交)。

这个函数其实也很简单,可能大家在下面两个断点处可能会有疑惑。我们考虑这样一个情景:假设一个文件系统有大量的写入操作,这时候,情况一:日志正在提交;情况二:日志已经写满了。由于文件系统有大量的写操作所以在这两个情况之下,可能会有大量的线程在log.lock(begin_op())上等(当然这在平时我们用的系统中是不可能出现这么抽象的情况的),

所以,每一个写操作在执行完成之后就会有两个选择:1.提交事务;2.唤醒一个线程继续执行写操作。由于在begin_op中,我们只有出了大循环之后才会使outstanding++,因此那些正在等待的操作并不会阻碍事务的提交,当outstanding执行完之后,我们也再唤醒一次。

注意,由于begin_op()的实现,这种唤醒并不会带来副作用(虚假唤醒),因为有一个大循环。

void
end_op(void)
{
  int do_commit = 0;

  acquire(&log.lock);
  log.outstanding -= 1;
  if(log.committing)
    panic("log.committing");
  if(log.outstanding == 0){
    do_commit = 1;
    log.committing = 1;
  } else {
    // 1.
    // begin_op() may be waiting for log space,
    // and decrementing log.outstanding has decreased
    // the amount of reserved space.
    wakeup(&log);
  }
  release(&log.lock);

  if(do_commit){
    // call commit w/o holding locks, since not allowed
    // to sleep with locks.
    commit();
    acquire(&log.lock);
    log.committing = 0;
    // 2.
    wakeup(&log);
    release(&log.lock);
  }
}

static void write_log(void)

这个函数的作用很简单:将buffer cache中对文件block所作的修改写到日志中(在commit中调用)

static void
write_log(void)
{
  int tail;

  for (tail = 0; tail < log.lh.n; tail++) {
    struct buf *to = bread(log.dev, log.start+tail+1); // log block
    struct buf *from = bread(log.dev, log.lh.block[tail]); // cache block
    memmove(to->data, from->data, BSIZE);
    bwrite(to);  // write the log
    brelse(from);
    brelse(to);
  }
}

void log_write(struct buf *b)

这里是将对日志的修改转移到了buffer cache中,因为在事务提交的时候,会将所用的buffer cache的内容都写回磁盘,所以我们只需要修改buffer cache上的内容。

如果文件的空间不足或者空间缩小了,函数就会动态地增或减少相应的块。

void
log_write(struct buf *b)
{
  int i;

  if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1)
    panic("too big a transaction");
  if (log.outstanding < 1)
    panic("log_write outside of trans");

  acquire(&log.lock);
  for (i = 0; i < log.lh.n; i++) {
    if (log.lh.block[i] == b->blockno)   // log absorbtion
      break;
  }
  log.lh.block[i] = b->blockno;
  if (i == log.lh.n) {  // Add new block to log?
    bpin(b);
    log.lh.n++;
  }
  release(&log.lock);
}

OK,logging层就讲解完了。

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

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

相关文章

H5视频付费点播打赏影视系统程序全开源运营版

这是一款视频打赏源码&#xff0c;勿做非法用途&#xff0c;由用户亲测功能完善&#xff0c;源码仅用于学习使用&#xff0c;分享链接是用户云盘&#xff0c;具有时效性&#xff0c;感兴趣的可以去学习。 thinkphp开发&#xff0c;前后端分离设计&#xff0c;支持游客登陆、VIP…

代码随想录算法训练营第四十二天| 01背包问题理论基础,416. 分割等和子集

理论基础&#xff1a; 带你学透0-1背包问题&#xff01;| 关于背包问题&#xff0c;你不清楚的地方&#xff0c;这里都讲了&#xff01;| 动态规划经典问题 | 数据结构与算法_哔哩哔哩_bilibili很多同学对背包问题的理解程度都处于一种黑盒的状态&#xff0c;及时这道题目在力…

【java】接口

什么是接口 接口当中存在的是对方法的定义&#xff0c;而不是对方法的具体实现。 为什么不实现这个方法呢&#xff1f; 继承的本质是代码的复用。当一个父类会经常被继承&#xff0c;并且子类都要自己实现方法时&#xff0c;父类中的方法就会显得累赘&#xff0c;并且占用了…

【JavaEE精炼宝库】计算机是如何工作的

目录 前言&#xff1a; 一、冯诺依曼体系 二、CPU基本知识 2.1 硬盘|内存|CPU关系&#xff1a; 2.2 指令&#xff1a; 2.3 CPU是如何执行指令的&#xff08;重点&#xff09;&#xff1a; 2.4 小结&#xff1a; 三、编程语言 3.1 程序&#xff1a; 3.2 编程语言发展&a…

IndexedDB解密:打开Web应用的数据存储之门

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 IndexedDB解密&#xff1a;打开Web应用的数据存储之门 前言IndexedDB简介数据库操作数据检索与索引异步操作与事件处理 前言 在Web的世界里&#xff0c;数据就像是一群旅行者&#xff0c;它们来自各个…

PHPStudy Apache或者MySQL启动以后自动停止

问题 phpstudy小皮面板中的Apache或MySQL启动以后自动停止 正在启动——已启动——已停止 总结&#xff1a;最主要的原因&#xff1a;端口冲突 端口冲突了&#xff0c;已经有其他程序占用了80、3306端口。 也就是说你的电脑上已经有了一个Apache、MySQL并且正在运行。 解决方案…

「PolarDB-X入门到精通」第六讲:MySQL生态兼容

在上一阶段的课程中&#xff0c;已经和大家一起了解了PolarDB分布式数据库的产品架构&#xff0c;并且带领大家一起分别通过PXD、源码编译完成了PolarDB-X 的安装部署。在接下来的课程中&#xff0c;我们将继续带领大家一起学习PolarDB-X的产品特性。 在本期的课程中&#xff0…

vitis 2020.1 Up date XSA文件后,编译不通过

原来是可以编译通过的&#xff0c;升级XSA文件后&#xff0c;出现各种问题&#xff0c;pmufw没法编译通过 xpfw_config.h:14:10: fatal error: xparameters.h: No such file or directory Vitis 2020.2 - fatal error: xparameters.h: No such file or directory (xilinx.com)…

物流EDI:GEFCO EDI 需求分析

GEFCO专注于汽车物流领域近70年&#xff0c;是欧洲整车市场的物流供应商&#xff0c;也是欧洲十大领先的运输和物流集团之一。GEFCO的业务遍及六大洲&#xff0c;业务覆盖150个国家&#xff0c;在全球拥有庞大的员工队伍&#xff0c;在全球汽车行业的挑战中茁壮成长。为汽车制造…

AR人脸美妆SDK解决方案,让妆容更加贴合个人风格

美妆行业正迎来前所未有的变革&#xff0c;为满足企业对高效、精准、创新的美妆技术需求&#xff0c;美摄科技倾力打造了一款企业级AR人脸美妆SDK解决方案&#xff0c;为企业打开美妆领域的新世界大门。 革命性的人脸美妆技术 美摄科技的AR人脸美妆SDK解决方案&#xff0c;不…

Milvus向量数据库(一)Milvus存储byte[]类型源向量数据

两种路线&#xff1a; 第一种是把byte[]转换为List< float >&#xff0c;然后存储到Milvus的floatVector中第二种是把byte[]转换为ByteBuffer&#xff0c;然后存储到Milvus的BinaryVector中 步骤&#xff1a; 我先用的是第一种&#xff0c;但是在转换float过程中&…

Python-VBA函数之旅-repr函数

目录 一、repr函数的常见应用场景 二、repr函数使用注意事项 三、如何用好repr函数&#xff1f; 1、repr函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a;https://blog.csdn.net/ygb_1024?spm1010.2135.…

mvc区域、Html.RenderAction、Html.RenderPartial、 模板、section

根据上图 Html.RenderPartial 与 Html.RenderAction 区别 RenderAction 会把对应的视图结果渲染 RenderPartial 会把html视图直接渲染 模板

十九、分布式数据库MyCat

目录 一、概述 1、MyCat是什么&#xff1f; 2、原理&#xff1a; 3、能干什么 1、读写分离 2、数据分片 3、多数据源整合 4、Mycat监控 4、安装部署 1、环境准备 2、安装 3、Mycat配置详解 1、server.xml user 标签 2、schema.xml schema标签&#xff1a; table标签&…

AI大模型探索之路-训练篇16:大语言模型预训练-微调技术之LoRA

系列篇章&#x1f4a5; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 AI大模型探索之路-训练篇4&#xff1a;大语言模型训练数据集概…

最新闲鱼小众蓝海虚拟资源,单号日入300+,三天必起店,矩阵放大月入1-2W

详情介绍 本项目售卖的虚拟资源非常小众&#xff0c;宅男的最爱&#xff0c;并且市场一片蓝海&#xff01;只需一步手机&#xff0c;随时随地操作项目&#xff0c;流量巨大&#xff0c;安装教程方法操作三天必起店&#xff0c;消息多到回不过来&#xff0c;一天轻松出个大几十单…

封装Springboot基础框架功能-03

在些模块中汇总了一些web开发常用的配置和功能。 模块源码结构 Restful API常用定义 QueryParam请求参数 Data public class QueryParam {private String key;private String value; }RestfulController实现 RestfulController.java&#xff0c;主要汇总一些常用的restful的…

启明智显分享|国产RISC-V@480MHz“邮票孔”工业级HMI核心板,高品质低成本,仅34.9元!

「Model系列」芯片是启明智显针对工业、行业以及车载产品市场推出的系列HMI芯片&#xff0c;主要应用于工业自动化、智能终端HMI、车载仪表盘、串口屏、智能中控、智能家居、充电桩显示屏、储能显示屏、工业触摸屏等领域。此系列具有高性能、低成本的特点&#xff0c;支持工业宽…

响应式编程Spring Reactor探索

一&#xff0c;介绍 响应式编程&#xff08;Reactive Programming&#xff09;&#xff0c;简单来说是一种生产者只负责生成并发出数据/事件&#xff0c;消费者来监听并负责定义如何处理数据/事件的变化传递方式的编程思想。 响应式编程借鉴了Reactor设计模式&#xff0c;我们…

一文搞懂前端跨页面通信的那些方案们

前端开发逃避不开跨页面通信这项工作&#xff0c;跨页面通信&#xff0c;就好比A页面要和B页面说话&#xff0c;可能只是说一句话&#xff0c;不需要回话&#xff0c;可能是要给一些东西&#xff0c;希望得到回复&#xff0c;并频繁进行沟通&#xff0c;接下来我们说说这些跨页…