Ext文件系统

标签: ext 文件系统 | 发表时间:2011-10-28 10:41 | 作者:GG大婶 Haides
出处:http://www.cnblogs.com/

1、数据的存储

  虽然从Ext2到Ext4,找数据的方式发生了变化,但是,磁盘的布局还是非常相似的。其实这个东西也不需要变化,因为现在也没什么特别巧妙的方式,而且磁盘的吞吐量、效率的瓶颈也不在这里。当然,这里排除那些根据自身文件特点设计的数据库,毕竟还是为了支持通用文件。磁盘布局如下:

  Boot在第一个块,放的应该是引导程序,超级块就放在了第二个块上,如果不是可以在mount的时候通过参数sb来设置。对于经常要访问(比较重要)的内容可以在每个块组中都存储(当然这是比较浪费空间的,所有有的只选择在部分),在不同的块组中访问这些内容的时候磁头移动的距离就小了。如果开启了sparse block功能,那么只会在3、5、7的幂级的块组上才冗余这些信息。

  在超级块里面保存了这个文件系统的统计信息等,而且从中可以看出这个文件系统的特点。在内存中装载的时候也从超级块开始,知道了磁盘上的布局之后才能顺利的进行后面的操作,ext4_super_block的内容大概如下:

  1. 块、inode等统计信息,访问时间等。
  2. 块大小、以及每块数目等布局信息。
  3. uid、gid。
  4. 特性相关。
  5. 预先分配相关。
  6. 日志支持相关。
  7. 64位的支持,其实保存了需要的高16位。

  块组中其实没有保存什么有用的信息(例如超级块),ext4_group_desc的存在就是为了把磁盘分成一个个的块组。那分成块组之后有什么好处呢?试想一下如果没有块组,那么是不是位图这个关键的数据是不是要集中放到磁盘上的一个固定的位置?访问当然是没有问题的,但是不管在磁盘上的什么地方访问的时候都要跑到一个相同的地方去再访问一次位图,这样的代价就比较大了。而且在分配的时候尽量把文件的数据块放在同一个组,那么能在分配阶段就一定程度地避免了碎片。下面是ext4_group_desc中大概的内容(和Ext2中的不同的是对更大范围的支持):

  1. 块组中块位图、inode位图、inode表的位置。
  2. 空闲块(inode)、目录的数目。
  3. 校验。

  接下来就是块位图、inode位图了。为什么需要inode位图呢?因为ext4文件系统在要分配一个新的inode的时候并不是像在内存中分配其他数据结构的时候需要的时候分配数据结构占用的空间,而是预先分配好一组放在位图后面,就像一个inode池的感觉。那么在需要ext4_inode的时候就需要知道哪些是空闲的,哪些是用过的,这就是位图的作用了。位图具体就没什么好介绍的了,下面看ext4_inode的内容:

  1. mode、uid、gid等我们能看到的信息。
  2. 各种时间戳。
  3. i_data用来找到数据块(下面介绍怎么找)。
  4. file acl。

  大家也许奇怪了,这里的inode中看不到层次关系啊,那我们看到的是从哪里来的?其实对计算机来说找一个文件最方便、最简单的方法就是通过ino。但是这个对人类来说就太不方便了。所以Ext就做了一个看起来比较单独的一层来支持人的查找习惯:ext4_dir_entry_2。这个结构其实就是从name到ino的一个转换。但是为什么不在inode中直接保存了这个信息?如果那样的话代价就挺大的。现在相当于用ext4_dir_entry_2来体现了Ext4文件系统的骨架,然后具体的数据却是放在一个池子里面的。下面是ext4_dir_entry_2的内容:

  1. ino。
  2. ext4_dir_entry_2的长度(其实可以看成是到下个项的指针)。
  3. 名字长度。
  4. inode的类型。
  5. name。

