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 disklog.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 logwrite_head();    // Write header to disk -- the real commitinstall_trans(); // Now install writes to home locationslog.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 blockstruct buf *from = bread(log.dev, log.lh.block[tail]); // cache blockmemmove(to->data, from->data, BSIZE);bwrite(to);  // write the logbrelse(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 absorbtionbreak;}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.rhkb.cn/news/321530.html

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

相关文章

神经网络极简入门

神经网络是深度学习的基础&#xff0c;正是深度学习的兴起&#xff0c;让停滞不前的人工智能再一次的取得飞速的发展。 其实神经网络的理论由来已久&#xff0c;灵感来自仿生智能计算&#xff0c;只是以前限于硬件的计算能力&#xff0c;没有突出的表现&#xff0c;直至谷歌的A…

AI数据中心网络技术选型,InfiniBand与RoCE对比分析

InfiniBand与RoCE对比分析&#xff1a;AI数据中心网络选择指南 随着 AI 技术的蓬勃发展&#xff0c;其对数据中心网络的要求也日益严苛。低延迟、高吞吐量的网络对于处理复杂的数据密集型工作负载至关重要。本文分析了 InfiniBand 和 RoCE 两种数据中心网络技术&#xff0c;帮助…

MySQL数据库练习——视图

schooldb库——utf8字符集——utf8_general_ci排序规则 先创建库&#xff0c;再去使用下列的DDL语句。 DDL CREATE TABLE student (id int(11) NOT NULL AUTO_INCREMENT COMMENT 学号,createDate datetime DEFAULT NULL COMMENT 创建时间,modifyDate datetime DEFAULT NULL …

(四)JVM实战——GC垃圾回收

垃圾回收算法 垃圾的判别 引用计数法&#xff1a;实现简单&#xff0c;判定效率高&#xff0c;回收没有延迟&#xff1b;无法解决循环引用的问题&#xff1b;可达性分析算法&#xff08;根搜索算法&#xff09;&#xff1a;没有循环引用的问题&#xff0c;防止内存泄漏 GCRo…

​​【收录 Hello 算法】3.3 数字编码

目录 3.3 数字编码 3.3.1 原码、反码和补码 3.3.2 浮点数编码 3.3 数字编码 Tip 在本书中&#xff0c;标题带有 * 符号的是选读章节。如果你时间有限或感到理解困难&#xff0c;可以先跳过&#xff0c;等学完必读章节后再单独攻克。 3.3.1 原码、反码和补码 在…

证券基金信创联盟研讨会:YashanDB分享金融核心数据库技术实践

4月26日&#xff0c;由证券基金行业信息技术应用创新联盟主办、WG3稽核风控系统工作组承办、国信证券股份有限公司协办的信创联盟2024年度系列研讨会第三期-稽核风控系统信创实践成功举办。国内头部企业国信证券、申万宏源证券、信达证券、国金证券、广发证券等单位共计300余人…

net lambda 、 匿名函数 以及集合(实现IEnumerable的 如数组 、list等)

匿名函数&#xff1a;》》》 Action a1 delegate(int i) { Console.WriteLine(i); }; Lambda:>>> Aciont a1 (int i) > { Console.WriteLine(i); }; 可以简写 &#xff08;编译器会自动根据委托类型 推断&#xff09; Action a1 &#xff08;i&#xff09;> {…

C语言 | Leetcode C语言题解之第74题搜索二维矩阵

题目&#xff1a; 题解&#xff1a; bool searchMatrix(int** matrix, int matrixSize, int* matrixColSize, int target) {int m matrixSize, n matrixColSize[0];int low 0, high m * n - 1;while (low < high) {int mid (high - low) / 2 low;int x matrix[mid /…

RISCV 外部GCC 工具链安装@FreeBSD15

在交叉编译的时候&#xff0c;可以使用FreeBSD15默认的工具链&#xff1a;LLVM 也可以使用GCC工具链&#xff0c;GCC可以使用现成pkg包安装&#xff0c;也可以编译安装。 LLVM的特点是高移植性和高效&#xff0c;但学习成本高。GCC的特点是成熟稳定&#xff0c;但优化能力有限…

