xv6源码分析 017

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.mfbz.cn/a/600827.html

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

相关文章

【busybox记录】【shell指令】expand

目录 内容来源&#xff1a; 【GUN】【expand】指令介绍 【busybox】【expand】指令介绍 【linux】【expand】指令介绍 使用示例&#xff1a; 把制表符转化为空格 - 默认输出 把制表符转化为空格 - 修改制表符转空格的个数 把制表符转化为空格 - 修改制表符转空格的个数…

HackMyVM-Animetronic

目录 信息收集 arp nmap nikto whatweb WEB web信息收集 feroxbuster steghide exiftool hydra ssh连接 提权 系统信息收集 socat提权 信息收集 arp ┌──(root㉿0x00)-[~/HackMyVM] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 08:00:27:9d:6d:7…

C语言——每日一题(轮转数组)

一.前言 前不久学习了时间复杂度的概念&#xff0c;便在力扣上刷了一道需要参考时间复杂度的题——轮转数组 https://leetcode.cn/problems/rotate-array/submissions这道题不能使用暴力算法&#xff0c;因为这道题对时间复杂度的要求不能为O&#xff08;N^2&#xff09;。因…

【数据库表的约束】

文章目录 一、NULL vs &#xff08;空字符串&#xff09;二、not null 和default三、列描述字段comment四、zerofill五、primary key 主键总结 一、NULL vs ‘’&#xff08;空字符串&#xff09; NULL和空字符串’’ NULL代表什么都没有。 空字符串’代表有&#xff0c;但串…

CI/CD 上云为何如此重要

近年来&#xff0c;敏捷度和速度日渐成为产品开发的关键。市场高速运行&#xff0c;时间就是金钱&#xff0c;也是企业发展的关键。游戏、金融、自动化产业等软件开发企业更像卷入了一场无休止的时间竞赛。 这也难怪 DevOps 备受欢迎。企业借助 DevOps 不断加速优质软件的交付…

​分享1.36G全国村名点数据

数据是GIS的血液&#xff01; 我们在《2015年中国电子地图数据》一文中&#xff0c;为大家有偿分享了一份图层丰富&#xff0c;且有26.8G大小的全国电子地图。 这里再为大家分享一份有1.36G大小的全国村名数据&#xff0c;本数据来自网友分享&#xff0c;据说为2023年的村名数…

VMware 替代专题|14 个常见问题,解读 VMware 替代的方方面面

随着 VMware by Broadcom 调整订阅模式和产品组合&#xff0c;不少用户也将 VMware 替代提上日程。为了帮助用户顺利完成从 VMware 替代方案评估到产品落地的一系列环节&#xff0c;我们通过这篇博客&#xff0c;对 VMware 替代场景下用户经常遇到的问题进行了梳理和解答。 更…

【工作记录】openjdk-22基础镜像的构建

背景 近期使用到的框架底层都用的是springboot3.0&#xff0c;要求jdk版本在17甚至更高。 于是决定制作一个基于openjdk22的基础镜像&#xff0c;本文对这一过程进行记录。 作为记录的同时也希望能够帮助到需要的朋友。 期望效果 容器内可以正常使用java相关命令且版本是2…

再议大模型微调之Zero策略

1. 引言 尽管关于使用Deepspeed的Zero策略的博客已经满天飞了&#xff0c;特别是有许多经典的结论都已经阐述了&#xff0c;今天仍然被问到说&#xff0c;如果我只有4块40G的A100&#xff0c;能否进行全量的7B的大模型微调呢&#xff1f; 正所谓“纸上得来终觉浅&#xff0c;…

华为OD机试【路灯照明问题】(java)(100分)

1、题目描述 在一条笔直的公路上安装了N个路灯&#xff0c;从位置0开始安装&#xff0c;路灯之间间距固定为100米。 每个路灯都有自己的照明半径&#xff0c;请计算第一个路灯和最后一个路灯之间&#xff0c;无法照明的区间的长度和。 2、输入描述 第一行为一个数N&#xff…