关于目录项在磁盘上怎么保存的呢?大概如下图:

  图中的黄色的部分是一个目录项,然后通过ren_len就可以找到下一个项的位置。这样的话就相当于内存中的链表中的next指针了。而且,从上面图中也能大概看出如何分配、回收项占用磁盘的内存了。

  一个目录项当然是有其对应的inode,该目录项的内容以及子目录中有哪些inode是在ext4_dir_entry_2中保存的,而这些值的位置是在inode对应的数据块中的。这貌似又到了一个先有鸡(ino)还是先有蛋(ext4_dir_entry_2)的问题了。不过这个问题的解决方法都是很简单的(可以回想一下slab 初始化中的类似问题)。不管有多少的目录,他们总是有一个根目录的,也就是root。这样只要把root的ino设置为一个固定的值所有的问题都解决了,如果没有记错的话root的ino是2。

  到了这里,也许大家在想数据块中的内容貌似不只是数据嘛(其实子目录也是目录文件的数据了),那到底数据块中保存的是什么呢?对不同类型的文件来说当然是不同的,大概的区分如下:

  1. 常规文件就是保存数据了。
  2. 目录文件中保存自己、父目录、子目录的目录项。
  3. 对于符号链接文件,如果路径很短就保存在inode中,不然为其分配数据块来保存。
  4. 设备、管道、socket没有数据块,设备文件的主、从设备号保存在inode中。

2、查找数据

  在对Ext文件系统还什么都不知道的时候应该比较关心从路径名到ino的过程。这个过程的大部分工作量应该在VFS中吧,而且感觉没什么特别的地方。现在查找数据对这部分的内容就忽略不计了。我们关系的是,给定ino&offset,怎么样知道在磁盘上的位置呢?在课本上学到的只是告诉我们这个地方应该是用B树的,但是从Ext2好像没看的B树的影子,反而看到了内存管理中见过的东西:间接块。

  间接块管理磁盘上文件的数据块位置和内存中分页的效果有点像,当然细节还是不同的。不过这个地方的坏处就很明显了,如果是访问大文件中的两个顺寻的物理块,那么即使他们物理上也是相邻的(如果内存中没有其缓存的话),也要从第一层的间接块开始访问,直到最后一层才知道真正的块号。在内存中分页当然是问题不大的,但是磁盘上这样高出几倍的访问时间效率会很差。

  实际中当然是没有这么差的,间接块被缓存在内存中了,那么在很多情况下是不需要在取磁盘上读取其内容了。这种管理方式大概如下:

  另外,磁盘上的间接块一个比较复杂的地方是并不是所有的块都需要用到三级间接块,只有直接寻址用完了才需要一级寻址,二、三级寻址是同样的道理。所以,在寻找之前就需要知道到底用到几级间接寻址?在各级间接寻址中的偏移量是多少(其实这还算一个比较简单的过程吧)?

  在Ext4中引入了Extents的管理方式,每个extent是一组连续的块,提高了不少效率。就像间接块那样,要浪费一些空间来保存管理结构。其中ext4_extent表示一组连续的数据块,所表示的范围是从ee_start_hi<<32+ee_start到ee_start_hi<<32+ee_start+ee_len-1的范围。ee_len是16位的无符号数,但是最高位被预分配特性中用来标识是否一级被初始化过,所以一个extent可以表示2^15个连续的数据块。ext4_extent的定义如下:

struct ext4_extent {
__le32 ee_block;
__le16 ee_len;
__le16 ee_start_hi;
__le32 ee_start_lo;
};

  ext4_extent是在这个树的最底层的,上面则是通过ext4_extent_idx构建整个树的结构的,只有到了字节点的时候才用到ext4_extent来表示真正的范围,就像下面这个图所表示的(一棵高度一定的树):
  这样的话每个块中以ext4_extent_header开头,保存了层数、魔数、该块中的项的数目等信息。下面来看这个结构的具体定义:

