当前位置: 首页 > 科技观察

从根上理解Node.js的Fs模块:一起设计一个文件系统

时间:2023-03-19 19:08:58 科技观察

Node.js提供了FileSystemapi,可以读写文件、目录、修改权限、创建软链接等,也许你更精通API的使用,但不一定了解这些API的原理。要想真正了解文件系统,就得从根本上看。Let'strytodesignafilesystemfrom0to1.从0到1设计文件系统什么是文件?这样一个比较完整的数据就是一个文件。但是电脑的持久化存储在硬盘上,主要是磁盘和SSD固定硬盘:电脑中的文件是逻辑概念,不是物理实体。那么如果给了这样一个硬盘,让我们自己创建一个文件系统来实现文件的功能。怎么做?简单地想一想:将它们并排放置。然后我们需要记录索引,文件在哪:这个可以,但是有一个问题,万一文件B被删除,相应的空间就会被释放:然后还有一个文件D,发现是不能放啊,地方好小。A和B之间的空间是碎片,碎片会将可用的磁盘空间分割成许多不连续的小块。该怎么办?如何更好地利用磁盘空间?堵塞!把文件分成小块,比如1k是一个块,不需要连续存储,只需要在索引中记录所有的块。看不清索引是怎么记录的。放大来看:每个文件的索引记录了存储了哪些数据块,这样就可以一个一个的读出来了。而文件删除了,那些块还可以继续使用,不会出现大碎片。精彩的!难怪文件系统的第一步是分块,内存管理的第一步是分页,这样才能高效地利用空间。这种索引节点可以称为索引节点,简称inode。此外,除了文件名和存储块外,还可以记录其他信息,如创建时间、修改时间、文件权限、所属用户等。至此,可以在物理层面对文件进行定义:一个文件就是inode记录的信息以及它索引的一系列数据块。然后我用一个block写了一个文件,删除了一个文件释放了一个block。我怎么知道?块的状态必须单独记录。数据块只有空闲和占用两种状态,一个二进制位就够了。通过一个二进制数记录所有块的空闲状态。一位代表一个值,称为位图。在这种情况下,它是块位图。硬盘大部分是数据块,第一段用来存放inode所在的块和数据块的块位图。Inode也存在于块中。比如我们规定只能用5个block来存放inode,那么inode的总量是有限制的,也就是说文件系统可以创建的文件是有上限的。我们还跟踪所有inode的空闲状态,即inode位图。简单了解一下我们设计的文件系统:为了更好的利用硬盘空间,我们将数据分成了块,inode中记录了每个文件使用了哪些块。inode还记录了文件的创建时间、修改时间、权限等信息。数据块的空闲状态通过blockbitmap记录,inode的空闲状态通过inodebitmap记录。但是如果我想知道硬盘有多少块,有多少被使用,有多少inode,有多少被使用,怎么办呢?简单,遍历一次blockbitmap和inodebitmap,就知道个数了。但是每次都太慢了,就像我们设计数据库的时候,一个论坛下有多少个帖子,这个数据不会每次都用sql去查询,而是在增删帖的时候动态维护一个字段。在数据库表中,可以直接查询。那么我们还设计了一个区块来存储这种统计信息,即:这个区块是更高层的统计信息,我们可以称之为超级区块。现在把我们设计的文件系统交给用户,我们就可以利用它来高效地利用硬盘了。发布版本:神光文件系统V1.0。但是现在我们的文件系统好像用处不大,只能创建文件,那我创建1000个文件呢?查询速度慢,容易命名冲突。该怎么办?命名空间!目录!喜欢文件夹的想法。如何实现?每个inode都是一个文件,所以仅仅将inode组织成树是不够的。比如我们有两个文件B和C:我们创建一个目录A:在inode中添加一个isDirectory属性,如果是目录,则读取数据块的内容,找到里面的inode节点号,然后知道目录文件。这就是目录的实现原理:利用inode的isDirectory属性来区分是文件还是目录。如果是目录,则读取数据块中的inode信息,找到子文件。如果是文件,直接读取数据块作为文件内容。从一个inode到另一个inode的查找序列称为路径。比如这样一个文件的inode搜索顺序:文件路径为/A/D/dongdong.jpg这就是文件路径的本质:文件路径就是inode搜索顺序现在我们支持目录的嵌套,和我们可以将文件和目录组织成树状结构,方便管理。发布版本:神光文件系统v2.0。现在一个inode只有一条路径,因为它是一棵树,如果我想通过两条路径找到同一个inode怎么办?比如/A/D/dongdong.jpg可以访问这个文件:我想通过/A/B/dongdong.jpg也可以访问,怎么办?只是指向过去。这样,确实有两条路径可以找到同一个文件。我们称这个额外链接为硬链接。但是因为一个节点有两个父节点,所以它不再是一棵树,而是一个图。所以文件树的概念严格意义上还是有问题的,它可能是一个文件映射。但是如果我想把dongdong.jpg的名字改成dongdong2.jpg呢?现在是同一个inode节点,改了就改了。但是我只想改变/A/B路径的文件名,没有别的。然后再创建一个inode节点重命名,然后这个inode节点指向dongdong.jpg。这种链接不直接引用过去,而是加一个inode重命名,然后引用过去。我们称这种软链接。为什么叫硬呢?因为不能改变,所以直接指向同一个inode。为什么叫软呢?因为可以改,所以多了一层inode来改名。所以我们分别命名为硬链接和软链接。硬链接和软链接都是用来在多个路径下查找同一个文件,但是硬链接不能单独重命名,而软链接可以。monorepo的实现是基于软链接的,可以指向同一个目录inode,也可以是别名。硬链接和软链接都实现了,可以出新版本了。发布版本:神光文件系统v3.0。回顾我们设计的文件系统:v1.0:使用数据块存储文件内容,提高硬盘利用效率,使用inode记录数据块和使用文件的创建时间和权限等信息,使用块位图记录数据块空闲时间Status通过inode位图记录inode空闲状态。索引节点和数据块的统计信息通过超级块记录。此版本实现文件访问,但不支持目录。v2.0:通过在inode中增加一个属性来记录存储在文件或目录目录的数据块中的具体文件列表的inode信息,在读取目录时可以读取文件列表。目录inode和文件inode的逐层搜索顺序称为文件路径。该版本实现了目录和路径的功能。v3.0:通过包含相同inode的多个目录inode,可以使用多个路径搜索同一个文件,称为硬链接。目录首先创建一个inode节点用于重命名,然后inode节点指向目标inode节点,这称为软链接。该版本实现了多路径软硬链接功能,统一查找文件。真实的文件系统也是类似的实现。目前文件系统有很多,比如ext2、FAT等,原理和我们设计的文件系统类似。文件系统的设计就完成了。回到最初的目的,我们要深入理解Node.js文件系统的API。下面我们一起来看看吧。Node.js文件系统apiNode.js通过V8注入了js的fsapi,底层是通过C++调用操作系统的文件系统函数,也就是我们上面设计的文件系统。我们调用的fsAPI最终调用的是操作系统的文件系统功能。自己设计了一个文件系统后,再看看fs的api,看看是否有更深的理解:fs.stat获取inode中的信息,fs.chmod修改文件权限,同样修改inode信息fs.chown修改用户修改inode信息fs.copyFile复制inode和数据块,将新的inode添加到对应目录的inode内容中fs.link创建软硬链接,即多路径搜索的inode同一个文件,软链接也可以改名fs.mkdir创建目录inode,fs.rmdir读取目录inode中包含的所有文件。inode的具体API有很多,但是都是用来操作我们上面设计的文件系统的。如果您从根本上了解文件系统,您将能够轻松使用这些API。总结为了真正理解Node.js的fs模块,我们一起设计了一个文件系统:把文件分成不同的数据块,这样磁盘空间就可以得到高效的利用。文件的索引节点记录了包含的数据块、创建时间、权限、是否为目录等信息。通过块位图记录数据块的空闲状态。通过inode位图记录inode空闲状态。使用superblock记录硬盘的inode和datablock使用信息。目录节点是通过inode对应的数据块内容包含文件inode的信息列表来实现的。我们得出一些重要的结论:文件本质上是索引节点+数据块。路径本质上是找到目标inode的路径。硬链接本质上是包含相同inode的多个目录inode。软链接本质上是创建一个额外的inode用于重命名,对应的数据块指向目标inode。Node.js的fsapi是调用通过c++注入v8的操作系统能力。了解了文件系统之后,学那些api就很容易了。