一文了解什么是SSL证书?——值得收藏

SSL证书&#xff0c;全称Secure Sockets Layer证书&#xff0c;是一种网络安全协议的实现方式&#xff0c;现在通常指的是其继任者TLS&#xff08;Transport Layer Security&#xff09;证书&#xff0c;不过习惯上仍称为SSL证书。它的主要作用是确保互联网上的数据传输安全&am…

ESP32 IDF linux下开发环境搭建

文章目录 介绍升级Python环境下载Python包配置编译环境及安装Python设置环境变量 ESPIDF环境搭建下载esp-idf 代码编译等待下载烧录成功查看串口打印 介绍 esp32 官方文档给的不是特别详细 参考多方资料 最后才完成开发 主要问题在于github下载的很慢本教程适用于ubuntu deban…

HarmonyOS实战开发-如何实现查询当前城市实时天气功能

先来看一下效果 本项目界面搭建基于ArkUI中TS扩展的声明式开发范式&#xff0c; 数据接口是和风&#xff08;天气预报&#xff09;&#xff0c; 使用ArkUI自带的网络请求调用接口。 我想要实现的一个功能是&#xff0c;查询当前城市的实时天气&#xff0c; 目前已实现的功能…

5.合并两个有序数组

文章目录 题目简介题目解答解法一 &#xff1a;合并后排序解法二&#xff1a;双指针排序 题目链接 大家好&#xff0c;我是晓星航。今天为大家带来的是 合并两个有序数组 相关的讲解&#xff01;&#x1f600; 题目简介 题目解答 解法一 &#xff1a;合并后排序 假设我们要合…

每日OJ题_贪心算法三⑤_力扣134. 加油站

目录 力扣134. 加油站 解析代码 力扣134. 加油站 134. 加油站 难度 中等 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一…

Redis学习(十)|使用消息队列的重试机制实现 MySQL 和 Redis 的数据一致性

文章目录 介绍原理整体方案实现步骤示例代码总结其他&#xff1a;Kafka 重试策略配置1. 生产者重试策略配置2. 消费者重试策略配置 介绍 在分布式系统中&#xff0c;保持 MySQL 和 Redis 之间的数据一致性是至关重要的。为了确保数据的一致性&#xff0c;我们通常采取先更新数…

红外与可见光图像融合评价指标(cddfusion中的代码Evaluator.py)

一、Evaluator.py全部代码&#xff08;可正常调用&#xff09; import numpy as np import cv2 import sklearn.metrics as skm from scipy.signal import convolve2d import math from skimage.metrics import structural_similarity as ssimdef image_read_cv2(path, modeRGB…

minio上传文件失败如何解决

1. 做了什么操作 通过接口上传excel文件&#xff0c;返回响应值 2. 错误如图 2. 如何解决 根据错误描述定位到了部署minio的地方minio通过docker部署&#xff0c;找到docker - compose发现配置文件中minio有两个端口&#xff0c;一个是用于api的&#xff0c;一个是用于管理界面…

AI模型:windows本地运行下载安装ollama运行Google CodeGemma可离线运行数据模型【自留记录】

AI模型&#xff1a;windows本地运行下载安装ollama运行Google CodeGemma可离线运行数据模型【自留记录】 CodeGemma 没法直接运行&#xff0c;需要中间软件。下载安装ollama后&#xff0c;使用ollama运行CodeGemma。 类似 前端本地需要安装 node.js 才可能跑vue、react项目 1…

QX-mini51学习---(2)点亮LED

目录 1什么是ed 2led工作参数 3本节相关原理图分析 4本节相关c 5实践 1什么是ed 半导体发光二极管&#xff0c;将电能转化为光能&#xff0c;耗电低&#xff0c;寿命长&#xff0c;抗震动 长正短负&#xff0c;贴片是绿点处是负极 2led工作参数 3本节相关原理图分析 当…
最新文章