最近完成了一个编程作业,大致功能是给定一个文件名,给出该文件所在目录和其本身所占用的簇号等信息。笔者选用了Linux的ext系列文件系统作为实验对象,通过实验对ext2文件系统的存储和索引有了一个较为细致的了解,在这里记录学习到的知识,以备查阅和参考。
(这里记录的是笔者作业过程中的收获,故而主要细节集中在Linux ext2文件系统的存储布局和如何进行文件数据读取上,其他诸如访问控制等信息则略过)
(本文的参考来源,更详细的ext2 文件系统的细节文本,包括结构的成员和功能的描述:The Second Extended File System,Internal Layout,这也是帮助笔者完成作业的主要文件,在本文中没有详细介绍的结构、字段等信息,在该文章中均有描述)
目录
1.概述
2.基本概念
3.文件系统概念
3.1 superblock
3.2 block
3.5 inode table
3.6 inode
3.7 directory
4. ext2文件系统布局
5. 文件系统相关操作
5.2 如何定位一个文件
5.3 文件系统的根目录与根目录 /
5.4 硬链接与符号链接
6. 参考资料
文件系统是一种对计算机数据的组织和存储的方式,它通过特定的格式对系统上的信息和数据(狭义的理解,可以认为就是目录和文件)进行管理和控制,并方便操作系统在使用时对目标资源进行索引和查找。文件系统在格式化分区(硬盘)时被创建,为操作系统提供独立于磁盘物理结构的逻辑结构。目前用户日常接触的主流的文件系统格式包括 ext系列(Linux),NTFS(Windows) 和 FAT16/32(U盘等),这里介绍的是Linux环境下的ext2文件系统。
Linux的ext系列文件系统使用superblock结构描述整个文件系统的配置数据,为操作系统使用文件系统提供基本信息。所有的数据均被视为文件进行管理,每个文件有且仅有一个inode结构进行描述,其中存放有与该文件相关的属性、数据存放的block号等控制信息,但文件名却不包含在其中。整个文件系统以block为管理的基本单位,文件的数据均存放在block中。目录被视为文件,拥有自己的inode结构,该文件的block块中存放着称为目录项的结构,每个目录项记录该目录下对应成员的文件名和对应的inode号等信息。为便于索引,将文件系统中的inode和block分成组(group)进行管理。
superblock:描述文件系统的基本结构,包含了文件系统的基本配置信息,其中包括该文件系统的inode和block数量、inode和block的固定大小、inode和block的组织方式等信息。
block:文件系统中进行数据存放的基本单位,每个文件占用整数个block,即不同文件无法将数据存放在同一个block中。block的大小和数量在文件系统格式化时即被确定,常见的block大小有1KB、2KB、4KB和8KB等。
inode: 文件系统中描述文件的基本结构,每个文件有且仅有一个inode与其对应,inode记录了一个文件除文件数据(存放在block中)和文件名(位于所在目录的数据块记录的目录项中)的所有信息,包括时间相关信息,访问控制信息,文件使用的block块信息等。
superblock包含了对一个文件系统的基本配置信息的描述,其总是自文件系统中偏移量为 1024 bytes 处的位置开始,并占据1024bytes的空间。
superblock中记录了与文件系统相关的配置信息,其中与文件访问相关的部分字段的信息如图所示:
| superblock中的偏移(offset) | 字段长度(bytes) | 字段名称 | 字段描述 |
| 0 | 4 | s_inodes_count | 该文件系统中inode节点的总个数 |
| 4 | 4 | s_blocks_count | 该文件系统中block节点的总个数 |
| 24 | 4 | s_log_block_size |
若该字段记录值为n(非负),则可通过1024<< n 得到block的大小。 如n=1,则说明一个block大小为1024bytes |
| 32 | 4 | s_blocks_per_group | 记录每个group中block的个数 |
| 40 | 4 | s_inodes_per_group | 记录每个group中inode的个数 |
| 88 | 2 | s_inode_size | 记录文件系统中inode的固定大小 |
完整的superblock结构的介绍可以参见这里。superblock的结构在ext2/3/4中基本是一致的。
操作系统在读取superblock的数据后,即获得文件系统的具体参数信息,以便进行文件系统操作。Linux系统提供命令供用户查看superblock及其相关信息。
dumpe2fs [options] 设备文件名 //输出文件系统的superblock及其块分组信息 -h //仅输出文件系统 superblock 的信息
用户可通过 df 命令获得当前挂载的文件系统的设备文件名,再通过 dumpe2fs -h 设备文件名(如/dev/sda1) 获得对应文件系统的superblock信息。
用户也可以编写程序,读取原始的二进制磁盘数据,根据上述 superblock 结构字段的偏移和含义对原始数据进行分析,从而获得 superblock 记录的信息。笔者的一个简单实现如下:
#include <unistd.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<def.h> //自定义的头文件,存放相关偏移的宏 super_block root_block; int main(int argc , char *argv[]) { int fd = open( FILE_SYSTEM ,O_RDONLY); if(!fd) { err_msg("failed to read the disk"); } getSuperBlock(fd); close(fd); return 0; } void getSuperBlock( int fd ) //get superblock information { unsigned char super_block_buffer[super_block_size]; //存放superblock的缓冲区 if( lseek( fd , offset_super_block , SEEK_SET )== -1 ) //设置文件偏移至super_block所在起始位置 err_msg("failed to used lseek in superblock reading"); if( read( fd , super_block_buffer , super_block_size )!= super_block_size ) //读取superblock数据至缓冲区 err_msg("failed to read superblock"); root_block.inode_size = parse_num( super_block_buffer + offset_s_inode_size , 2); //inode size root_block.block_size =( (unsigned int)1024 )<< parse_num(super_block_buffer + offset_s_log_block_size , 4 ); //block size root_block.num_of_inode = parse_num(super_block_buffer + offset_s_inodes_count , 4 ); //inode number root_block.num_of_block = parse_num( super_block_buffer + offset_s_blocks_count , 4); //block number printf("num of inode:%u \t inode size:%u\n", root_block.num_of_inode , root_block.inode_size ); printf("num of blocks:%u \t block_size:%u\n",root_block.num_of_block , root_block.block_size ); root_block.inode_per_grp = parse_num( super_block_buffer + offset_s_inodes_per_group , 4 ); //inode_per_group root_block.block_per_grp = parse_num( super_block_buffer + offset_s_blocks_per_group , 4 ); //block_per_group printf("num of inodes per group:%u\n", root_block.inode_per_grp ); printf("num of blocks per group:%u\n", root_block.block_per_grp ); printf("\n\n"); } void err_msg( char *msg) // print error message and exit { fprintf( stderr , "%s\n" , msg ); exit(0); } unsigned int parse_num( unsigned char *temp , int length ) //解释小端法存放的数据 { int i; unsigned int count = 0; for( i = 0 ; i < length ; i++ ) { unsigned int k = temp[i]; k = k << ( i * 8 ); count = count + k ; } return count; }