struct ext4_extent_header {
__le16 eh_magic; /* probably will support different formats */
__le16 eh_entries; /* number of valid entries */
__le16 eh_max; /* capacity of store in entries */
__le16 eh_depth; /* has tree real underlying blocks? */
__le32 eh_generation; /* generation of the tree */
};

  而ext4_extent_idx的作用也就很明显了,帮助我们很容易地找到ext4_extent就可以了。这里只需要存储一个范围、以及这个范围内下层的位置就好啦,该结构的具体定义如下:

struct ext4_extent_idx {
__le32 ei_block;
__le32 ei_leaf_lo;
__le16 ei_leaf_hi;
__u16 ei_unused;
};

  其中ei_block就是偏移量的范围了,在查找的时候可以通过二分(ext4_ext_binsearch_idx)来找到所在的ext4_extent_idx,这里可以看出这种管理方式能表示的单个文件的大小是比ext2大了些。ei_leaf_lo+ei_leaf_hi<<32就是下层的ext4_extent_idx的位置了。关于如何定位的细节就不在这里说明了,看代码还是比较容易的。

  Ext4中给定一个文件,怎么知道他是间接块方式还是extents方式的呢?通过ext4_inode中的flag的相应位来检查。两种管理方式都用到了i_data段,所以是不可能共存的,况且共存不会带来什么好处,反而会让复杂度急剧增加。
3、日志

  什么是日志以及日志的作用就不在这里说了。Ext3与Ext2的管理数据块的方式都是差不多的,不同之处是加上了记录日志的功能,可以从Ext2平滑地过渡到Ext3。Ext3日志文件系统的思想就是对文件系统任何的高级操作都分两步进行,首先,把待写入的块的一个副本存放在日志中;其次,当发往日志的I/O数据传送完成时,块就写入文件系统。当发往文件系统的I/O数据传送终止时,日志的块副本就被丢弃。

  Ext3既可以只对元数据的修改做日志,也可以记录所有的日志。有下面的三种日志模式:

  1. journal,把所有数据块的改变都记入日志,最安全也最慢。
  2. ordered,只记录元数据的改动,Ext3会把元数据和相关的数据块进行分组,以便把元数据写入磁盘之前写入数据块。
  3. writeback,只记录元数据改动,最快的一种模式。

  Ext3本身不处理日志,而是利用日志块设备(JBD)。Ext3调用JBD例程来确保在系统万一出现故障时他的后续操作不会损坏磁盘数据结构。Ext3与JBD之间的交互本质上基于三个基本单元:日志记录、原子操作、事务。磁盘上把日志记录在.journal中,下面看该文件的结构:

可以看到日志的内容是一组组的提交块以及撤销块,下面看提交块的结构:

在日志中,每个块都开头都是journal_header_t(当然是不包括数据块的)。这个结构表明了这个描述块是一种描述块(其实可以说成是三种中的一种)还是普通的数据块。下面来看journal_header_t结构:

typedef struct journal_header_s{
__be32 h_magic;
__be32 h_blocktype;
__be32 h_sequence;
} journal_header_t;

h_magic算是一个标志吧,如果死一个日志块描述则为JFS_MAGIC_NUMBER。h_blocktype表示块的类型,有以下五种:

#define JBD2_DESCRIPTOR_BLOCK   1
#define JBD2_COMMIT_BLOCK 2
#define JBD2_SUPERBLOCK_V1 3
#define JBD2_SUPERBLOCK_V2 4
#define JBD2_REVOKE_BLOCK 5

  如果数据块被转义,那么开头的四个字节可能正好是JFS_MAGIC_NUMBER,那么这个块就背误认为是描述符块了。解决方法是当要往日志中写一个普通的数据块时,如果发现其开头的四个字节刚好是JFS_MAGIC_NUMBER,则将该4个字节改写成0,并且在描述符块的索引项中设置JFS_FLAG_ESCAPE,表示被转义过了,在恢复的时候重新把这四个字节该为JFS_MAGIC_NUMBER。而h_sequence表示这个描述块的序号。在journal_header_t后面跟着的是n个要提交的块的描述结构,也就是journal_block_tag_t,该结构如下:

