我的OS | 一个文件系统的设计与实现

本文介绍了一个名为CFS的文件系统设计,包括启动区+超级块的结构,目录与i-node的格式,以及格式化、挂载、同步、创建、写入和读取等关键操作。CFS的inode设计小巧,通过间接块实现大文件支持,适合小型存储系统。代码部分展示了主要函数的功能和实现。

今天,笔者要设计一个类似ext2的文件系统CFS (cunix file-system),在https://github.com/pengruiyang-cpu/rlib上的cfs.c和cfs.h。

设计

不管啥样的文件系统,他都是由这几个部分组成的:

  • 启动区
  • 超级块
  • 根目录
  • 数据区

(NTFS, FAT系列文件系统将超级块放到了启动区内)

所以,咱们就一个个的分析,一个个设计吧。

启动区 + 超级块

CFS的启动区就放在超级块的前512个字节里头,里面只放了代码,文件系统的全部信息都存储在超级块4096字节中。他们分别是(参考了Minix FS):

  • 启动区(512字节)
  • 魔数 (1328E3B
  • 块数量(一块是4096字节,4KB)
  • 块位图的块号码
  • 根目录的块号码
  • 保留(可能作为loader,3568字节)

需要特别说明的是第二个魔数,研究过Linux的应该能想到magic这个单词,Linux中reboot的magic是他的生日,他大女儿的生日和他二女儿三女儿的生日,CFS中这个字段必须位1328E3B,否则在进行挂载的时候会警告。
这里面的块位图和根目录都只是指他们的块号码,并不是保存在这里。

好了,还有什么吗?对,还有这个讨厌的’保留’。所谓保留就是说不知道怎么用了,留着以后用吧。
但在CFS里面,保留这个地方笔者打算放loader,因为笔者觉得这里放的下,要是保存成文件就太浪费资源了,这是笔者尤其讨厌的。

目录与i-node的格式

目录和inode都是UNIX系文件系统中不可或缺的东西,目录用来保存inode,inode用来保存文件的信息。inode被笔者设计的极其小(仅仅只有32字节!),这是笔者的一个习惯吧,用速度换空间。

  • inode位图
  • inode们(固定为4096个,以后更改)
  • 文件名称
  • 保留(又是这家伙儿,烦人,迟早给你用了)

目录没什么好说的,里面只有几个东西,但是有点儿大。保留的东西是为了凑齐一块,否则得用软件逻辑实现,镇南首,索性扔在这儿吧。

inode就有好说的了,他可是少而精,不想某文件系统多而烂,运行慢,装的满……

  • inode号码(用来索引文件名)
  • 模式(具体见cfs.h)
  • 占用的块数量(如果是pointer指针文件,则指向被指向inode的块号码)
  • 到上一个块的距离
  • 第一间接块
  • 第二间接块
  • 第三间接块
  • 保留(8字节)

模式这个东西只用了2字节,不像ext2用了unsigned int4个字节。
至于到上一个块的距离这个笔者就要说说了,因为unsigned int最大只有4GB(这直接导致老版本ext2只能保存4GB文件),所以笔者用他保存块数量,然后(块数量 * 4096 + 到上一个块的距离)就是文件大小。
间接块这玩意儿是ext2就有的老东西了,但是笔者没有用直接块,这是为了“节省空间”(为节省60字节而使用一个块?)

比如,你把第一个数据块保存到了块1234,第一间接块的第一个就是1234,inode的第一间接块就是第一间接块的块号码。

第二间接块中保存的就是(4096 / 4 = 1024)个第一间接块(笔者还没来得及实现,不过第一间接块暂时够用了,他可以撑4MB的文件),第三间接块就是1024个第二间接块。
照这样算,第一间接块可以保存1024 * 4096 = 4096KB = 4MB的文件,第二间接块可以保存1024 * 4096 * 1024 = 4GB的文件,第三间接块就可以保存1024 * 4096 * 1024 * 1024 = 4TB的文件,笔者家的硬盘加起来还只有他的1/4,可以够用50年了。

代码

接下来,咱们来搞一搞笔者几个月来写的这堆代码。

程序入口: main(argc, argv)

程序的入口是大家很熟悉的main函数(是不是有点失望没用汇编写……),他干的事情如下:

  • 打开 设备文件

  • 解析 命令行

  • 如果 命令行 令 格式化
    调用 cfs_format格式化设备为CFS

  • 否则
    调用 cfs_init挂载磁盘

调用 cfs_create创建文件newfile.txt
写入 newfile.txt
读取 newfile.txt

很简单,而且没有涉及到一点点技术,笔者就直接懒得说了。

格式化: cfs_format(fd)

格式化函数叫做cfs_format,他其实很简单,就是获取设备信息,然后写入到超级块。

  • 获取设备大小
  • 写入 超级块
  • 写入 块位图
  • 初始化 根目录

关于初始化根目录这一块儿(cfs.c 121 - 175),笔者要说一点。当inode = 2的时候,笔者怎么调用cfs_write_block都无法写入(准确说,写入后无法读取),使用inode 3就可以,一点问题也没有。所以,笔者就索性创建了新文件zero来替代/dev/zero。

然后,就很简单了,没得说了(其实是笔者有点儿累了)

挂载: cfs_init(fd)

这个函数其实不牵扯到内核的挂载,只是起了这个听起来好听的名字而已,其实就是将文件系统的数据读取出来罢了。

哀,懒得说了,大家自己看吧,只要把数据按顺序读取出来就好了。

同步: cfs_writeback(fd)

这个函数和sync函数差不多,都是同步文件系统,也就是将内存中的数据写入,刚好是cfs_init的反操作。

创建: cfs_create(dir, filename, mode)

创建文件的函数没有叫做creat,而是叫做正常的名字create,他干了这些事情:

  • 寻找 空闲的inode号码
  • 写入 inode

写入: cfs_write_block(fd, inode, block_pos, buffer, size)

终于有个有难度的函数了。
这个函数用来写入文件,和unistd.h中的系统调用write原型差不多,他干的事情看起来很简单:

  • 判断 应该写入第几间接块
  • 如果 第一间接块
    1. 如果 第一次使用第一间接块

      1. 初始化 inode->第一间接块指针
    2. 初始化 第一间接块的索引

    3. 读取 第一间接块

    4. 令 第一间接块 的 第一间接块的索引 个 为 空闲块

    5. 写入 第一间接块

    6. 写入 数据块

首先,第一间接块的索引就是第几个块(block_pos)。
写入第一间接块和数据块的顺序可以换,但是笔者觉得这样顺手,就写成这样了。

写入,笔者都是用的包装函数write_block读取也都是read_block。这两个函数干的事儿差不多。

  • 保存 当前文件位置
  • 令 当前文件位置 为 块号码
  • 写入/读取 数据
  • 还原 当前文件位置

读取: cfs_read_block(fd, inode, block_pos, buffer, size)

这里的size和上面cfs_write_block不太一样,这里是指最大,应该写作max_size的,可能是笔者大意了。

  • 判断 应该读取第几间接块
  • 如果 第一间接块
    1. 初始化 第一间接块的索引
    2. 读取 第一间接块
    3. 读取 数据块

怎么结尾儿呢,还是老话吧。

你学会了吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值