硬盘
图片来自极客时间趣谈linux操作系统
硬盘→盘片→磁道→扇区(每个 512 字节)
文件系统就是安装在这样的硬盘之上
Linux 下最主流的文件系统格式——ext 系列的文件系统的格式
inode 与块的存储
块(Block)
硬盘分成相同大小的单元,块(Block)
一块的大小是扇区大小的整数倍,默认是 4K(格式化设置)
一个文件不用给他分配一块连续的空间,分散成一个个小块进行存放
inode 结构
需要一个结构 inode 来存放文件通过块来存储的信息
inode 数据结构
\linux-4.13.16\fs\ext4\ext4.h
struct ext4_inode {
__le16 i_mode; /* File mode */
__le16 i_uid; /* Low 16 bits of Owner Uid */
__le32 i_size_lo; /* Size in bytes */
__le32 i_atime; /* Access time */
__le32 i_ctime; /* Inode Change time */
__le32 i_mtime; /* Modification time */
__le32 i_dtime; /* Deletion Time */
__le16 i_gid; /* Low 16 bits of Group Id */
__le16 i_links_count; /* Links count */
__le32 i_blocks_lo; /* Blocks count */
__le32 i_flags; /* File flags */
......
__le32 i_block[EXT4_N_BLOCKS];/* Pointers to blocks */
__le32 i_generation; /* File version (for NFS) */
__le32 i_file_acl_lo; /* File ACL */
__le32 i_size_high;
......
};
-
基本信息
文件的数据存放得太散,需要设立一个索引区域,用来维护“某个文件分成几块、每一块在哪里”等等这些基本信息 -
元数据部分
例如名字、权限等 -
文件的读写权限 i_mode,属于哪个用户 i_uid,哪个组 i_gid,大小是多少 i_size_io,占用多少个块 i_blocks_io
-
维护三个文件相关的时间: i_atime 访问时间; i_ctime 更改 inode 时间; i_mtime 更改文件时间
-
文件分为多个块, 每个块的位置存放在 inode 的 i_block 中
i_block 如何通过块存储文件
i_block 结构,在 inode 数据结构中
__le32 i_block[EXT4_N_BLOCKS];/* Pointers to blocks */
EXT4_N_BLOCKS 定义
\linux-4.13.16\fs\ext4\ext4.h
#define EXT4_NDIR_BLOCKS 12
#define EXT4_IND_BLOCK EXT4_NDIR_BLOCKS
#define EXT4_DIND_BLOCK (EXT4_IND_BLOCK + 1)
#define EXT4_TIND_BLOCK (EXT4_DIND_BLOCK + 1)
#define EXT4_N_BLOCKS (EXT4_TIND_BLOCK + 1)
ext2 和 ext3 如何存储
- 1、前 12 项直接保存了块的位置
通过 i_block[0-11],直接得到保存文件内容的块 - 2、若文件较大, 则第十三项i_block[12]指向间接块
间接块里面放数据块的位置,通过间接块可以找到数据块 - 3、如果文件再大一些,i_block[13]会指向一个块,可以用二次间接块
二次间接块里面存放了间接块的位置,间接块里面存放了数据块的位置,数据块里面存放的是真正的数据 - 4、如果文件再大一些,i_block[14]会指向三次间接块。原理和上面一样
图片来自极客时间趣谈linux操作系统
- 问题?
对于大文件来讲,要多次读取硬盘才能找到相应的块,访问速度就会比较慢
ext4 存储改进
ext4 做了一定的改变。它引入了一个新的概念,叫做 Extents
Extents 树形结构, 每个节点由一个头 ext4_extend_header 来描述节点
图片来自极客时间趣谈linux操作系统
ext4_extent_header 结构体
\linux-4.13.16\fs\ext4\ext4_extents.h
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 */
};
eh_entries 表示这个节点里面有多少项,分两种,两种类型的项的大小都是 12 个 byte
- 叶子节点
直接指向硬盘上的连续块的地址,称为数据节点 ext4_extent - 分支节点
指向下一层的分支节点或者叶子节点,称为索引节点 ext4_extent_idx
\linux-4.13.16\fs\ext4\ext4_extents.h
/*
* This is the extent on-disk structure.
* It's used at the bottom of the tree.
*/
struct ext4_extent {
__le32 ee_block; /* first logical block extent covers */
__le16 ee_len; /* number of blocks covered by extent */
__le16 ee_start_hi; /* high 16 bits of physical block */
__le32 ee_start_lo; /* low 32 bits of physical block */
};
/*
* This is index on-disk structure.
* It's used at all the levels except the bottom.
*/
struct ext4_extent_idx {
__le32 ei_block; /* index covers logical blocks from 'block' */
__le32 ei_leaf_lo; /* pointer to the physical block of the next *
* level. leaf or next index could be there */
__le16 ei_leaf_hi; /* high 16 bits of physical block */
__u16 ei_unused;
};
文件不大: inode 可放下一个头 + 4 个数据项, eh_depth = 0 表示数据节点
文件较大: 4 个 extent 放不下,就要分裂成为一棵树,eh_depth>0 的节点就是索引节点,其中根节点深度最大 , 除了根节点其他节点都存于一个块中, 4K 能存 340 项, 每项可放 128MB, 总 42.5GB
inode 位图和块位图
专门弄了一个块来保存 inode 的位图,在这个块的 4k 里面,每一位对应一个 inode,如果是1,表示这个 inode 已经被用了;如果是0,则表示没被用。同样的专门用一个块保存 block 的位图
文件系统的格式
块组
数据块的位图是放在一个块里面的,共 4k,每位表示一个数据块,共可以表示 4∗1024∗8=215 个数据块,如果每个数据块按默认的 4K,最大可以表示空间为 215 ∗4∗1024= 227 个 byte,也就是 128M
如果采用,“一个块的位图 + 一系列的块”,外加 “一个块的 inode 的位图 + 一系列的 inode 的结构”,最多能够表示 128M ,这个结构称为一个块组
块组用数据结构 ext4_group_desc 表示,包含 inode 位图 bg_inode_bitmap_lo , block 位图 bg_block_bitmap_lo 和 inode 列表 bg_inode_table_lo
块组描述符表
一个个块组,就基本构成了整个文件系统的结构,多个块组描述符同样组成一个列表,称为块组描述符表
超级块
还需要有一个数据结构,对整个文件系统的情况进行描述,这个就是超级块ext4_super_block。
包含信息
- 整个文件系统一共有多少 inode,s_inodes_count
- 一共有多少块,s_blocks_count_lo
- 每个块组有多少 inode,s_inodes_per_group
- 每个块组有多少块,s_blocks_per_group 等
对于整个文件系统,第一个块组前 1k 用于启动引导
整个文件系统格式 如下
图片来自极客时间趣谈linux操作系统
超级块和块组描述符表都是全局信息,而且这些数据很重要都需要备份
备份策略
默认情况
每一个块组里面都保存超级块和块组描述符表的副本
sparse_super 特性
超级块和块组描述符表的副本只会保存在块组索引为 0、3、5、7 的整数幂里
这有个问题,超级块不是很大备份多了也没有太多问题;但是对于块组描述符表备份太多,一方面很浪费空间,另一个方面由于一个块组最大 128M,而块组描述符表里面有多少项,限制了有多少个块组从而限制了整个文件系统的大小
Meta Block Groups 特性
将块组分成多个组(元块组) ,块组描述符表只保存当前元块组中块组的信息, 并在元块组内备份。避免块组表浪费空间, 或限制文件系统的大小。
每个元块组里面的块组描述符表仅仅包括自己的,一个元块组包含 64 个块组,这样一个元块组中的块组描述符表最多 64 项
图片来自极客时间趣谈linux操作系统
根据图中,每一个元块组包含 64 个块组,块组描述符表也是 64 项,备份三份,在元块组的第一个,第二个和最后一个块组的开始处。
目录存储格式
目录也是文件, 也有 inode, inode 里面也是指向一些块, 块中保存目录里各个文件信息,这些信息称为 ext4_dir_entry。普通文件的块里面保存的是文件数据。
ext4_dir_entry 结构
struct ext4_dir_entry {
__le32 inode; /* Inode number */
__le16 rec_len; /* Directory entry length */
__le16 name_len; /* Name length */
char name[EXT4_NAME_LEN]; /* File name */
};
struct ext4_dir_entry_2 {
__le32 inode; /* Inode number */
__le16 rec_len; /* Directory entry length */
__u8 name_len; /* Name length */
__u8 file_type;
char name[EXT4_NAME_LEN]; /* File name */
};
有两个版本: ext4_dir_entry 有一个 一个 16 位的 name_len;ext4_dir_entry_2 一个 8 位的 name_len 和 8 位的 file_type
列表的模式保存
在目录文件的块中,最简单的保存格式是列表
每一项都会保存这个目录的下一级的文件的文件名和对应的 inode
第一项 “.” 当前目录; 第二项 “…” 上一级目录
索引的模式保存
目录下面的文件太多的时候,一个个去找,太慢了,添加了索引的模式
inode 中设置 EXT4_INDEX_FL 标志,目录文件的块的组织形式将发生变化
struct dx_root
{
struct fake_dirent dot;
char dot_name[4];
struct fake_dirent dotdot;
char dotdot_name[4];
struct dx_root_info
{
__le32 reserved_zero;
u8 hash_version;
u8 info_length; /* 8 */
u8 indirect_levels;
u8 unused_flags;
}
info;
struct dx_entry entries[0];
};
其中 索引项 dx_entry
struct dx_entry
{
__le32 hash;
__le32 block;
};
其实就是文件名的哈希值和数据块的一个映射关系
图片来自极客时间趣谈linux操作系统
软链接和硬链接的存储格式
链接即文件的别名: ln -s 创建软链接; ln 创建硬链接
图片来自极客时间趣谈linux操作系统
硬链接与原始文件共用一个 inode, 但不能跨文件系统
软链接是一个文件, 有自己的 inode, 该文件内容指向另一个文件, 可跨文件系统
inode 和数据块在文件系统上的关联关系
无论是文件夹还是文件,都有一个 inode。inode 里面会指向数据块,对于文件夹的数据块,里面是一个表,是下一层的文件名和 inode 的对应关系,文件的数据块里面存放的才是真正的数据。
图片来自极客时间趣谈linux操作系统
参考资料:
趣谈Linux操作系统(极客时间)链接:
http://gk.link/a/10iXZ
欢迎大家来一起交流学习