操作文件是我们平时经常有的操作。但是我们可能并不是很了解他们原理,比如为什么删除一个很大的文件,会非常快?创建一个文件的时候,系统发生了什么?为什么删除的文件,还可以恢复?知其然知其所以然。我们一起深入探索文件系统的一些原理。这篇先分析一下创建文件的过程。
我们先看一下文件系统在硬盘中的布局。
我们再看一下文件系统在内存中的布局。
对着上面的图,从左向右,我们看到
1 一个进程有一个文件描述符数组,这个描述符数组的元素,就是我们平时操作文件的时候,使用的那个fd。定义如下:
struct file * filp[NR_OPEN];
2 每个文件描述符只是索引,他对应的项指向一个file结构体,file结构体定义如下。
// 管理打开文件的内存属性的结构,比如操作位置(inode没有读取操作位置这个概念,),实现系统进程共享inode
struct file {
unsigned short f_mode;
unsigned short f_flags;
unsigned short f_count;
struct m_inode * f_inode;
off_t f_pos;
};
file结构体是和inode的作用是不一样的,inode是更多的是存储文件的一些持久化的数据,比如大小,时间、属主,数据块位置等。inode是存在硬盘中的。在操作的文件的时候才会加载到内存。如果有修改,需要回写硬盘。file存储的是文件临时的元数据,他只存在内存里。比如一个文件当前读写位置,打开模式等等。关闭文件后就会丢失这些数据。
3 在文件系统中,每一个文件都对应一个inode结构体。inode保存了一个文件的元数据,包括大小,时间,属主,块号等等。inode存在于硬盘和内存,内存的inode叫m_inode,他的部分属性和硬盘inode一一对应(硬盘inode叫d_inode),还有一些是只存在内存中的属性。我们看一下inode在硬盘中的布局。
了解一系列结构体后,我们开始分析创建文件的这个过程。主要是两件事情,第一,判断文件是否存在,如果不存在则开始创建。
1 根据路径找到最后一级目录对应的inode节点。目录其实也是文件,他和一般文件的区别是,一般文件存储的是用户数据,目录文件存储的是文件信息。目录文件里存储的数据就是一个对象数组,每个元素保存了文件名和inode节点号。
// 目录项结构
struct dir_entry {
// inode号
unsigned short inode;
// 文件名
char name[NAME_LEN];
};
假设我们找/a/b/hello.txt这个文件。因为/是根文件系统的根路径,他在文件系统初始化的时候,根文件系统会从固定的位置(第一个inode节点),把他对应的inode结构体加载到内存中。我们根据根inode,就知道根目录下面有多少dir_entry,然后逐个比较找到目录a对应的dir_entry,从dir_entry中得到目录a的inode号,再根据a的inode号把inode结构体从硬盘中加载到内存,继续这个过程,直到最后找到hello.txt。
2 所以我们从一个目录下找一个目录或者文件的时候,其实就是遍历这个数组,对比name是否一样,是的话根据inode号取出inode结构体,从而取得文件数据。
3 因为我们是创建文件,所以是肯定找不到的。
4 上面已经解释过,一个文件对应一个inode。现在我们创建一个文件,那自然,我们就要先在硬盘中申请一个inode,并且修改文件系统的元数据inode位图,即这个inode被使用了。然后再在内存中申请一个m_inode。供用户操作文件的。至此,创建文件就完成了。我们发现,创建一个文件,底层发生的事情其实就是在硬盘申请一个inode就可以了。
5 当我们开始操作m_inode对应的文件的时候。比如写入。回归上面的inode结构体可以发现,这时候文件其实是没有被分配硬盘空间的。现在需要写入,那首先就要先在硬盘中申请一块空间。并修改块位图信息。然后申请一块和该硬盘块关联的内存块,用户写入的数据就存在该内存块中,系统会定时回写到硬盘中对应的块。
这就是创建一个文件的大致过程。