typedef struct journal_block_tag_s{
__be32 t_blocknr;
__be32 t_flags;
__be32 t_blocknr_high;
} journal_block_tag_t;

t_blocknr和t_blocknr_high组合起来指明要处理的是哪个磁盘上的块,t_flags则指明了操作,有下面的几个值:

#define JBD2_FLAG_ESCAPE       1
#define JBD2_FLAG_SAME_UUID 2
#define JBD2_FLAG_DELETED 4
#define JBD2_FLAG_LAST_TAG 8

1代表这个块经过转义了,2表明和前面是相同的UUID(在第一个索引项中包含了128位的UUID,表明该块所属的文件系统),有这个标志可以节省空间。journal_header_t中没有指出下面有多少个索引项,所以如果发现t_flags中设置了JBD2_FLAG_LAST_TAG就说明是最后一个了。

  提交块就很简单了,只是一个journal_header_t了,下面看取消块的结构:

  虽然对journal_header_t进行了一次包装,其实也只是增加了一个r_count,表示取消块中实际使用的字节数。接下来是保存的一组块好,这个是用来加快恢复速度的。如果在恢复的时候发下了保存的这些块并且当前transaction ID <= 67则不必恢复了(因为transaction 67中已经将这些块删除了)。从这里可以看出取消块中保存的应该是一些特别的journal_block_tag_t所指向的块号(也就是这些块是没办法恢复的了?)。好了,下面在来一张日志的布局图(相比来说这张图片更清楚一些):

  日志处理的关键不在磁盘上,而是在内存中(貌似很多其他的东西也是这样的==)。在内存中,事务用transaction_t来表示,描述了一个日志文件中的一个描述符块。主要有些一下内容:

  1. 事务的序列号、事务的状态、在日志中的开始位置、所管理的缓存数目。
  2. 几个由journal_head构成的列表,保存不同状态的缓存。
  3. 最长等待时间、启动时间、到期时间。
  4. 检查点统计。
  5. running number。
  6. handle数目。
journal_head双向链表有9中,在得到一个buffer_head的时候要决定把它放在哪个链表上。各个链表之间的不同会在后面给出:
#define BJ_None         0       // 不是日志
#define BJ_SyncData 1 // 同步数据?提交日志之前刷出数据。
#define BJ_Metadata 2 // 元数据
#define BJ_Forget 3 // 被取代的
#define BJ_IO 4 // 临时I/O用的
#define BJ_Shadow 5 // 缓存区的内容正在被保存到日志
#define BJ_LogCtl 6 // 缓存区包含日志描述符
#define BJ_Reserved 7 // 保留缓存区用与日志的访问需要
#define BJ_Locked 8 // 提交期间被锁定
#define BJ_Types 9
这些链表上保存的是journal_head,该结构相当于对buffer_head进行了一次封装,把缓存区纳入日志系统的管理中。该结构定义如下:
struct journal_head {
struct buffer_head *b_bh;      // 指向缓存区
int b_jcount;       //
unsigned b_jlist;         // 日志列表
unsigned b_modified;    // 被现在的运行事务修改了
char *b_frozen_data;    // 保存一份冻结数据
char *b_committed_data;    // 一份未提交数据的保存,避免复写未提交的删除?
transaction_t *b_transaction;    // 所有者,只是把数据放在这个上面?
transaction_t *b_next_transaction;    // 正在修改数据的事务。一个事物正在提交,另一个在修改
struct journal_head *b_tnext, *b_tprev;// 构建双向链表
transaction_t *b_cp_transaction; // 在哪个事务中这个缓存在被检查?
struct journal_head *b_cpnext, *b_cpprev; // 构建双向链表
};

在内存中用journal_s来管理一个日志,该结构如下:

    journal_superblock_t    *j_superblock;// 超级块    
