# 文件系统

太久没有写文章了，有时候是因为懒，有时候是觉得理解的不够透彻，更多的是因为感觉文笔太差。。

今天就来聊聊文件系统，这也是个庞然大物，先说说现在流行的分布式存储把，现在分布式存储主要分为对象存储，块存储，和文件存储，这些存储其实单机本来就有的，不过是云计算厂商在云上提供了这些服务，把单机的存储转到了云上，准确的说其实应该叫云上的分布式对象存储、块存储、文件存储。

块存储要从Linux系统设备说起，这是Unix系统遗留下来的传统，也是一种优秀的设计思想，把所有的东西都看成文件，设备当然也是一种文件咯，根据读写方式的不同分为：块设备（按照Block为单位读写），字符设备（可以按照offset以字节为单位读写），以及其他设备。在虚拟文件系统里，下一层是就是块设备层，块设备层会调用驱动程序对硬盘进行读写，而对VFS提供的这个块设备的服务抽出来，拿到云上去做就叫块存储。

对象存储其实就是KV存储，因为在比如Java这种语言里，value存储的往往是一个对象的序列化后的数据，所以叫对象存储（理解的不对请告诉我！）。

文件存储指的是提供符合POSIX标准的存储服务，在Linux用户态下，想要对文件进行读写操作，必须通过系统提供的接口，比如read,write,open等，而POSIX标准就是一个针对操作系统提供的接口的标准，也就说说能够提供read,write,open等接口的存储服务就是文件存储。

#### 文件系统

其实文件系统茫茫多，ext,ext2,ext3,ext4等等等，看看linux/fs下面的模块就知道了，为了统一这些模块，linux抽象出了虚拟文件系统VFS，VFS把文件抽象成了inode，每个inode都会关联一组函数指针，这一组函数指针就是不用的文件系统具体的读写操作。

Linux启动时会挂载一个根目录文件系统，这个由你启动参数指定，启动过程中还有一些特殊的文件系统，比如sockfs,tmpfs,procfs等等，这些文件系统都会被挂载到一个挂载树上，挂载树的根就是`/`，他是这个系统这次启动的根目录，你可以在这棵树上继续挂载新的节点，每个节点都会针对一个具体的文件系统。

当你做一个操作时，比如open("/foo/bar")，VFS首先会在挂载树中查找挂载点，然后从挂载点找到对应的inode，从这个inode开始对剩余的路径继续进行相应的操作。

#### EXT2文件系统

EXT2和EXT3是兼容的是个简单的文件系统，很适合来学习，下面我们来剖析一下EXT2文件系统。

上面说到了，块设备就是按照块来读写的设备，那么EXT2就是决定如何将数据存储到对应的块设备上去的系统。

文件系统其实很简单，只有文件和目录，目录里可能会包含目录和文件，是一个树状的结构，而文件可能会存储数据，数据从几KB到几GB不等，所以要选择合适的文件系统，就首先要确定你的存储场景。而这么多的文件系统其实就是针对不同的场景经过权衡取舍的结果。

EXT2文件系统将许多Blocks分成多个Block Group，每个Block Group都有一个Group Descriptor，这些Group Descriptor都在一起，位于SuperBlock的后面，而每个Group Descriptor都含有block bitmap，inode bitmap，inode table等。

**SuperBlock**

对于EXT2文件系统来说，首先就是SuperBlock，SuperBlock是一个特殊的块，它记载着本文件系统全局的属性信息，位于磁盘起始的1024字节处，假如BlockSize是1024字节，它就位于第二个Block，假如BlockSize是4096字节，那它位于第一个Block，BlockSize在创建具体的文件系统时指定。

```
/*
 * Structure of the super block
 */
struct ext2_super_block {
	unsigned long  s_inodes_count;	/* Inodes count */
	unsigned long  s_blocks_count;	/* Blocks count */
	unsigned long  s_r_blocks_count;/* Reserved blocks count */
	unsigned long  s_free_blocks_count;/* Free blocks count */
	unsigned long  s_free_inodes_count;/* Free inodes count */
	unsigned long  s_first_data_block;/* First Data Block */
	unsigned long  s_log_block_size;/* Block size */
	long           s_log_frag_size;	/* Fragment size */
	unsigned long  s_blocks_per_group;/* # Blocks per group */
	unsigned long  s_frags_per_group;/* # Fragments per group */
	unsigned long  s_inodes_per_group;/* # Inodes per group */
	unsigned long  s_mtime;		/* Mount time */
	unsigned long  s_wtime;		/* Write time */
	unsigned short s_mnt_count;	/* Mount count */
	short          s_max_mnt_count;	/* Maximal mount count */
	unsigned short s_magic;		/* Magic signature */
	unsigned short s_state;		/* File system state */
	unsigned short s_errors;	/* Behaviour when detecting errors */
	unsigned short s_pad;
	unsigned long  s_lastcheck;	/* time of last check */
	unsigned long  s_checkinterval;	/* max. time between checks */
	unsigned long  s_reserved[238];	/* Padding to the end of the block */
};
```

