自己编写UNIX文件系统

近日有人求助,要写一个UNIX文件系统作为暑假作业。这种事情基本是学操作系统的必须要做的或者是做过的,毕竟文件系统是操作系统课程的一个重要组成部分。要实现这个UNIX文件系统,很多人就扎进了UNIX V6的的系统源码,以及《莱昂氏UNIX源代码分析》和《返璞归真:UNIX技术内幕》这两本书,很多人出来了,很多人在里面迷失了...最终忘了自己只是要实现一个UNIX文件系统而已。

为何会迷失,因为代码不是自己写的,而且年代久远,编程理念不同了,作者为何那样写不一定就能理解,实际上对于任何别人写的代码,总是会有一些不易理解的地方,当然,如果作者水平超级高,那么代码也就相对容易理解。因此,写代码永远比读代码要容易!既然是要写一个文件系统,为何要用现成的UNIX V6代码呢?如果理解了UNIX文件的布局和结构,自己从零开始不参考任何现有的代码做一个也不是什么难事,最根本的是UNIX文件系统本身,至于说代码,仅仅是一个实现与用户操作的一个接口而已。如果代码是自己一点一点写的,那么你肯定能彻底明白每一行的每一个语句的精确含义,至于为何这么写,你当然及其明了!

本文留下我仓促间几个小时写的一个类UNIX文件系统的代码,不是让别人看的,是为了自己留档,因为本文已经说了,看别人的代吗只能学习经验,不能理解本质,更何况,你看的还得是超级一流的代码,而我写的,则是超级垃圾的代码。我只想说,理解问题的本质要比代码重要得多,代码,码,并不是很多人想象中的那般重要!本文的实现基于Linux系统,即在Linux系统上编写一个用户态程序,实现UNIX文件的IO接口以及操作。

推荐阅读:

我一向坚持的原则,那就是任何东西的根本性的,本质上的原理以及背后的思想都是及其简单的,所谓的复杂性都是优化与策略化的扩展带来的,正如TCP一样,UNIX的文件系统也不例外!我们必须知道,什么是最根本的,什么是次要的。对于UNIX文件系统,最根本的就是其布局以及其系统调用接口,一个处在最低层,一个在最上层开放给用户,如下所示:

系统调用接口:open,write,read,close...

文件系统布局:引导快,超级块,inode区表,数据块区

所有的在二者中间的部分都是次要的,也就是说那些东西不要也行,比如高速缓冲,高速缓存,VFS层,块层...因此在我的实现代码中,并没有这些东西,我所做到的,仅仅是UNIX文件系统所要求必须做到的最小集,那就是:

面对一个按照UNIX文件系统标准布局的“块设备”,可以使用open,read,write等接口进行IO操作。

在实现中,我用一个标准的Linux大文件来模拟磁盘块,这样块的操作基本都映射到了Linux标准的write,read等系统调用了。

首先定义必要的结构体:

//超级块结构
struct filesys {
        unsigned int s_size;        //总大小                                         
        unsigned int s_itsize;        //inode表大小                                       
        unsigned int s_freeinodesize;    //空闲i节点的数量                         
        unsigned int s_nextfreeinode;    //下一个空闲i节点
        unsigned int s_freeinode[NUM];    //空闲i节点数组
        unsigned int s_freeblocksize;    //空闲块的数量         
        unsigned int s_nextfreeblock;    //下一个空闲块
        unsigned int s_freeblock[NUM];    //空闲块数组
        unsigned int s_pad[];        //填充到512字节 
};
//磁盘inode结构
struct finode {
        int fi_mode;            //类型:文件/目录
        int fi_uid_unused;        //uid,由于和进程无关联,仅仅是模拟一个FS,未使用,下同
        int fi_gid_unused;
        int fi_nlink;            //链接数,当链接数为0,意味着被删除
        long int fi_size;        //文件大小
        long int fi_addr[BNUM];        //文件块一级指针,并未实现多级指针
        time_t  fi_atime_unused;    //未实现
        time_t  fi_mtime_unused;
};
//内存inode结构
struct inode {
        struct finode  i_finode;
        struct inode    *i_parent;    //所属的目录i节点
        int    i_ino;            //i节点号
        int    i_users;        //引用计数
};
//目录项结构(非Linux内核的目录项)
struct direct
{
        char d_name[MAXLEN];        //文件或者目录的名字
        unsigned short d_ino;        //文件或者目录的i节点号
};
//目录结构
struct dir
{
        struct direct direct[DIRNUM];    //包含的目录项数组
        unsigned short size;        //包含的目录项大小   
};
//抽象的文件结构
struct file {
        struct inode *f_inode;        //文件的i节点
        int f_curpos;            //文件的当前读写指针
};

之所以叫做类UNIX文件系统,是因为我并没有去精确确认当时的UNIX文件系统的超级块以及inode表的结构,只是大致的模仿其布局,比如超级块中字段,以及字段的顺序可能和标准的UNIX文件系统并不完全一致。但是不管怎么说,当时的UNIX文件系统基本就是这个一个样子。另外,可以看到file结构体内容及其少,因为本质上,我只是想表示“一个inode节点相对于一个读写者来说,就是一个file”,仅此而已。接下来就是具体的实现了,我的方式是自下而上的,这样做的好处在于便于今后的扩展。那么首先要完成的就是i节点的分配和释放了,我的实现中,是将文件i节点映射到了内存i节点,这样或许违背了我的初衷,我不是说过不要那么多“额外”的东西来扰乱视听的吗?是的,然而比起那些所谓的额外的优化,我更不喜欢频繁的调用read和write。反正,只要自己能控制住局面即可。

在实现中,还有一个大事就是内存的分配与释放,这些也不是本质的,记住,要实现的仅仅是一个UNIX文件系统,其它的能绕开则绕开!显然malloc,free等也是我们要绕开的,于是我基本都使用预分配空间的东西-全局数组。以下是全局变量:

//内存i节点数组,NUM为该文件系统容纳的文件数
struct inode g_usedinode[NUM];
//ROOT的内存i节点
struct inode *g_root;
//已经打开文件的数组
struct file* g_opened[OPENNUM];
//超级块
struct filesys *g_super;
//模拟二级文件系统的Linux大文件的文件描述符
int g_fake_disk = -1;

相关推荐