int j_format_version;// 超级块的版本
spinlock_t j_state_lock;// 保护版本
int j_barrier_count;// 等待创建屏障锁的进程数目
struct mutex j_barrier;// 屏障锁
transaction_t *j_running_transaction;// 目前正在运行的事务
transaction_t *j_committing_transaction;// 正在提交的事务
transaction_t *j_checkpoint_transactions;// 等待检查的事务
wait_queue_head_t j_wait_transaction_locked;// 等待一个锁定事务去开始提交
wait_queue_head_t j_wait_logspace;// 等待检查完成
wait_queue_head_t j_wait_done_commit;// 等待提交完成
wait_queue_head_t j_wait_checkpoint;// 触发检查的等待队列
wait_queue_head_t j_wait_commit;// 触发提交的等待队列
wait_queue_head_t j_wait_updates;// 等待更新完成
struct mutex j_checkpoint_mutex;// 避免同时检查
unsigned long j_head;// 第一个没用的块
unsigned long j_tail;// 最后一个还在用的块
unsigned long j_free;// 日志中的空闲块
unsigned long j_first;// 日志的开始
unsigned long j_last;// 日志的结束
struct block_device *j_dev;// 块设备
int j_blocksize;// 块大小
unsigned long long j_blk_offset;// 在块设备上的大小
struct block_device *j_fs_dev;// 客户端的文件系统的块设备
unsigned int j_maxlen;// 日志的最大容量
spinlock_t j_list_lock;// 保护buffer并发
struct inode *j_inode;// 任选的存放日志的inode
tid_t j_tail_sequence;// 最老的事务的序列号
tid_t j_transaction_sequence;// 下个事务的序列号
tid_t j_commit_sequence;// 最近提交的事务的序列号
tid_t j_commit_request;// 最近提交事务的编号
__u8 j_uuid[16];// uuid
struct task_struct *j_task;// 现在正在提交的进程
int j_max_transaction_buffers;// 一次日志提交元数据的限制
unsigned long j_commit_interval;// 事务的生命长度
struct timer_list j_commit_timer;// 定时器
spinlock_t j_revoke_lock;
struct jbd2_revoke_table_s *j_revoke;// 在现在的事务上的取表
struct jbd2_revoke_table_s *j_revoke_table[2];
struct buffer_head **j_wbuf;// 提交的bh数组
int j_wbufsize;
pid_t j_last_sync_writer;
struct transaction_stats_s *j_history;
int j_history_max;
int j_history_cur;
spinlock_t j_history_lock;
struct proc_dir_entry *j_proc_entry;
struct transaction_stats_s j_stats;
void *j_private;
};

作者: GG大婶 发表于 2011-10-28 10:41 原文链接

评论: 0 查看评论 发表评论


最新新闻:
· Google在2011年的重要收购(2011-10-28 16:56)
· 黑客宣告iPhone 4S被破解 越狱工具尚未发布(2011-10-28 16:48)
· ifanr 访谈:手机 QQ 浏览器背后的运营故事(2011-10-28 16:29)
· Android 平台遗弃史(2011-10-28 16:21)
· 腾讯入股开心新进展:要求其开发商接入开心网(2011-10-28 16:17)

编辑推荐:乔布斯:初心与终点

网站导航:博客园首页  我的园子  新闻  闪存  小组  博问  知识库

相关 [ext 文件系统] 推荐:

Ext文件系统

- Haides - 博客园-首页原创精华区
  虽然从Ext2到Ext4,找数据的方式发生了变化,但是,磁盘的布局还是非常相似的. 其实这个东西也不需要变化,因为现在也没什么特别巧妙的方式,而且磁盘的吞吐量、效率的瓶颈也不在这里. 当然,这里排除那些根据自身文件特点设计的数据库,毕竟还是为了支持通用文件.   Boot在第一个块,放的应该是引导程序,超级块就放在了第二个块上,如果不是可以在mount的时候通过参数sb来设置.

[Ext JS 4] MVC 应用程序框架

