学习笔记5
第十一章 EXT2文件系统
知识点归纳
(一)EXT2文件系统数据结构
- 通过mkfs创建虚拟磁盘
mke2fs [-b blksize -N ninodes] device nblocks
在设备上创建一个带有nblocks个块(每个块大小为blksize字节)和ninodes个索引节点的EXT2文件系统。设备可以是真实设备,也可以是虚拟磁盘文件。如果未指定blksize,则默认块大小为1KB,如果未指定ninoides, mke2fs将根据nblocks计算一个默认的ninodes数。得到的EXT2文件系统可在Linux中使用。举个具体的例子,下面的命令
dd if=/dev/zero of=vdisk bs«1024 count«1440
mke2fs vdiek 1440
可在一个名为vdisk的虚拟磁盘文件上创建一个EXT2文件系统,有1440个大小为1KB的块。
- 虚拟磁盘布局
一开始,我们先使用这个基本文件系统布局进行假设。在适当的时候,我们会指出一些变化,包括硬盘上大型EXT2/3文件系统中的变化。下面来简要解释一下磁盘块的内容。
Block#0:引导块B0是引导块,文件系统不会使用它。它用来容纳一个引导程序,从磁盘引导操作系统。 - 超级块
Block#1:超级块(在硬盘分区中字节偏移量为1024)B1是超级块,用于容纳整个文件系统的信息。下文说明了超级块结构中的一些重要字段。
struct ext2_auper_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; /*Allocation 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 */
u32 s_mtime; /*Mount time */
u32 s_wtime; /*Write time */
U16 s_jnnt_count; /*Mount count */
S16 s_max_mnt_count; /*Maximal mount count */
ul6 s_magic; /*Magic signature */
// more non-essential fields
ul6 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_mnt_count时,fsck会话将被迫检查文件系统的一致性。
s_magic:标识文件系统类型的幻数。EXT2/3/4文件系统的幻数是0xEF53。
- 快组描述符
Block#2:块组描述符块(硬盘上的s_first_data_block+1) EXT2将磁盘块分成几个组。 每个组有8192个块(硬盘上的大小为32K)。每组用一个块组描述符结构体来描述。
struct ext2_group_desc{
u32 bg_block_bitmap; //Bmap block number
u32 bg_inode_bicmap; //Imap block number
u32 bg_inode_table; //Inodes begin block number
ul6 bg_free_blocks_count; //THESE are OBVIOUS
U16 bg_free_inodes_count;
U16 bg_used_dirs_count;
ul6 bg_pad; //ignore these
u32 bg_reserved(3);
};
由于一个虚拟软盘(FD)只有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)(bg_block_bitmap)位图是用来表示某种项的位序列,例如磁盘块或索引节点。位图用于分配和回收项。在位图中,0位表示对应项处于FREE状态,1位表示对应项处于IN_USE状态。一个软盘有1440个块,但是Block#。未被文件系统使用。所以,位图只有1439个有效位。无效位被视作IN_USE,设置为1。
Block#9:索引节点位图(Imap)(bg_inode_bitmap)一个索引节点就是用来代表一个文件的数据结构。EXT2文件系统是使用有限数量的索引节点创建的。各索引节点的状态用B9的Imap中的一个位表示。在EXT2 FS中,前10个索引节点是预留的。所以,空EXT2 FS的Imap以10个1开头,然后是0。无效位再次设置为1。 - 索引节点
Block#10:索引(开始)节点(bg_inode_table)每个文件都用一个128字节(EXT4中是256字节)的唯一索引节点结构体表示。下面列出了主要索引节点字段。
struct ext2_inode {
ul6 i_mode; //16 bits = |tttt|ugs|rwx|rwx|rwx|
ul6 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为U16或2字节无符号整数。
在i_mode字段中,前4位指定了文件类型,例如:tttt=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[l5]数组包含指向文件磁盘块的指针,这些磁盘块有:
- 直接块:i_block[0] Mi_block[ll],指向直接磁盘块。
- 间接块:i_block[12]指向一个包含256个块编号(对于1KB BLKSIZE)的磁盘块,每个块编号指向一个磁盘块。
- 双重间接块: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]。 - 目录条目
目录包含dir_entry结构,即
struct ext2_dir_entry_2{
U32 inode; //inode number; count from 1; NOT 0
U16 rec_len; //this entry's length in bytes
U8 name_len; //name length in bytes
u8 file_type; //not used
char name[EXT2_NAME_LEN]; //name:1-255 chars, no ending NULL
dir_entry是一种可扩充结构。名称字段包含1到255个字符,不含终止NULL。所以 dir_entry的rec_len也各不相同。
(二)邮差算法
邮差算法的意思是:一个城市有 M 个街区,编号从 0 到 M-1 ,每个街区又有 N 座房子,编号从 0 到 N-1。我们可以用 BA =(街区,房子)表示每一栋房子的位置,同时我们也可以使用线性方法将房子编为 0,1,...,N 等。如何进行线性地址 LA 与街区地址 BA 的相互转换?如果都从 0 开始计数,转换就会非常简单。
Linear_address LA = N*block + house;
Block_address BA = (LA / N, LA % N);
注意,只有都从 0 开始计数,转换才有效。如果有些条目不是从 0 开始计数的,则不能直接在转换公式中使用。为方便表述,我们将这种转换方法称为邮差算法。下面给出邮差算法的几种应用。
- C语言中的 Test-Set-Clear 位
在标准C语言程序中,最小的可寻址单元是一个字符或字节。在一系列位组成的位图中,通常需要对位进行操作。考虑字符buf[1024],它有1024个字节,用buf[i]表示,其中i=0,1,...,1023。它还有8192个位,编号为0,1,2,···,8191。
struct bits{
unsigned int bit0 : 1; // bit0 field is a single bit
unsigned int bit123 : 3; // bit123 field is a range of 3 bits
unsigned int otherbits :27; // other bits field has 27 bits
unsigned int bit31 : 1; // bit31 is the highest bit
}var;
- 将索引节点号转换为磁盘上的索引节点
在 EXT2文件系统中,每个文件都有一个唯一的索引节点结构。在文件系统磁盘上,索引节点从inode table块开始。每个磁盘块包含
INODES_PER_BLOCK = BLoCK_SIZE/sizeof(INODE)
个索引节点。每个索引节点都有一个唯一的索引节点号,ino=1,2,…,从1开始线性计数。已知一个ino,如1234,那么哪个磁盘块包含该索引节点,以及哪个索引节点在该块中呢?我们需要知道磁盘块号,因为需要通过块来读/写一个真正的磁盘。
blcok = (info - 1) * INODES_PER_BLCOK + inode_table;
inode = (info - 1) % INODES_PER_BLCOK
(三)遍历 EXT2 文件系统树
- 遍历算法
- 读取超级块。检查幻数 s_magic(0xEF53),验证它确实是 EXT2 FS。
- 读取块组描述符块(1 + s_first_data_block),以访问组0描述符。从块组描述符的bg_inode_table条目中找到索引节点的起始块编号,并将其称为 InodesBeginBlock。
- 读取 InodeBeginBlock,获取/的索引节点,即 INODE#2。
- 将路径名标记为组件字符串,假设组件数量为n。例如,如果路径名 =/a/b/c,则组件字符串是"a""b""c",其中n=3。用name[0],name[1],…,name[n-1]来表示组件。
- 从根索引节点开始,在其数据块中搜索 name[0]。为简单起见,我们可以假设某个目录中的条目数量很少,因此一个目录索引节点只有12个直接数据块。有了这个假设,就可以在12个(非零)直接块中搜索 name[0]。目录索引节点的每个数据块都包含以下形式的 dir_entry 结构体;
[ino rec_len name_len NAME] [ino rec_len name_len NAME]....
其中 NAME 是一系列 nlen 字符,不含终止 NULL。对于每个数据块,将该块读入内存并使用 dir_entry *dp 指向加载的数据块。然后使用 name_len将 NAME 提取为字符串,并与 name[0] 进行比较。如果它们不匹配,则通过以下代码转到下一个 dir_entry:
dp =(dir_entry*)((char *)dp + dp->rec_len);
继续搜索。如果存在 name[0],则可以找到它的 dir_entry,从而找到它的索引节点号。
* 使用索引节点号ino来定位相应的索引节点。回想前面的内容,ino 从1开始计数。使用邮差算法计算包含索引节点的磁盘块及其在该块中的偏移量。
blk =(ino - 1) / INODE8_PER_BLOCK + InodesBeginBlock;
offset = (ino - 1) % INODES_PER_BLOCK;
然后在索引节点中读取/a,从中确定它是否是一个目录(DIR)。如果/a不是目录,则不能有/a/b,因此搜索失败。如果它是目录,并且有更多需要搜索的组件,那么继续搜索下一个组件 name[1]。
u32 search (INODE *inodePtr, char *name)
{
// search for name in the data blocks of current DIR inode
// if found, return its ino; else return 0 )
}
然后我们只需调用 search()n次,如下所示。
Assume:n,name[0],....,name[n-1] are globals
INODE *ip points at INODE of /
for(i=0; i<n; i++)
{
ino = search(ip, name[4])
if(!ino){ // can't find name[i], exit;}
use ino to zead in INODE and let ip point to INODE
}
如果搜索循环成功结束,ip必须指向路径名的索引节点。遍历有多个组的大型 EXT2/3 文件系统也是类似操作。
(四)EXT2 文件系统的实现
- 文件系统的结构
- 当前运行进程的PROC结构体;
- 文件系统的根指针;
- 一个openTable条目;
- 内存索引节点;
- 已挂载的文件系统表。
- 文件系统的级别
第1级别实现了基本文件系统树,以实现指定函数,第2级别实现了文件内容的读/写函数,第3级别实现了文件系统的挂载、卸载和文件保护。
(五)基本文件系统
-
type.h 文件
这类文件包含EXT2文件系统的数据结构类型,比如超块、组描述符、索引节点和目录条目结构。此外,它还包含打开文件表、挂载表、PROC结构体和文件系统常数。 -
global.c 文件
这类文件包含文件系统的全局变量。全局变量的例子有:
MINODE minode [NMINODE]; // in memory INODEs
MTABLE mtable [NMTABLE]; // mount tables
OFT oft [NOFT]; // Opened file instance
PROC proc[NPROC]PROC ] // PROC structures
PROC *running; // current executing
- 实用程序函数
- get_block/put_block 函数
- iget(dev, ino) 函数
- The iput(INODE *mip) 函数
- getino() 函数
- getino()/iget()/iput()的使用
(七)文件系统的级别
文件系统的实现分为三个级别。每个级别处理文件系统的不同部分。这使得实现过程模块化,更容易理解。在文件系统的实现过程中,FS目录包含实现EXT2文件系统的文件。
- 1级文件系统函数及其原理
1级文件系统函数的功能是文件和路径、创建和删除等较为直接和底层的相关的操作。
mkdir:mkdir创建一个带路径名的新目录。同时将目录的权限设置为 0755 ,即 rwxr_xr_x ,所有人可以访问和读写,其他人可以访问但是只能读取。
creat:creat创建一个空的普通文件。
rmdir:删除一个路径。
link:链接文件。
unlink:解除链接文件。 - 2级文件系统函数及其原理
2级文件系统函数的功能常常是打开、读取文件、写入文件等对文件进行的操作。
open:打开文件。
lseek:将打开的文件描述符在OFT中的偏移量从文件开头或当前位置开始的字节位置。
close:关闭文件描述符。 - 3级文件系统函数及其原理
3级文件系统函数进行文件系统的挂载、卸载和文件保护等。
实践与截图
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
2020-10-02 20201226 马瑞婕 第一周学习总结
2020-10-02 自我介绍