【第6节课笔记】LagentAgentLego

Lagent 最中间部分的是LLM&#xff0c;即为大语言模型模块&#xff0c;他可以思考planning和调用什么action&#xff0c;再将其转发给动作执行器action executer执行。 支持的工具如下&#xff1a; Arxiv 搜索 Bing 地图 Google 学术搜索 Google 搜索 交互式 IPython 解释器 IP…

指针再学习笔记

概念 示例 类型 示例 作用 注意&#xff1a;有些内存地址可能系统不会允许任意访问 运算 示例 空指针

cmake install命令无法覆盖同名文件

文章目录 1. 问题记录2. 原因排查3. 解决方案 1. 问题记录 我有两个同名文件test.txt&#xff0c;它们内容不同&#xff0c;但时间戳相同&#xff08;文件属性中的修改时间相同&#xff09; 我希望在cmake中利用install命令&#xff0c;将${PATH_SRC}/test.txt替换${PATH_DES…

Delta lake with Java--利用spark sql操作数据1

今天要解决的问题是如何使用spark sql 建表&#xff0c;插入数据以及查询数据 1、建立一个类叫 DeltaLakeWithSparkSql1&#xff0c;具体代码如下&#xff0c;例子参考Delta Lake Up & Running第3章内容 import org.apache.spark.sql.SaveMode; import org.apache.spark.…

AT32 雅特力CAN详细使用说明配置细则

CAN 过滤器使用说明 CAN 过滤器相当于关卡&#xff0c;每当收到一条报文时&#xff0c;CAN 要先将收到的报文从这些过滤器上"过滤"一下&#xff0c;能通 过的报文是有效报文&#xff0c;收进相关联 FIFO&#xff08;FIFO0 或 FIFO1&#xff09;&#xff0c;不能通过的…

【贪心算法】最小生成树Kruskal算法Python实现

文章目录 [toc]问题描述最小生成树的性质证明 Kruskal算法Python实现时间复杂性 问题描述 设 G ( V , E ) G (V , E) G(V,E)是无向连通带权图&#xff0c; E E E中每条边 ( v , w ) (v , w) (v,w)的权为 c [ v ] [ w ] c[v][w] c[v][w]如果 G G G的一个子图 G ′ G^{} G′是…

聪明与诚实:社会信任的桥梁

在现代社会中&#xff0c;我们经常听到这样的评价&#xff1a;“某人真聪明。”然而&#xff0c;当我们深入思考时&#xff0c;会发现“聪明”这个词背后所承载的含义并不单一。聪明和狡诈往往被混淆&#xff0c;而诚实的价值却时常被忽视。在一个高度诚信的社会里&#xff0c;…

Arduino控制继电器,制作智能浇水系统

所需硬件材料 Arduino模块、继电器、直流电机、3-6v电池&#xff08;这个是必须的&#xff0c;电机不能直接接在arduino的5v引脚上&#xff0c;会引起电压不足&#xff09;、杜邦线 实现效果&#xff1a; 电机转动一秒停一秒 将硬件连接如下&#xff1a; 将电机连接到继电…

【电路笔记】-RC振荡器电路

RC振荡器电路 文章目录 RC振荡器电路1、概述2、RC 相移网络3、基本RC振荡器电路4、运算放大器RC振荡器5、运算放大器相位滞后RC振荡器电路6、RC振荡器示例11、概述 RC 振荡器使用放大器和 RC 反馈网络的组合,由于级之间的相移而产生输出振荡。 当单级晶体管放大器作为共发射…

使用API有效率地管理Dynadot域名,设置所有域名默认whois信息

关于Dynadot Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮…

asp.net成绩查询系统

说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据库&#xff09; 主要技术&#xff1a; 基于asp.net架构和sql server数据库 功能模块&#xff1a; asp.net成绩查询系统 学生功能有查看成绩和修改账号密码等 后台管理员可以进行用户管理 管理员添加管理员查询注…