信息安全系统设计与实现:第十一章学习笔记
20191331 lyx
教材学习内容总结
第十一章 EXT2文件系统
学习目标
充分理解一个文件系统,掌握EXT2文件系统的数据结构,通过简单编程实现EXT2文件系统的功能,了解如何实现支持linux内核文件的操作的EXT2文件系统,了解文件系统的三个级别。
什么是EXT2文件系统
EXT2第二代扩展文件系统second extended filesystem,缩写为ext2,是LINUX内核所用的文件系统。它开始由Rémy Card设计,用以代替ext,于1993年1月加入linux核心支持之中。ext2 的经典实现为LINUX内核中的ext2fs文件系统驱动,最大可支持2TB的文件系统,至linux核心2.6版时,扩展到可支持32TB。其他的实现包括GNU Hurd,Mac OS X ,Darwin ,BSD。ext2为数个LINUX发行版的默认文件系统,如Debian、Red Hat Linux等。
其单一文件大小与文件系统本身的容量上限与文件系统本身的簇大小有关,在一般常见的 x86 电脑系统中,簇最大为 4KB, 则单一文件大小上限为 2048GB, 而文件系统的容量上限为 16384GB。
但由于目前核心 2.4 所能使用的单一分割区最大只有 2048GB,实际上能使用的文件系统容量最多也只有 2048GB。
至于Ext3文件系统,它属于一种日志文件系统,是对ext2系统的扩展。它兼容ext2,并且从ext2转换成ext3并不复杂。
EXT2文件系统的结构和布局
文件系统中存储的最小单元是块(block),一个块的大小是在格式化时确定的。启动块(Boot Block)的大小为1KB,由PC标准规定,用来存储磁盘分区信息和启动信息,任何文件系统都不能修改启动块。
启动块之后才是ext2文件系统的开始,ext2文件系统将整个分区划分成若干个同样大小的块组(Block Group)。
在整体的规划当中,文件系统最前面有一个启动扇区(boot sector),这个启动扇区可以安装启动管理程序, 这是个非常重要的设计,因为如此一来我们就能够将不同的启动管理程序安装到个别的文件系统最前端,而不用覆盖整颗硬盘唯一的 MBR 。
每个块组的组成:
-
超级块(Super Block)描述整个分区的文件系统信息,如inode/block的大小、总量、使用量、剩余量,以及文件系统的格式与相关信息。超级块在每个块组的开头都有一份拷贝(第一个块组必须有,后面的块组可以没有)。 为了保证文件系统在磁盘部分扇区出现物理问题的情况下还能正常工作,就必须保证文件系统的super block信息在这种情况下也能正常访问。所以一个文件系统的super block会在多个block group中进行备份,这些super block区域的数据保持一致。
-
超级块记录的信息有:
-
1、block 与 inode 的总量(分区内所有Block Group的block和inode总量);
-
2、未使用与已使用的 inode / block 数量;
-
3、block 与 inode 的大小 (block 为 1, 2, 4K,inode 为 128 bytes);
-
4、filesystem 的挂载时间、最近一次写入数据的时间、最近一次检验磁盘 (fsck) 的时间等文件系统的相关信息;
-
5、一个 valid bit 数值,若此文件系统已被挂载,则 valid bit 为 0 ,若未被挂载,则 valid bit 为 1 。
-
每个区段与 superblock 的信息都可以使用 dumpe2fs 这个命令查询
- 块组描述符表(GDT,Group Descriptor Table)由很多块组描述符组成,整个分区分成多个块组就对应有多少个块组描述符。
每个块组描述符存储一个块组的描述信息,如在这个块组中从哪里开始是inode Table,从哪里开始是Data Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有一份拷贝。
-
块位图(Block Bitmap)用来描述整个块组中哪些块已用哪些块空闲。块位图本身占一个块,其中的每个bit代表本块组的一个block,这个bit为1代表该块已用,为0表示空闲可用。假设格式化时block大小为1KB,这样大小的一个块位图就可以表示1024*8个块的占用情况,因此一个块组最多可以有10248个块。
-
inode位图(inode Bitmap)和块位图类似,本身占一个块,其中每个bit表示一个inode是否空闲可用。 Inode bitmap的作用是记录block group中Inode区域的使用情况,Ext文件系统中一个block group中可以有16384个Inode,代表着这个Ext文件系统中一个block group最多可以描述16384个文件。
-
inode表(inode Table)由一个块组中的所有inode组成。一个文件除了数据需要存储之外,一些描述信息也需要存储,如文件类型,权限,文件大小,创建、修改、访问时间等,这些信息存在inode中而不是数据块中。inode表占多少个块在格式化时就要写入块组描述符中。
在Ext2/Ext3文件系统中,每个文件在磁盘上的位置都由文件系统block group中的一个Inode指针进行索引,Inode将会把具体的位置指向一些真正记录文件数据的block块,需要注意的是这些block可能和Inode同属于一个block group也可能分属于不同的block group。我们把文件系统上这些真实记录文件数据的block称为Data blocks。
- 数据块(Data Block)是用来放置文件内容数据的地方。
注意到OpenEuler操作系统内核也使用EXT2作为文件系统
其结构为
文件描述符与打开文件之间的关系
在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。
每一个文件描述符会与一个打开文件相对应,同时,不同的文件描述符也会指向同一个文件。相同的文件可以被不同的进程打开也可以在同一个进程中被多次打开。系统为每一个进程维护了一个文件描述符表,该表的值都是从0开始的,所以在不同的进程中你会看到相同的文件描述符,这种情况下相同文件描述符有可能指向同一个文件,也有可能指向不同的文件。具体情况要具体分析,要理解具体其概况如何,需要查看由内核维护的3个数据结构。
-
- 进程级的文件描述符表
-
- 系统级的打开文件描述符表
-
- 文件系统的i-node表
文件描述符、打开的文件句柄以及i-node之间的关系
例如要查找"/home/ealtieri/hello.txt"这个文件,先从inode2找到root inode,然后从i_block中找到home的ext2_dir_entry_2,然后得到home的inode编号,再递归寻找。
EXT2文件系统在OpenEuler中的实践
ext2分区划分的基本单位是block,磁盘上前1024byte是boot block,记录操作系统在分区中的位置。
之后的空间划分为多个block group(一般代表柱面组)。每个block group的第一个block为super block每个block group的super block都相同,是为了增加冗余,提高可靠性。
通过 mkfs 创建虚拟磁盘
mke2fs -b blkesize -N ninodes device nblocks
在设备上创建一个带有nblocks个块(每个块大小为blksize字节)和ninodes个索引节点的EXT2文件系统。设备可以是真实设备,也可以是虚拟磁盘文件。如果未指定blksize,则默认块大小为1KB。如果未指定ninoides,mke2fs将根据 nblocks 计算一个默认的ninodes数。
查看现有磁盘信息
使用命令dd if=/dev/zero of=20191331d bs=1024 count=1440
创建名为20191331d
的虚拟磁盘
格式化为EXT2文件系统mke2fs 20191331d 1440
EXT2文件系统的的一些数据结构
一个ext2文件系统的文件或目录包括索引结点和数据块两个部分,索引结点存放文件的属性、存取权限、修改时间以及其他的一些信息,而数据块存放文件的内容。
目录:
当我们在 Linux 下的 ext2 档案系统建立一个目录时, ext2 会分配一个 inode 与至少一块Block 给该目录。其中,inode 记录该目录的相关属性,并指向分配到的那块 Block ;而 Block则是记录在这个目录下的相关连的目录的关连性!
档案:
当我们在 Linux 下的 ext2 建立一个一般档案时, ext2 会分配至少一个 inode 与相对于该档案大小的 Block 数量给该档案。例如:假设我的一个 Block 为 4 Kbytes ,而我要建立一个 100KBytes 的档案,那么 linux 将分配一个 inode 与 25 个 Block 来储存该档案。
Block#0:
引导块,文件系统不会使用它。它用于容纳从磁盘引导操作系统的引导程序。
Block#1:
超级块(在硬盘分区中字节偏移量为1024)。用于容纳关于整个文件系统的信息。
超级块中一些重要字段。
1.SuperBlock超级块
记录了block的总数量,inode的数量,block的size等等。
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 */
...
2.Group Description块组描述符
记录了磁盘上所有block group的信息(为了冗余),当然包含本block group的信息:block bitmap的位置,inode 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.block bitmap,inode bitmap
0表示block是free,1表示block被占用
4.Inode table索引节点
存储ext2_inode数据结构的地方,每个ext2_inode有个编号,编号从1开始,root inode的编号为2
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保存文件类型和读取权限
当i_mode为文件时,i_blocks表示data block的指针。
当i_mode为文件夹时,i_blocks表示ext2_dir_entry_2的指针。
每个ext2_dir_entry_2的大小都不一样,所以查找ext2_dir_entry_2时要线性搜索,这是时间换空间。
使用df命令查看各个分区挂在情况
查看超级块
邮差算法
邮差算法:在计算机系统中, 经常出现下面这个问题。 一个城市有M 个街区, 编号从 0到M-1。 每个街区有N座房子, 编号从0 到 N-1。每座房子有一个唯一的街区地址, 用(街区, 房子)表示, 其中0<=街区<M, 0<=房子<N。 来自外太空的外星人可能不熟悉地球上的街区寻址方案, 倾向于采用线性方法将这些房子地址编为 0, 1, ···, N-1, N, N+1 等。 已知某个街区地址 BA= (街区, 房子), 怎么把它转换为线性地址 LA, 反过来,已知线性地址,怎么把它转换为街区地址?如果都从0开始计数,转换就会非常简单。
Linear_addraaa LA=N*block + house;
Blook_address BA=(LA/N, LA % N);
EXT2文件系统树的遍历
(1)读取超级块。检查幻数s_magic(0xEF53),验证它确实是 EXT2 FS。
(2)读取块组描述符块(1 + s_first_data_block),以访问组0描述符。从块组描述符的bg_inode_table条目中找到索引节点的起始块编号,并将其称为InodesBeginBlock。
(3)读取 InodeBeginBlock,获取/的索引节点,即 INODE#2。
(4)将路径名标记为组件字符串,假设组件数量为n。例如,如果路径名 =/a/b/c,则组件字符串是"a""b""c",其中n=3。用name[0],name[1],…,name[n-1]来表示组件。
(5)从(3)中的根索引节点开始,在其数据块中搜索 name[0]。为简单起见,我们可以假设某个目录中的条目数量很少,因此一个目录索引节点只有12个直接数据块。有了这个假设,就可以在12个(非零)直接块中搜索 name[0]。目录索引节点的每个数据块都包含以下形式的 dir entry 结构体;
[ino rec_len name_len NAME] [ino rec_len name_len NAME]....
其中NAME是一系列nlen字符,不含终止NUL。对于每个数据块,将该块读入内存并使用 dir_entry *dp指向加载的数据块。然后使用 name_len将NAME提取为字符串,并与name[0]进行比较。如果它们不匹配,则通过以下代码转到下一个dir_entry:
dp =(dir_entry*)((char *)dp + dp->rec_len);
继续搜索。如果存在 name[0],则可以找到它的 dir_entry,从而找到它的索引节点号。
(6)使用索引节点号ino来定位相应的索引节点。回想前面的内容,ino 从1开始计数。使用邮差算法计算包含索引节点的磁盘块及其在该块中的偏移量。
blk =(ino - 1) / INODE8_PER_BLOCK + InodesBeginBlock;
offset = (ino - 1) % INODES_PER_BLOCK;
然后在索引节点中读取/a,从中确定它是否是一个目录(DIR)。如果/a不是目录,则不能有/a/b,因此搜索失败。如果它是目录,并且有更多需要搜索的组件,那么继续搜索下一个组件 name[1]。现在的问题是∶在索引节点中搜索/a的 name[1],与第(5)步完全相同。
(7)由于(5)~(6)步将会重复n次,所以最好编写一个搜索函数∶
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 文件系统也是类似操作。
文件系统的级别
文件系统的实现分为三个级别。每个级别处理文件系统的不同部分。这使得实现过程模块化,更容易理解。在文件系统的实现过程中,FS目录包含实现EXT2文件系统的文件。
使用第1级别FS 函数的用户命令程序有:mkdir、creat.mknod. rmdir.link.unlink.symlink, rm、ls、cd和 pwd等。
- 第1级别实现了基本文件系统树。它包含以下文件,实现了指定函数。
mkdir_creat.c : make directory, create regular file
ls_cd_pwd.c : list directory, change directory, get CWD path
rmdir.c : remove directory
link_unlink.c : hard link and unlink files
symlink_readlink.c : symbolic link files
stat.c : return file information
misc1.c : access,chmod, chown, utime, etc.
- 第2级别实现了文件内容读/写函数。
open_close_lseek.c : open file for RBAD | WRITE|APPEND,close file and lseek
read.c : read from file descriptor of an opened regular file
write.c : write to file descriptor of an opened regular file
opendir_readdir.c : open and read directory
- 第3级别实现了文件系统的挂载、卸载和文件保护。
mount__umount.c : mount/umount file systems
file_protection : access permission checking
file-locking : lock/unlock files
文件系统挂载
1.系统调用处理
用户执行挂载是通过系统调用路径进入内核处理,拷贝用户空间传递参数到内核,挂载委托do_mount。
2.挂载点路径查找
挂载点路径查找,挂载委托path_mount.
3.参数合法性检查
参数合法性检查, 新挂载委托do_new_mount
4.调用具体文件系统挂载方法
5.挂载实例添加到全局文件系统树
编程实现ls-l命令
代码托管:https://gitee.com/DKY2019/xxaqxt/blob/master/myls_l.c
参考资料
详解EXT2 https://blog.csdn.net/gongjiwei/article/details/82025142
ext2文件系统结构 https://www.jianshu.com/p/18c74734911b
文件系统挂载 https://zhuanlan.zhihu.com/p/378011720
ext2文件系统学习 https://blog.csdn.net/weixin_30415801/article/details/95911807
20191331lyx
2021/10/17