我的OS | 一个文件系统的设计与实现
发布于 2021-01-17 21:45
今天,笔者要设计一个类似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 int
4个字节。
至于到上一个块的距离这个笔者就要说说了,因为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
的,可能是笔者大意了。
判断 应该读取第几间接块
如果 第一间接块
初始化 第一间接块的索引
读取 第一间接块
读取 数据块
怎么结尾儿呢,还是老话吧。
你学会了吗?
本文来自网络或网友投稿,如有侵犯您的权益,请发邮件至:aisoutu@outlook.com 我们将第一时间删除。
相关素材