- - CSDN博客Web前端推荐文章
大型客户端应用程序总是很难编写,很难组织和很难维护. 随着功能的增加和更多的开发人员加入项目,对项目的控制也越来越困难了. Ext JS 4 提供了一个新的应用程序框架帮助组织代码. 模型 - 一组栏位和数据的集合. Model (在Ext JS 3中使用Record class). 视图 - 组件类型, grids,trees 和 panels 都是属于试图.

(转)使用mysqladmin ext了解MySQL运行状态

- - jackyrong
mysqladmin是MySQL一个重要的客户端,最常见的是使用它来关闭数据库,除此,该命令还可以了解MySQL运行状态、进程信息、进程杀死等. 本文介绍一下如何使用mysqladmin extended-status(因为没有"歧义",所以可以使用ext代替)了解MySQL的运行状态. 使用mysqladmin extended-status命令可以获得所有MySQL性能指标,即show global status的输出,不过,因为多数这些指标都是累计值,如果想了解当前的状态,则需要进行一次差值计算,这就是mysqladmin extended-status的一个额外功能,非常实用.

你的下一个文件系统——Btrfs

- frocket - K.I.S.S. - 简单哲学
提起 Btrfs ,相信广大折腾帝们都不会陌生,被誉为“下一代 Linux 文件系统”的它,具有扩展性好、支持数据校验、支持多设备管理等等强大特性,使得 Ext4 也只能成为悲剧的过渡产品,还不赶快找一个 Ubuntu 10.10、Fedora 15、Meego 什么的试一下. 支持什么多设备、什么数据校验跟你有一毛钱关系啊.

NFS 文件系统源代码剖析

- wuyan - IBM developerWorks 中国 : 文档库
NFS 文件系统是目前最为成功的网络文件系统,在文件共享领域有着出色的表现,特别是 pNFS 的出现,使得 NFS 文件系统在性能和规模上有了大幅提升,为其带了更为广阔的应用空间. 同时,NFS 之所以备受关注,还在于它在 NAS 存储领域的关键作用. 本文剖析了 NFSv3 文件系统源代码,使开发工程师,技术支持人员,特别是存储领域从业人员对 NFS 有更为深刻的认识.

文件系统文献推荐

- Lianhui Wang - 刘爱贵的专栏
非常好的文件系统阅读文献,研究存储和文件系统的不能不读的,其中的每一个文件系统都非常经典的,代表存储或者文件系统发展过程的里程碑.

Fedora 16默认文件系统为Btrfs

- rIPPER - Solidot
51开源社区 写道 "在6月8日举行的Fedora工程指导委员会会议上,委员会决定Fedora 16 将以Btrfs作为默认文件系统. Btrfs,是Oracle于2007年宣布并进行中的copy-on-write文件系统. 目标是取代Linux目前各大发行版的ext4文件系统,改善ext4的限制,特别是单个文件的大小,总文件系统大小或文件检查.

linux下的文件系统选型

- Michael - shell&#39;s home
    贝壳原来一直认为文件系统可以随便选,结果最近吃了两次苦头. 一个是btrfs对虚拟机支持不良,另一个是特定情况下xfs性能比ext3高20倍. 痛定思痛,打算列一下文件系统选型的方法和依据,欢迎拍砖.     下面我列一下纳入参考的文件系统,当然,ntfs就不要出来搞基了,玩嵌入式/光盘live之类的朋友也不要来凑热闹了阿.

Linux 下的文件系统选型

- 桔子 - python.cn(jobs, news)
贝壳原来一直认为文件系统可以随便选,结果最近吃了两次苦头. 一个是btrfs对虚拟机支持不良,另一个是特定情况下xfs性能比ext3高20倍. 痛定思痛,打算列一下文件系统选型的方法和依据,欢迎拍砖. 下面我列一下纳入参考的文件系统,当然,ntfs就不要出来搞基了,玩嵌入式/光盘live之类的朋友也不要来凑热闹了阿.

Linux的proc文件系统详解

- - 博客园_iTech's Blog
Linux系统上的/proc目录是一种文件系统,即proc文件系统. 与其它常见的文件系统不同的是,/proc是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态.