比较关键的有s\_blocks\_per\_group和s\_log\_block\_size，这就确定了磁盘分区上有多少个block group。

**BlockDescriptor**

Block Descriptor紧跟SuperBlock，Block Descripotr的数量可以通过公式 `(s_blocks_count - s_first_data_block - 1) / s_blocks_per_group + 1`算出，而且每个BlockDescriptor的大小必须不能超过BlockSize，所以我们可能一下将所有的Block Descriptor读出来。

```
struct ext2_group_desc
{
	unsigned long  bg_block_bitmap;		/* Blocks bitmap block */
	unsigned long  bg_inode_bitmap;		/* Inodes bitmap block */
	unsigned long  bg_inode_table;		/* Inodes table block */
	unsigned short bg_free_blocks_count;	/* Free blocks count */
	unsigned short bg_free_inodes_count;	/* Free inodes count */
	unsigned short bg_used_dirs_count;	/* Directories count */
	unsigned short bg_pad;
	unsigned long  bg_reserved[3];
};
```

**Inode**

虚拟文件系统我们就提到Inode了，这里的Inode其实像是虚拟文件系统的Inode序列化后存放到磁盘中的Inode，每一个目录或者文件都会对应一个Inode。

根目录的Inode是固定的，从根目录开始对于特定Inode的读取，首先要知道Inode的inode号，然后用inode号除以SuperBlock中的s\_inodes\_per\_group得到在哪个Block Descriptor，通过余数得到在这个BlockDescriptor里的哪个inode，从inode table中读取对应的inode。

```
/*
 * Structure of an inode on the disk
 */
struct ext2_inode {
	unsigned short i_mode;		/* File mode */
	unsigned short i_uid;		/* Owner Uid */
	unsigned long  i_size;		/* Size in bytes */
	unsigned long  i_atime;		/* Access time */
	unsigned long  i_ctime;		/* Creation time */
	unsigned long  i_mtime;		/* Modification time */
	unsigned long  i_dtime;		/* Deletion Time */
	unsigned short i_gid;		/* Group Id */
	unsigned short i_links_count;	/* Links count */
	unsigned long  i_blocks;	/* Blocks count */
	unsigned long  i_flags;		/* File flags */
	unsigned long  i_reserved1;
	unsigned long  i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
	unsigned long  i_version;	/* File version (for NFS) */
	unsigned long  i_file_acl;	/* File ACL */
	unsigned long  i_dir_acl;	/* Directory ACL */
	unsigned long  i_faddr;		/* Fragment address */
	unsigned char  i_frag;		/* Fragment number */
	unsigned char  i_fsize;		/* Fragment size */
	unsigned short i_pad1;
	unsigned long  i_reserved2[2];
};
```

对文件的读写其实就是对于inode的读写，inode是如何存放连续的数据的呢，答案就在i\_block里。

首先每个inode都有15个block指针，前12个指针是direct pointer，直接指向一个具体的block。

假设这些block用完了之后，第13个指针是indirect pointer，它指向的是block的指针。

第十四个指针是double indirect pointer，它指向的是block指针的指针。

第十五个指针是triple indirect pointer，它指向的是block指针的指针的指针。

通过这样多级指针的设计我们可以把某个文件扩展的很大，而在文件较小时也不影响它的性能。

**direntry**

direntry是一种固定大小的存储格式，它代表一个具体的目录，它的inode内容就是它所包含的子目录或者文件。

```
struct ext2_dir_entry {
	unsigned long  inode;			/* Inode number */
	unsigned short rec_len;			/* Directory entry length */
	unsigned short name_len;		/* Name length */
	char           name[EXT2_NAME_LEN];	/* File name */
};
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://chenfeigao.gitbook.io/notes/cao-zuo-xi-tong/wen-jian-xi-tong.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
