超好的一篇帖子:http://topic.csdn.net/t/20021222/01/1284250.html
讲journal在ext3 中起的作用
虽然没有人回应,我还是先自己讨论讨论吧。同样的东西在水木的kernelTech版也发过了。如果想和我讨论,我的邮箱 xjaguar@263.net
这个post对我自己来说,目的有两个:一,有经验的,可以帮我检验一下
理解,没经验的,这可以作为一个入门级的文档;二,希望有兴趣的,能和我
一起做移植的工作。
EXT3,是在EXT2上设计的,可以说是 EXT2 + Journal(日志)。Journal
是为提高可靠性而设计的一个特殊的文件(确切说来也可以不是文件)。基本
过程是,把对文件系统操作划分为一个个事务,使用Journal记录这些事务,
然后再实际执行他们。这样,即使由于当机,导致事务没有完成,也可以根据
日志,快速的把文件系统恢复到一个正确的状态。可以看出以上所说的基本原
理、术语,都数据库相类似的。
====================================
文件系统特点
文件系统保存两类数据:元数据(meta-data)和用户数据。元数据即分区
特征、目录结构、文件属性。
UNIX环境中有字符、区块两类设备,通常文件系统是存储在块设备中。保
存这些数据的基本单元是block (区块),它通常是实际磁盘上 sector 大小的
2的幂次倍。创建、删除和修改文件等动作的最终结果,都是体现在对区块的
修改。
比如ext2分区中建立一个文件,需要完成这几步,(未必按照这个顺序)
a.分配inode,
b.需要找到他所在目录的inode,向他的数据中加入新的目录项,
c.分配文件所需的数据块。
d.修改分配inode的数据
(如果这些操作没有一起完成,那么你的磁盘上的文件系统就有点小问题了。)
显然这里至少修改了:
1(superblock)+1(inode-bitmap) + 1(inode-table)+ 1(block-bitmap)
这四个块。这也意味着,有了这四个块的正确数据,就足够把这个操作恢复了。
===================================
缓存特点
缓存对文件系统显然是绝对不能少的,Unix的缓存的基本特征它以虚拟块为
单位。即,读取文件时,由文件文件系统计算出给定文件位置所对应的块号,根
据这个号码再向缓存管理模块请求这个块的数据。(插一句:还有另一种策略,
让缓存和文件偏移量相对应。也就是说,每个内核中的文件控制块(和物理文件
一一对应的数据结构)保存独立的缓存映射表,根据这个表可以直接从文件位置
(偏移量)找到数据,不需要文件系统的计算。这是NT的Cache Manager采取的
策略。)
这种结构使得journal层纪录的东西可以大大简化。它只要记住被修改的块
的编号和数据,就可以完成恢复。这个设计很妙,好处是journal层和文件系统
的耦合很小。其结果:
文件系统的恢复可以不需要具体文件系统代码的参与。文件系统只需要说:
journal层,你给我检查xxx分区,有错的话就恢复它。而Journal层有足够的信
息完成它。如果journal层中记录的是文件的操作:
如"创建大小为xxx,属性为yyyy的 /xyz/xxx 文件",
恢复的时候,没有文件系统代码的帮助,journal层没办法完成。两边代码都少
了。
不同的文件系统都有可能使用一个journal层。道理很简单,对吧。
===================================
Journal记录的数据
Journal提供两种记录模式,一种只记录元数据,另一种记录所有写入的数
据。第一眼看到,你就会感觉"第二种方式很可能慢"。的确,按照设计者自己
的说法,性能差不多减半,因为数据要写到journal和实际文件两个地方,即写
了两次。同时,第一种方式有可能会产生意料之外的问题。因为既然Journal只
记录了元数据,那么就只能保证元数据的一致性,但是用户数据就…,所以用户
可能会看到原来的数据,甚至看到原本属于其它文件的数据(可能的安全漏洞哦,
不过这个问题的对策设计者自己也有说)。
不过我自己总是在想,有没有避免第二种方式中写入两次的办法?如果有,
那就可以放弃第一种办法。你有什么想法?
==================================
对外接口
有了上面的概念,自然就该转到接口定义了。Jbd提供给文件系统层的一个
要修改inode-bitmap时,告诉jbd,我要修改一个和inode-bitmap所在块对应的
缓存;做完了,告诉jbd,事务结束。
这个东西现在好像文档还是很少(至少98年,ext3就应该在开发过程中了)
关于接口,比我刚才说的稍详细的文档在:
Documentation/DocBook/journal-api.tmpl
(make之前的,起始位置自然是你的linux源文件的目录)
==================================
实现。。。
头大的问题,其实让我看jbd的实现的原始动机是移植ext3,在看ext3时又
发现还有个jbd。事情常常就是这样在以出人意料的方式连接在一起,对于我
这样的初学者来说。
(继续实现部分)
实现·块缓存读写
Jbd中有一个专门的线程(执行的函数 fs/jbd/journal.c:
int kjournald(void*))来根据一定的时间间隔去写将事务写入journal文件。
把事务写入,这显然不会是一个简单的操作,会要多部分数据,而文件系统可
能也要访问某一个块,所以同步问题又来了。在jbd中,有一个全局的
spinlock journal_datalist_lock。(这个锁本不该是全局的,它会把多个
ext3分区的块缓存都用一个锁保护起来,实现中仍这样做的原因可能是全局
锁实现起来更简单,而设计者没有时间修改)。
事务是在纪录对块的操作,不可避免的会出现在几个事务中访问同一个
块。所以,jbd不但要能从事务找到它涉及到的块缓存,同时也要能从块缓
存知道它在那个事务中被用到。那么要记录多少个从 "块缓存->事务" 的这
种关联?答案是一个。
如果两个事务都在写,逻辑上没问题,后一次将覆盖前一次的结果。但
是,运行时却要复杂很多。比如,事务31要写块7511,之后事务32也写7511,
该怎么办?设计者选择的是copy-on-write(所以“答案是一个”:),将缓存
复制一份,交给…写。
把"谁"给"谁"?设想这时候还有人需要读块7511,它应该读到那个数据呢?
应该是32将处理的那个缓存。所以,缓存复制后,新复制出来的一份交给事
务31处理,旧的(jbd注释中把这个叫做primary copy)交给事务32或是读者。
不过这部分的代码看起来比上面的要复杂得多。
实现·删除文件
按照设计者Dr. Stephen Tweedie的说法,"对于所有日志式文件系统的
设计者来说,删除文件是噩梦的来源"。
从一个例子来说。先删除目录 ./temp1(事务44),向文件./text按追
加模式写入数据(事务45)。具体点,删除目录导致释放目录使用的块7511;
追加数据时恰巧给./text 文件分配了块7511,然后写了一句话"nightmare",
就在这时,宕机了。
还有其他事情碰巧也发生在一起了:journal层没能把上面这两个事务记
录在案。世界重新开始,自然目录./temp1保留了下来,而且内容却是
"nightmare"。
解决的方法…,我并没有在代码中找到,倒不是说一定就没实现。也许,
被移到ext3的实现代码中,由ext3尽可能保证不将刚释放掉的块立即再使用。
实现·on-disk结构
这里的信息全部来自于阅读代码,主要出现在函数:
fs/jbd/transaction.c: journal_commit_transaction 和
fs/jbd/recovery.c: journal_recovery 。
所以,即是第一手资料,又很可能会有遗漏。
每个事务在文件里包含了三个区域:块描述符区,Revoke区和Commit区。
每个区都有共同格式的标头。标头中包含常见的MagicNumber,区类型标示符,
这个区所属的事务的标示号。
描述符区:
在标头之后,是一系列的journal_block_tag_t结构,这些tag之后,是
同样数量的数据块,每个tag,记录了后面一个数据块实际的区块号。
Reovke区:
这个区的结构和实现过程倒是不难理解。Revoke,按照字典的意思,是
废弃,似乎和删除等有关系。不过从何它相关联的东西看,似乎它的作用很
少。
还是从假设说起。试想事务34记录了对块7511的写动作,事务35里记录
了"删除"7511的动作。让我们从两个面来看:在恢复过程中,上面这样的动
作显然只需要做事务35里的"删除"(实际上就是jbd什么也不做);但是jbd在
记录时必须记下来,因为当还在34处时,它没办法预知未来要发生的事情。
块结构:在标头后是多个被Revoke的块的标号。
Commit区:
这里的数据最简单,除了标头区内别的数据完全没用。这个区的逻辑意
思是:这个事务被完整地记录了。
块结构:在标头后是多个被Revoke的块的标号。