learn-tech/专栏/计算机基础实战课/35Linux文件系统(一):Linux如何存放文件?.md
2024-10-16 10:18:29 +08:00

272 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
35 Linux文件系统Linux如何存放文件
你好我是LMOS。
上一节课我们一起了解了什么是文件和文件系统。接下来的两节课我们继续深入学习Linux上的一个具体的文件系统——Ext3搞清楚了文件究竟是如何存放的。
这节课我会带你建立一个虚拟硬盘并在上面建立一个文件系统。对照代码实例相信你会对Ext3的结构有一个更深入的认识。课程配套代码你可以从这里下载。话不多说我们开始吧。
建立虚拟硬盘
要想建立文件系统就得先有硬盘,我们直接用真正的物理硬盘非常危险,搞不好数据就会丢失。所以,这里我们选择虚拟硬盘,在这个虚拟硬盘上操作,这样怎么折腾都不会有事。
其实我们是用Linux下的一个文件来模拟硬盘的写入硬盘的数据只是写入了这个文件中。所以建立虚拟硬盘就相当于生成一个对应的文件。比如我们要建立一个 100MB 的硬盘,就意味着我们要生成 100MB 的大文件。
下面我们用 Linux 下的 dd 命令(用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换)生成 100MB 的纯二进制的文件(就是向 1100M 字节的文件里面填充为 0 ),代码如下所示:
dd bs=512 if=/dev/zero of=hd.img count=204800
;bs:表示块大小这里是512字节
;if表示输入文件/dev/zero就是Linux下专门返回0数据的设备文件读取它就返回0
;of表示输出文件即我们的硬盘文件
;count表示输出多少块
下面我们就要在虚拟硬盘上建立文件系统了,所谓建立文件系统就是对虚拟硬盘放进行格式化。可是,问题来了——虚拟硬盘毕竟是个文件,如何让 Linux 在一个文件上建立文件系统呢?
这个问题我们要分成两步来解决。
第一步,把虚拟硬盘文件变成 Linux 下的回环设备,让 Linux 以为这是个设备。下面我们用 losetup 命令,将 hd.img 这个文件变成 Linux 的回环设备,代码如下:
sudo losetup /dev/loop0 hd.img
第二步,由于回环设备就是 Linux 下的块设备用户可以将其看作是硬盘、光驱或软驱等设备并且可以用mount命令把该回环设备挂载到特定目录下。这样我们就可以用 Linux 下的 mkfs.ext3 命令,把这个 /dev/loop0 回环块设备格式化进而格式化hd.img文件在里面建立 Ext3 文件系统。
sudo mkfs.ext3 -q /dev/loop0
需要注意的是loop0可能已经被占用了我们可以使用loop1、loop2等你需要根据自己电脑的情况处理。
我们可以用 mount 命令将hd.img挂载到特定的目录下如果命令执行成功就能验证我们虚拟硬盘上的文件系统成功建立。命令如下所示
sudo mount -o loop ./hd.img ./hdisk/ ;挂载硬盘文件
这行代码的作用是将hd.img这个文件使用 loop 模式挂载在 ./hdisk/目录之下通过这个hdisk目录就能访问到hd.img虚拟硬盘了。并且我们还可以用常用的mkdir、touch命令在这个虚拟硬盘中建立目录和文件。
Ext3文件系统结构
我们建好了硬盘对其进行了格式化也在上面建立了Ext3文件系统。下面我们就来研究一下Ext3文件系统的结构。
Ext3文件系统的全称是Third extended file system已经有20多年的历史了是一种古老而成熟的文件系统。Ext3在Ext2基础上加入了日志机制也算是对Ext2文件系统的扩展并且也能兼容Ext2。Ext3是在发布Linux2.4.x版本时加入的支持保存上TB的文件保存的文件数量由硬盘容量决定还支持高达255字节的文件名。
Ext3的内部结构是怎样的呢Ext3将一个硬盘分区分为大小相同的储存块每个储存块可以是2个扇区、4个扇区、8个扇区分别对应大小为1KB、2KB、4KB。
所有的储存块又被划分为若干个块组每个块组中的储存块数量相同。每个块组前面若干个储存块中依次放着超级块、块组描述表、块位图、inode节点位图、inode节点表、数据块区。需要注意的是超级块和块组描述表是全局性的在每个块组中它们的数据是相同的。
我再帮你画一个逻辑结构图,你就容易理解了,如下所示:
上图中第1个储存块是用于安装引导程序或者也可以保留不使用的。超级块占用一个储存块在第2个储存块中即储存块1储存块的块号是针对整个分区编码的从0开始。其中的块组描述符表、块位图、inode节点位图、inode节点表的占用大小是根据块组多少以及块组的大小动态计算的。
下面我们分别讨论这些重要结构。
Ext3文件系统的超级块
我们首先要探讨的是Ext3文件系统的超级块它描述了Ext3的整体信息例如有多少个inode节点、多少个储存块、储存块大小、第一个数据块号是多少每个块组多少个储存块等。
Ext3文件系统的超级块存放在该文件系统所在分区的2号扇区占用两个扇区。当储存块的大小不同时超级块所在块号是不同的。
比如说当储存块大小为1KB时0号块是引导程序或者保留储存块超级块起始于1号块储存当块大小为2KB时超级块起始于0号储存块其位于0号储存块的后1KB前1KB是引导程序或者保留当储存块大小为4KB时超级块也起始于0号储存块其位于0号块的1KB处。总之超级块位于相对于分区的2号~3号扇区这一点是固定的。
下面我们看一看用C语言定义的超级块代码如下所示
struct ext3_super_block {
__le32 s_inodes_count; //inode节点总数
__le32 s_blocks_count; // 储存块总数
__le32 s_r_blocks_count; // 保留的储存块数
__le32 s_free_blocks_count;// 空闲的储存块数
__le32 s_free_inodes_count;// 空闲的inode节点数
__le32 s_first_data_block; // 第一个数据储存块号
__le32 s_log_block_size; // 储存块大小
__le32 s_log_frag_size; // 碎片大小
__le32 s_blocks_per_group; // 每块组包含的储存块数
__le32 s_frags_per_group; // 每块组包含的碎片
__le32 s_inodes_per_group; // 每块组包含的inode节点数
__le32 s_mtime; // 最后挂载时间
__le32 s_wtime; // 最后写入时间
__le16 s_mnt_count; // 挂载次数
__le16 s_max_mnt_count; // 最大挂载次数
__le16 s_magic; // 魔数
__le16 s_state; // 文件系统状态
__le16 s_errors; // 错误处理方式
__le16 s_minor_rev_level; // 次版本号
__le32 s_lastcheck; // 最后检查时间
__le32 s_checkinterval; // 强迫一致性检查的最大间隔时间
__le32 s_creator_os; // 建立文件系统的操作系统
__le32 s_rev_level; // 主版本号
__le16 s_def_resuid; // 默认用户保留储存块
__le16 s_def_resgid; // 默认用户组保留储存块
__le32 s_first_ino; // 第一个非保留inode节点号
__le16 s_inode_size; // inode节点大小
__le16 s_block_group_nr; // 当前超级块所在块组
__le32 s_feature_compat; // 兼容功能集
__le32 s_feature_incompat; // 非兼容功能集
__le32 s_feature_ro_compat;// 只读兼容功能集
__u8 s_uuid[16]; // 卷的UUID全局ID
char s_volume_name[16]; // 卷名
char s_last_mounted[64]; // 文件系统最后挂载路径
__le32 s_algorithm_usage_bitmap; // 位图算法
//省略了日志相关的字段
};
以上的代码中我省略了日志和预分配的相关字段而__le16 __le32在x86上就是u16、u32类型的数据。le表示以小端字节序储存数据定义成这样是为了大小端不同的CPU可以使用相同文件系统或者已经存在的文件系统的前提下方便进行数据转换。
Ext3文件系统的块组描述符表
接着我们来看看Ext3文件系统的块组描述符里面存放着用来描述块组中的位图块起始块号、inode节点表起始块号、空闲inode节点数、空闲储存块数等信息文件系统中每个块组都有这样的一个块组描述符与之对应。所有的块组描述符集中存放就形成了块组描述符表。
块组描述符表的起始块号位于超级块所在块号的下一个块,在整个文件系统中,存有很多块组描述符表的备份,存在的方式与超级块相同。
下面我们看一看用C语言定义的单个块组描述符结构如下所示
struct ext3_group_desc
{
__le32 bg_block_bitmap; // 该块组位图块起始块号
__le32 bg_inode_bitmap; // 该块组inode节点位图块起始块号
__le32 bg_inode_table; // 该块组inode节点表起始块号
__le16 bg_free_blocks_count; // 该块组的空闲块
__le16 bg_free_inodes_count; // 该块组的空闲inode节点数
__le16 bg_used_dirs_count; // 该块组的目录计数
__u16 bg_pad; // 填充
__le32 bg_reserved[3]; // 保留未用
};
对照上述代码我们可以看到多个ext3_group_desc结构就形成了块组描述符表而__le16 __le32类型和超级块中的相同。如果想知道文件系统中有多少个块组描述符可以通过超级块中总块数和每个块组的块数来进行计算。
Ext3文件系统的位图块
接下来要说的是Ext3文件系统的位图块它非常简单每个块组中有两种位图块一种用来描述块组内每个储存块的分配状态另一种用于描述inode节点的分配状态。
位图块中没有什么结构就是位图数据即块中的每个字节都有八个位。每个位表示一个相应对象的分配状态该位为0时表示相应对象为空闲可用状态为1时则表示相应对象是占用状态。例如位图块中第一个字节表示块组0~7号储存块的分配状态第二个字节表示块组8~15号储存块的分配状态 ……依次类推。位图块的块号可以从块组描述符中得到。
Ext3文件系统的inode节点
接下来我们再深入研究一下inode节点。上节课我们提过inode节点用来存放跟文件相关的所有信息但是文件名称却不在inode节点之中文件名称保存在文件目录项中。
inode节点中包含了文件模式、文件链接数、文件大小、文件占用扇区数、文件的访问和修改的时间信息、文件的用户ID、文件的用户组ID、文件数据内容的储存块号等这些重要信息也被称为文件的元数据。
那么用C语言如何定义单个inode节点结构呢代码如下所示
struct ext3_inode {
__le16 i_mode; // 文件模式
__le16 i_uid; // 建立文件的用户
__le32 i_size; // 文件大小
__le32 i_atime; // 文件访问时间
__le32 i_ctime; // 文件建立时间
__le32 i_mtime; // 文件修改时间
__le32 i_dtime; // 文件删除时间
__le16 i_gid; // 建立文件的用户组
__le16 i_links_count; // 文件的链接数
__le32 i_blocks; // 文件占用的储存块 */
__le32 i_flags; // 文件标志
union {
struct {
__u32 l_i_reserved1;
} linux1;
struct {
__u32 h_i_translator;
} hurd1;
struct {
__u32 m_i_reserved1;
} masix1;
} osd1; //操作系统依赖1
__le32 i_block[EXT3_N_BLOCKS];// 直接块地址
__le32 i_generation; // 文件版本
__le32 i_file_acl; // 文件扩展属性块
__le32 i_dir_acl; // 目录扩展属性块
__le32 i_faddr; // 段地址
union {
struct {
__u8 l_i_frag; //段号
__u8 l_i_fsize; //段大小
__u16 i_pad1;
__le16 l_i_uid_high;
__le16 l_i_gid_high;
__u32 l_i_reserved2;
} linux2;
struct {
__u8 h_i_frag; //段号
__u8 h_i_fsize; //段大小
__u16 h_i_mode_high;
__u16 h_i_uid_high;
__u16 h_i_gid_high;
__u32 h_i_author;
} hurd2;
struct {
__u8 m_i_frag; //段号
__u8 m_i_fsize; //段大小
__u16 m_pad1;
__u32 m_i_reserved2[2];
} masix2;
} osd2; //操作系统依赖2
__le16 i_extra_isize;
__le16 i_pad1;
};
这就是inode节点它包含文件的所有信息。文件的数据内容的储存块号保存在i_block中这个i_block数组前十二元素保存的是1~12这12个储存块号第十三个元素开始保存的是一级间接储存块块号、二级间接储存块块号、三级间接储存块块号。
那问题来了,什么是间接储存块?我给你画幅图,你就明白了。
由上图可知一个inode节点中有11个直接储存块其中存放的是块号能直接索引11个储存块。
如果每个储存块大小是1KB的话可以保存11KB的文件数据当文件内容大于11KB时就要用到一级间接储存块。
这时一级间接储存块里的块号索引的储存块中不是文件数据而是储存的指向储存块的块号它可以储存1024/4个块号即可索引1024/4个储存块。二级、三级间接块则依次类推只不过级别更深保存的块号就更多能索引的储存块就更多储存文件的数据量就更大。
Ext3文件系统的目录项
讲到这里我们已经对Ext3文件系统若干结构都做了梳理现在你应该对Ext3文件系统如何储存文件有了一定认识。
可是文件系统中还有许多文件目录,文件目录是怎么处理的呢?
Ext3文件系统把目录当成了一种特殊的文件即目录文件目录文件有自己的inode节点能读取其中数据。在目录文件的数据中保存的是一系列目录项目录项用来存放文件或者目录的inode节点号、目录项的长度、文件名等信息。
下面我们看一看用C语言定义的单个目录项结构长什么样
#define EXT3_NAME_LEN 255
struct ext3_dir_entry {
__le32 inode; // 对应的inode节点号
__le16 rec_len; // 目录项长度
__u8 name_len; // 文件名称长度
__u8 file_type; // 文件类型:文件、目录、符号链接
char name[EXT3_NAME_LEN];// 文件名
};
目录项结构大小不是固定不变的这是由于每个文件或者目录的名称不一定是255个字符一般情况下是少于255个字符这就导致name数组不必占用完整的空间。所以目录项是动态变化需要结构中的rec_len字段才能知道目录项的真实大小。
重点回顾
今天的课程我们就结束了,我们一起回顾一下学习的重点。
首先为了体验一下怎么建立文件系统同时为了避免我们在物理硬盘的误操作导致丢失数据所以我们用文件方式建立了一个虚拟硬盘并在上面格式化了Ext3文件系统。
接着我们从逻辑上了解Ext3文件系统重点了解了它的几个重要结构超级块用于保存文件系统全局信息了块组描述符用于表示硬盘的一个个块组位图用于分配储存块和inode节点inode节点用于保存文件的元数据还有文件数据块的块号最后还有目录结构用来存放者文件或者目录的inode节点号、目录项的长度、文件名等信息。
这节课的导图如下所示,供你参考:
下节课我们继续聊聊怎么读取文件系统的文件,敬请期待。
思考题
请问Ext3文件系统的超级块放在硬盘分区的第几个扇区中。
欢迎你在留言区记录自己的收获,或者向我提问。如果觉得这节课还不错,别忘了分享给身边的朋友。