本章知识点如下:
EXT2文件系统的历史与现状
EXT2文件数据结构
各个级别文件系统函数算法
融合所有级别于一个项目中
EXT2文件系统历史
多年来,Linux一直使用EXT2(Card等1995)作为默认文件系统。EXT3(EXT3,2014)是EXT2的扩展。EXT3中增加的主要内容是一个日志文件,它将文件系统的变更记录在日志中。日志可在文件系统崩溃时更快地从错误中恢复。没有错误的EXT3文件系统与EXT2文件系统相同。EXT3的最新扩展是EXT4(Cao等2007)。EXT4的主要变化是磁盘块的分配。在EXT4中,块编号为48位。EXT4不是分配不连续的磁盘块,而是分配连续的磁盘块区,称为区段。除了这些细微的更改之外,文件系统结构和文件操作保持不变。
EXT2文件系统数据结构
1. 通过 mkfs 创建虚拟磁盘
在Linux下,命令
mke2fs [-b blkesize -N ninodes] device nblocks
在设备上创建一个带有nblocks个块(每个块大小为blksize字节)和ninodes个索引节点的EXT2文件系统。设备可以是真实设备,也可以是虚拟磁盘文件。如果未指定blksize,则默认块大小为1KB。如果未指定ninoides,mke2fs将根据
nblocks 计算一个默认的ninodes数。得到的EXT2文件系统可在Linux中使用。
2. 虚拟磁盘布局
一个磁盘可以分成多个分区,每个分区必须先用格式化工具格式化成某种格式的文件系统,才能存储文件,在格式化的过程中会在磁盘上写一些管理存储布局的信息。
- Block#0:
引导块,文件系统不会使用它。它用于容纳从磁盘引导操作系统的引导程序。 - Block#1:
超级块(在硬盘分区中字节偏移量为1024)。用于容纳关于整个文件系统的信息。
超级块中一些重要字段
struct et2_super block {
u32 s_inodes_count; /* Inodes count */
u32 s_blocks_count; /* Blocks count */
u32 s_r_blocks_count; /* Reserved blocks count */
u32 s_free blocks_count; /* Free blocks count */
u32 s_free_inodes_count; /* Free inodes count */
u32 s_first_data_block; /* First Data Block */
u32 s_log block_size; /* Block size */
u32 s_log_cluster_size; /* Al1ocation cluster size */
u32 s_blocks per_group; /* # Blocks per group * /
u32 s_clusters per_group; /* # Fragments per group */
u32 s_inodes_per_group; /* # Inodes per group * /
u32s_mtime; /* Mount time * /
u32s_wtime; /* write time */
u16s_mnt_count; /* Mount coune* /
s16 s_max_ntcount; /* Maximal mount count */
u16 B_magic; /* Magic signature */
//more non-essential fields
u16 s_inode_size; /* size of inode structure*/
}
s_first_data_block:0表示4KB块大小,1表示1KB块大小。它用于确定块组描述符的起始块,即s_first_data_block +1。
s_log_block_size确定文件块大小,为1KB*(2**s_log_block_size),例如0表示 1KB块大小,1表示2KB块大小,2表示4KB块大小,等等。最常用的块大小是用于小文件系统的1KB和用于大文件系统的4KB。
s_mnt_count:已挂载文件系统的次数。当挂载计数达到max_mount_count时,fsck会话将被迫检查文件系统的一致性。
s_magic是标识文件系统类型的幻数。EXT2/3/4文件系统的幻数是OxEF53。
- Block#2
块组描述符(硬盘上的s_first_data_blocks-1)
EXT2将磁盘块分成几个组,每个组有8192个块(硬盘上的大小为32K)
struct ext2_group_dese {
u32 bg_b1ock_bitmap; //Bmap bloak number
u32 bg_inode_bitmap; //Imap block number
u32 bg_inode_table; //Inodes begin block number
u16 bg_free_blocks_count; //THESE are OBVIOUS
u16 bg_free_inodes_count;
u16 bg_used_dirs_count;
u16 bg_pad; // ignore these
u32 bg_reserved[3];
};
由于一个软盘只有1440个块,B2只包含一个块组描述符。其余的都是0。在有大量块组的硬盘上,块组描述符可以跨越多个块。块组描述符中最重要的字段是bg_block_bitmap.bg_inode_bitmap和 bg_inode_table,它们分别指向块组的块位图、索引节点位图和索引节点起始块。对于Linux格式的EXT2文件系统,保留了块3到块7。所以,bmap=8,imap=9,inode_table= 10。
-
Block #8 块位图(Bmap)
用来表示某种项的位序列。0表示对应项处于FREE状态,1表示处于IN_USE状态。1个软盘有1440个块,但Block#0未被文件系统使用,所以对应位图只有1439个有效位,无效位视作IN_USE处理,设置为1. -
Block #9 索引节点位图(Imap)
一个索引节点就是用来代表一个文件的数据结构。EXT2文件系统是使用有限数量的索引节点创建的。各索引节点的状态用B9 中 Imap中的一个位表示。在EXT2 FS 中,前10个索引节点是预留的。所以,空EXT2FS的Imap 以10个1开头,然后是0。无效位再次设置为1。 -
Block #10 索引(开始)节点块(bg_inode_table)
每个文件都用一个128字节(EXT4中的是256字节)的独特索引节点结构体表示。
struct ext2_inode{
u16 i_mode;
// 16 bits =|tttt|ugs|rwx|rwx|rwxl
u16 i_uid;
// owner uid
u32 i_size;
// file size in bytes
u32 i_atime;// time fields in seconds
u32 i_ctime;
// since 00:00:00,1-1-1970
u32 i_mtime;
u32 i_dtime;
u16 i_gid;
// group ID
u16 i_links_count;// hard-link count
u32 i_blocks;// number of 512-byte sectors
u32 i_flags;// IGNORE
u32 i_reserved1;
// IGNORE
u32 i_block[15];// See details below
u32 i_pad[7];
// for inode size = 128 bytes
}
在索引节点结构体中,i_mode 为ul6或2字节无符号整数。
|4
|3 |9 |
i_mod -|tttt|ugs|zvwkzvwxrws|
在i mode 字段中,前4位指定了文件类型,例如∶tt=1000表示REG文件,0100表示 DIR文件等。接下来的3位ugs表示文件的特殊用法。最后9位是用于文件保护的rwx 权限位。
i_size字段表示文件大小(以字节为单位)。各时间字段表示自1970年1月1日0时0分0秒以来经过的秒数。所以,每个时间字段都是一个非常大的无符号整数。可借助以下库函数将它们转换为日历形式;
char
*ctime(&time_field)
将指针指向时间字段,然后返回一个日历形式的字符串。
例如:printf("%s",ctime(&inode.i_atime);// note∶ pass & of time field prints i_atime in calendar form.
i_block[15]数组包含指向文件磁盘块的指针,这些磁盘块有∶
- 直接块∶iblock[0] 至i block[11],指向直接磁盘块。
- 2. 间接块∶i block[12]指向一个包含256个块编号(对于1KB BLKSIZE)的磁盘块,每个块编号指向一个磁盘块。
- 3. 双重间接块∶i block[13]指向一个指向256个块的块,每个块指向 256个磁盘块。
- 三重间接块∶i_block[14]是三重间接块。对于"小型"EXT2文件系统,可以忽略它。索引节点大小(128或256)用于平均分割块大小(1KB或4KB),所以,每个索引节点块都包含整数个索引节点。在简单的EXT2文件系统中,索引节点的数量是184个(Linux默认值)。索引节点块数等于184/8=23个。因此,索引节点块为B10至B32。每个索引节点都有一个唯一的索引节点编号,即索引节点在索引节点块上的位置+1。注意,索引节点位置从0开始计数,而索引节点编号从1开始计数。0索引节点编号表示没有索引节点。根目录的索引节点编号为2。同样,磁盘块编号也从1开始计数,因为文件系统从未使用块0。块编号0表示没有磁盘块。
- 数据块
紧跟在索引节点块后面的是文件存储数据块。假设有184个索引节点,第一个实际数据块是B33,它就是根目录/的i_block[0]。
根据不同文件类型有一下几种情况:
(1)对于常规文件,文件的数据存储在数据块中。
(2)对于目录,该目录下的所有文件名和目录名存储在数据块中,除文件名之外,ls -l命令中看到的信息则存储在inode中;目录也是一种文件,是一种特殊类型的文件。
(3)对于符号链接,如果目标路径名较短则直接保存在inode中以便更快的查找,如果目标路径名较长则分配一个数据块来保存。
(4)设备文件,FIFO和socket等特殊文件没有数据块,设备文件的主设备号和次设备号保存在inode中。
数据块寻址
邮差算法
例:一个城市有M个街区,编号从0到M-1。每个街区有N座房子,编号从0到N-1。每座房子有一个唯一的街区地址,用(街区,房子)表示,其中0≤街区<M,0≤房子<N。外人不熟悉该街区寻址方案,采用线性方法将房子地址编为0,1,…,N-1,N,N+1等。已知某个街区地址BA=(街区,房子),如何转换为线性地址LA,相反,已知线性地址,如何转换为街区地址?
LA=N*block + house;
BA=(LA/N, LA%N); (只有从0开始计数转换才有效)
编历EXT2文件系统树
编历一个EXT2文件系统和一个文件的路径名,例如/a/b/c,问题是如何找到这个文件。查找文件相当于查找其索引节点。
1编历算法
(1)读取超级块;
(2)读取块组描述符块(1+s_first_data_block),以访问组0描述符;
(3)读取InodeBeginBlock,获取/的索引节点,即INODE#2;
(4)将路径名标记为组件字符串,假设组件数量为n;
(5)从(3)中的根索引节点开始,在其数据块中搜索name[0];
(6)使用索引节点号ino来定位相应的索引节点。Ino从1开始计数,使用邮差算法计算包含索引节点的磁盘块及其在该块中的偏移量;
(7)由于(5)~(6)步将会重复n次,所以最好编写一个搜索函数。
2将路径名转换为索引节点
已知一个包含EXT2文件系统和路径名的设备,例如/a/b/c/d,编写一个C函数:INODE *path2inode(int fd, char *pathname);返回一个指向文件索引节点的INODE指针;如果文件不可访问,则返回0。
3显示索引节点磁盘块
编写一个C程序showblock,可打印文件的所有磁盘块(编号);
问题与解决思路:
书上对于EXT2文件系统的数据结构的讲解只有较为简单的讲述,对于实际操作方式还是有些不清楚
参考这篇博文:https://www.cnblogs.com/ant-colonies/p/11005511.html
1 超级块(Super Blook)
超级块的数据结构如下:
主要的几项描述有:s_inodes_count / s_blocks_count(该分区inode/block总数),s_free_inodes_count / s_free_blocks_count(该分区空闲inode/block数),s_first_data_block(数据块中的起始块位置),s_log_block_size(块大小),s_blocks_per_group(每个块组的块数),s_magic(魔数)
struct ext2_super_block {
__u32 s_inodes_count; /* Inodes count */
__u32 s_blocks_count; /* Blocks count */
...
__u32 s_free_blocks_count; /* Free blocks count */
__u32 s_free_inodes_count; /* Free inodes count */
__u32 s_first_data_block; /* First Data Block */
__u32 s_log_block_size; /* Block size */
...
__u32 s_blocks_per_group; /* # Blocks per group */
...
__u16 s_magic; /* Magic signature */ # ext2的魔数为0xEF53
...
说明:每个块组中都有一份超级块的拷贝。当文件系统挂载时,通常只有块组0中的超级块(主备份)会被读取,其他的块组中的超级块只是作为备份,以防文件系统的崩溃。
2 组描述符表(Group Descriptors Table,GDT)
超级块之后就是组描述符表,是由该分区所有的块的组描述符(Group Descriptor)组成的,每个块组描述符记录了本块组的inode/block bitmap和inode table等。块组描述符数据结构如下所示:
struct ext2_group_desc { __u32 bg_block_bitmap; /* Blocks bitmap block */ __u32 bg_inode_bitmap; /* Inodes bitmap block */ __u32 bg_inode_table; /* Inodes table block */ __u16 bg_free_blocks_count; /* Free blocks count */ __u16 bg_free_inodes_count; /* Free inodes count */ __u16 bg_used_dirs_count; /* Directories count */ __u16 bg_pad; __u32 bg_reserved[3]; };
说明:与超级类似,组描述符表也存在各块组中紧接着超级块,其目的与超级块一样,作为备份,防止文件系统的崩溃。
3 块位图和inode位图(block/inode bitmap)
位图(bitmap)是位(bit)的序列,每一个位代表该位图所在块组中的一个特定的数据块(block bitmap)或inode table中一个特定的inode(inode bitmap)。当bit为0时,表示对应的block/inode空闲;为1时,表示已被占用。
位图始终索引其所在的块组,并且block位图和inode位图均为1block大小,从而限制了该块组的大小。例如一般的block大小为1024bytes,因此一个块组总共有1024*8个block。
4 inode表(inode table)
inode表由一系列连续的block块组成,每个块中都预定义了一定数量的inode。inode表的起始块位置(块号)存储在组描述符的bg_inode_table字段中。
系统对inode表中的inode进行编号,从1开始,inode的数据结构被定义在ext2_fs.h文件的
struct ext2_inode
函数中:
以下是inode数据结构中比较重要的字段:
struct ext2_inode {
__u16 i_mode; /* File type and access rights */
__u16 i_uid; /* Low 16 bits of Owner Uid */
__u32 i_size; /* Size in bytes */
__u32 i_atime; /* Access time */
__u32 i_ctime; /* Creation time */
__u32 i_mtime; /* Modification time */
__u32 i_dtime; /* Deletion Time */
__u16 i_gid; /* Low 16 bits of Group Id */
__u16 i_links_count; /* Links count */
__u32 i_blocks; /* Blocks count */
__u32 i_flags; /* File flags */
...
__u32 i_block[EXT2_N_BLOCKS]; /* Pointers to blocks */
...
};
i_mode字段中包含了文件的类型和文件的访问权限,被定义在宏文件macro (sys/stat.h
)中。
Sign | Type | Macro |
- | Regular file | S_ISREG(m) |
d | Directory | S_ISDIR(m) |
c | Character Device | S_ISCHR(m) |
b | Block Device | S_ISBLK(m) |
f | Fifo | S_ISIFO(m) |
s | Socket | S_ISSOCK(m) |
l | Symbolic Link | S_ISLNK(m) |
Domain | Read | Write | Exec | All |
User | S_IRUSR | S_IWUSR | S_IXUSR | S_IRWXU |
Group | S_IRGRP | S_IWGRP | S_IXGRP | S_IRWXG |
All | S_IROTH | S_IWOTH | S_IXOTH | S_IRWXO |
i_blocks是该inode指向的文件已使用的block数量;/*
175 * Constants relative to the data blocks
176 */
177 #define EXT2_NDIR_BLOCKS 12
178 #define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS
179 #define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)
180 #define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)
181 #define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)
在i_block[]
数组中有15个指针,它们所代表的含义如下:
-
i_block[0..11]
point directly to the first 12 data blocks of the file. # 序列号0-11的12个元素(指针)指向文件开头的12个数据块 -
i_block[12]
points to a single indirect block # 第13号元素指向单索引间接块 -
i_block[13]
points to a double indirect block # 第14号元素指向双索引间接块 -
i_block[14]
points to a triple indirect block # 第15号元素指向三索引间接块
由此我们可以计算出ext2文件系统单个文件的最大容量(假设block大小为1K),数组i_block[]的长度为32bit/8=4byte:
- 直接索引:12 指针
- 单间接索引:1024/4=256个直接索引,256 指针
- 双间接索引:1024/4=256个单间接索引,256*256 指针
- 三间接索引:1024/4=256个双间接索引,256*256*256 指针
因此可得到12K+256K+64M+16G,即大致为16G。如果block的大小为4K,则文件最大可为4T。(注意:真正决定文件大小的是底层的寄存器,寄存器的位数决定了其寻址的能力)
5 inode表中的inode指向的目录文件
inode指向的目录文件需加以注意,我们可以通过测试S_ISDIR(mode) macro来加以识别:
if (S_ISDIR(inode.i_mode)) ...
假设inode指向的块是目录实体/home,目录中的内容包含了一系列的文件名和指向inode表中的对应的,如下图:
目录文件的数据结构如下:
struct ext2_dir_entry_2 { __u32 inode; /* Inode number */ __u16 rec_len; /* Directory entry length */ __u8 name_len; /* Name length */ __u8 file_type; char name[EXT2_NAME_LEN]; /* File name */ };
字段file_type总共有0-7可能的值,分别代表:
目录内容中的各项(实体)的大小是非固定的,大小取决于文件名称的长度。文件名称最大长度为EXT2_NAME_LEN的值,一般为255;文件名称的实际长度存放于字段name_len;rec_len存储的是本目录实体的大小,该字段自然也就决定了下一目录实体的位置了。
Example of EXT2 directory
注意:目录中的inode号,指向的是inode表中的inode,指向data block的是i_block[EXT2_N_BLOCKS]的数组中的指针。
实践内容与截图:
1、查看文件操作系统
以上示例效果中还有一个叫做buffers memory的内存区块,这个区块缓存了文件系统的部分Inode信息,这样保证了操作系统不会随时到文件系统上寻找Inode——优化文件系统的读性能。cache memory区块和buffers memory区块由操作系统自行管理,它们只会使用当前没有被应用程序占用的空闲内存。当应用程序请求新的内存区块且空闲内存不够时,操作系统会释放部分cache memory区块或者buffers memory区块。
2、df、du命令学习
df(英文全拼:disk free)用于显示目前在Linux系统上的文件系统磁盘使用情况统计。
第二列指定一个特定的文件系统1K-块1K是1024字节为单位的总内存。用和可用列正在使用中,分别指定的内存量。
使用列指定使用的内存的百分比,而最后一栏"安装在"指定的文件系统的挂载点。
用一个-i选项的df命令的输出显示inode信息而非块使用量