NFS 文件系统源代码剖析

NFS 文件系统概述

NFS(Network File System,网络文件系统)是一种基于网络的文件系统。它可以将远端服务器文件系统的目录挂载到本地文件系统的目录上,允许用户或者应用程序像访问本地文件系统的目录结构一样,访问远端服务器文件系统的目录结构,而无需理会远端服务器文件系统和本地文件系统的具体类型,非常方便地实现了目录和文件在不同机器上进行共享。虽然 NFS 不是唯一实现这个功能的文件系统,但它无疑是最成功一个。

NFS 的第一个版本是 SUN Microsystems 在 20 世纪 80 年代开发出来的,至今为止,NFS 经历了 NFS,NFSv2,NFSv3 和 NFSv4 共四个版本。现在,NFS 最新的版本是 4.1,也被称为 pNFS(parallel NFS,并行网络文件系统)。

前四个版本的 NFS,作为一个文件系统,它几乎具备了一个传统桌面文件系统最基本的结构特征和访问特征,不同之处在于它的数据存储于远端服务器上,而不是本地设备上,因此不存在磁盘布局的处理。NFS 需要将本地操作转换为网络操作,并在远端服务器上实现,最后返回操作的结果。因此,NFS 更像是远端服务器文件系统在本地的一个文件系统代理,用户或者应用程序通过访问文件系统代理来访问真实的文件系统。

众所周知的是,NFS 的客户端在访问远端服务器文件系统时,既需要通过服务器获得文件的属性信息,还需要通过服务器获得文件的数据信息,这使得 NFS 天然地具备将文件的属性信息和数据信息分离在不同服务器上进行访问的特性,于是最后一个版本 NFS4.1/pNFS,将 Lustre/CephFS/GFS 等集群文件系统的设计思想引入到自身中,成为一个具有里程碑意义的 NFS 版本。它使得 NFS 的数据吞吐的速度和规模都得到了极大提高,为 NFS 的应用带了更为广阔的空间。

NFS 之所以备受瞩目,除了它在文件共享领域上的优异表现外,还有一个关键原因在于它在 NAS 存储系统上应用。NAS 与 DAS 和 SAN 在存储领域的竞争中,NFS 发挥了积极的作用,这更使得 NFS 越来越值得关注。

NFSv3 源代码结构

相比之前的两个版本,NFSv3 是一个较为稳定和成熟的 NFS 版本,而之后的 NFSv4 除了在安全和性能上有所提高外,还在网络连接中加入了状态属性,因此显得复杂一些。在此,本文以 NFSv3 为例来剖析 NFS 文件系统的源代码结构,所用源码来自 Linux 2.4.9 内核。

按照 NFS 文件系统的设计与实现,NFS 文件系统主要分为三个部分:The Protocol(网络协议),Client Side(NFS 客户端)和 Server Side(NFS 服务器)。NFS 客户端提供了接口,保证用户或者应用程序能像访问本地文件系统一样访问 NFS 文件系统,NFS 服务器作为数据源,为 NFS 客户端提供真实的文件系统服务,而网络协议则使得 NFS 客户端和 NFS 服务器能够高效和可靠地进行通信。NFS 网络协议使用的是 RPC(Remote Procedure Call,远程过程调用)/XDR(External Data Representation,外部数据表示)机制,因此本文将剖析的重点放在 NFS 客户端和 NFS 服务器上。

Client Side 源代码

Client Side 的头文件在 include/linux/ 下面,C 文件在 fs/nfs 下面。

  • dir.c/file.c/inode.c/symlink.c/unlink.c:与文件操作相关的系统调用
  • read.c/write.c/flushd.c:文件读写
  • mount_clnt.c/nfs_root.c:将 NFS 文件系统作为 root 目录的相关实现
  • proc.c/nfs2xdr.c/nfs3proc.c/nfs3xdr.c:网络数据交换

与文件操作相关的系统调用都在 struct file_operations,struct inode_operations 这两个数据结构里面定义。文件的读操作 nfs_file_read 和写操作 nfs_file_write 被单独提出来,因为文件读写性能将直接关系到文件系统的成败,本文在后面会重点阐述其实现。

Server Side 源代码

Server Side 的头文件在 include/linux/nfsd 下面,C 文件在 fs/nfsd 下面。

  • auth.c/lockd.c/export.c/nfsctl.c/nfscache.c/nfsfh.c/stats.c:导出目录的访问管理
  • nfssvc.c:NFS 服务 deamon 的实现
  • vfs.c:将 NFS 文件系统的操作转换成具体文件系统的操作
  • nfsproc.c/nfsxdr.c/nfs3proc.c/nfs3xdr.c:网络数据交换

导出目录的访问管理主要解决网络文件系统实现面临的几个重要问题,包括目录导出服务,外部访问的权限控制,多客户端以及客户端与服务器的文件并发操作等。

一个典型例子:rename 的调用过程

在 NFS 文件系统的文件操作中,除了 read 和 write 操作考虑到性能因素,专门使用了缓存机制外,其它的操作基本上都是同步完成的。本文以 rename 为例来进行说明,如下图所示。首先用户或者应用程序开始调用文件操作,经过系统调用 sys_rename,到达虚拟文件系统层 vfs_rename,然后交给 NFS 文件系统 nfs_rename 来处理。NFS 文件系统无法操作存储介质,它调用 NFS 客户端函数 nfs3_proc_rename 和 NFS 服务器函数 nfsd3_proc_rename 进行通信,把文件操作转发到 NFS 服务器的虚拟文件系统层 vfs_rename,最后调用具体的文件系统如 ext2 的函数 ext2_raname,完成文件重命名。


图 1. rename 调用过程
图 1. rename 调用过程  

与传统文件系统相同点

在阐述 NFS 文件系统与传统桌面文件系统的相同点之前,我们首先简要回顾一下 Linux 操作系统上文件系统的体系结构。按照 Linux 文件系统剖析的划分,Linux 文件系统从上至下主要由虚拟文件系统层,特定文件系统层和页高速缓存层三部分组成,如下图所示。当然,这种划分并不是一定的,例如在执行直接 I/O 调用时,是不需要进行页高速缓存的,另外,对于块设备的读写,进行页高速缓存之后还会有通用块层和 I/O 调度层的处理。


图 2. 文件系统体系结构
图 2. 文件系统体系结构  

用户或者应用程序通过统一的系统调用接口对文件系统进行操作,然后系统调用进入虚拟文件系统层,虚拟文件系统根据文件系统类型,调用特定文件系统的操作函数。对用户和应用程序来说,由于接口完全相同,因此用户感觉不到差异,应用程序也可以无缝地移植到 NFS 文件系统上。Linux 通过一组对象对文件系统的操作,这组对象是 superblock(超级块对象),inode(索引节点对象),dentry(目录项对象)和 file(文件对象),如下图所示。所有文件系统都支持这些对象,正是因为它们,VFS 层可以对 NFS 和其它文件系统一视同仁,只管调用这些对象的数据和函数指针,把具体的文件系统数据布局和操作都留给特定的文件系统来完成。


图 3. VFS 对象
图 3. VFS 对象  

NFS 与其它文件系统一样,向内核声明和注册自己的文件系统类型。

			
 static DECLARE_FSTYPE(nfs_fs_type, "nfs", nfs_read_super, FS_ODD_RENAME); 
 ... ... 
 module_init(init_nfs_fs) 
 module_exit(exit_nfs_fs)

同样,NFS 也需要根据自己的文件类型设置相应的文件操作函数。如果是正规文件,需要设置 inode 操作函数,file 操作函数,以及 address_space 操作函数;如果是目录文件,需要设置 inode 操作函数,file 操作函数;如果是链接,则只需设置 inode 操作函数。

			
 static void 
 nfs_fill_inode(struct inode *inode, struct nfs_fh *fh, struct nfs_fattr *fattr) 
 { 
     ... ... 
     inode->i_op = &nfs_file_inode_operations; 
     if (S_ISREG(inode->i_mode)) { 
          inode->i_fop = &nfs_file_operations; 
          inode->i_data.a_ops = &nfs_file_aops; 
     } else if (S_ISDIR(inode->i_mode)) { 
          inode->i_op = &nfs_dir_inode_operations; 
          inode->i_fop = &nfs_dir_operations; 
     } else if (S_ISLNK(inode->i_mode)) 
          inode->i_op = &nfs_symlink_inode_operations; 
     else 
          init_special_inode(inode, inode->i_mode, fattr->rdev); 
     ... ... 
 }

与传统文件系统不同点

与内存文件系统,闪存文件系统和磁盘文件系统这些本地文件系统最大的不同在于,NFS 文件系统的数据是基于网络,而不是基于存储设备的,因此 NFS 文件系统在设计自己的 inode 和 superblock 数据结构,以及实现文件操作函数时,无需考虑数据布局情况。同样是因为基于网络,NFS 文件系统的权限控制和并发访问的要求比本地文件系统更高,读写的缓存机制也大大有别于本地文件系统。

superblock 和 inode


清单 1. NFS 的 superblock 定义
				
 struct rpc_clnt * 	 client; 			 /* RPC 客户端句柄 */ 
 struct nfs_rpc_ops * 	 rpc_ops; 		 /* RPC 客户端函数向量表 */ 
 int 			 flags; 			 /* 标识信息 */ 
 unsigned int 		 rsize; 			 /* 每次读请求的最小数据量 */ 
 unsigned int 		 rpages; 			 /* 每次读请求的最小数据量(以页为单位)*/ 
 unsigned int 		 wsize; 			 /* 每次写请求的最小数据量 */ 
 unsigned int 		 wpages; 			 /* 每次写请求的最小数据量(以页为单位)*/ 
 unsigned int 		 dtsize; 			 /* 每次读目录信息的最小数据量 */ 
 unsigned int 		 bsize; 			 /* NFS 服务器端的块大小 */ 
 unsigned int 		 acregmin; 		 /* 正规文件在缓存中驻留的最小允许时间 */ 
 unsigned int 		 acregmax; 		 /* 正规文件在缓存中驻留的最大允许时间 */ 
 unsigned int 		 acdirmin; 		 /* 目录文件在缓存中驻留的最小允许时间 */ 
 unsigned int 		 acdirmax; 		 /* 目录文件在缓存中驻留的最大允许时间 */ 
 unsigned int 		 namelen; 		 /* NFS 服务器端的主机名称最大长度 */ 
 char * 			 hostname; 		 /* NFS 服务器端的主机名称 */ 
 struct nfs_reqlist * 	 rw_requests; 		 /* 异步读写请求队列信息 */


清单 2. NFS 的 inode 定义
				
 __u64 fsid;                       	 /* 根目录(导出目录)信息 */ 
 __u64 fileid;                     	 /* 当前文件信息 */ 
 struct nfs_fh 		 fh; 		 /* 文件句柄 */ 
 ... ... 
 struct list_head 	 read; 		 /* 读数据页队列 */ 
 struct list_head 	 dirty; 		 /* 脏数据页队列 */ 
 struct list_head 	 commit; 		 /* 提交数据页队列 */ 
 struct list_head 	 writeback; 	 /* 写回数据页队列 */ 
 unsigned int 		 nread, 		 /* 读数据页数量 */ 
			 ndirty, 		 /* 脏数据页数量 */ 
			 ncommit, 	 /* 提交数据页数量 */ 
			 npages; 		 /* 写回数据页数量 */ 
 ... ...

以上省略了 superblock 和 inode 定义的公共部分,列出的仅是 NFS 文件系统 superblock 和 inode 定义的私有部分,因为只有这些私有定义才能体现出文件系统的设计原则。从这些定义可以看出,私有部分数据结构里面主要包含网络连接和读写请求两个方面相关的信息。superblock 里 client 定义了 RPC 协议的客户端连接状态,rpc_ops 定义了 RPC 协议的客户端入口函数,如 nfs3_proc_read,nfs3_proc_write,nfs3_proc_create 等。inode 里 fh 是 NFS 客户端和 NFS 服务器相互传递的关键参数,4 个页队列用于进行读写缓存,随后两小节将分别予以介绍。

file handle

file handle(fh 或者 fhandle)在 NFS 客户端和 NFS 服务器之间相互传递,建立 NFS 客户端的 inode 和 NFS 服务器的 inode 的关联关系。它主要表征的是 NFS 服务器上 inode 和物理设备的信息。file handle 对于 NFS 客户端来说是透明的,NFS 客户端不需要知道它的具体内容。file handle 在 NFS 客户端的定义是 66 个字节,前两个字节组成一个无符号 short 型,表示 file handle 的大小,后 64 个字节组成数据区,存储 file handle 的内容。

 #define NFS_MAXFHSIZE 		 64 
 struct nfs_fh { 
	 unsigned short 		 size; 
	 unsigned char 		 data[NFS_MAXFHSIZE]; 
 };

file handle 在 NFS 服务器的定义由 knfsd_fh 数据结构表示,fh_size 表示 file handle 的大小,数据区 fh_base 是一个联合体,有 fh_old,fh_pad,fh_new 三种定义,最大也是 64 个字节。考虑到当前的 NFS 版本是 v3,只看 fh_new 的定义。fh_version 表示 fh_new 定义的版本,当前版本是 1。fh_auth_type 表示认证方式,0 表示不认证。fh_fsid_type 表示根目录(即导出目录)的信息存储方式,如果是 0,那么从 fh_auth 开始前 2 个字节表示根目录所在设备的 major 号,后 2 个字节表示根目录所在设备的 minor 号,随后的 4 个字节表示根目录的 inode 索引号。fh_fileid_type 表示当前文件的信息存储方式,如果是 1,那么在表示完 fh_fsid 后,紧接着 4 个字节表示当前文件的 inode 索引号,之后 4 个字节表示当前文件的 inode generation 号。

 struct nfs_fhbase_new { 
	 __u8 		 fb_version; 	 /* == 1, even => nfs_fhbase_old */ 
	 __u8 		 fb_auth_type; 
	 __u8 		 fb_fsid_type; 
	 __u8 		 fb_fileid_type; 
	 __u32 		 fb_auth[1]; 
 };

read 和 write

前面介绍文件系统体系结构的时候,将它分为了虚拟文件系统,特定文件系统和页高速缓存三个层次。NFS 文件系统使用了这三个层次的功能,它本身完成了特定文件系统的功能,同时既为虚拟文件系统提供了完整的调用接口,也用到了页高速缓存来提高读写性能。就层次划分而言,与传统桌面文件系统相比,NFS 文件系统的读写操作不再需要通用块层和 I/O 调度层,而是使用了多个列表以及相关操作来进一步缓存数据,增强读写效率。当然 NFS 文件系统也不再使用存储设备驱动,而是通过网络协议来获取和提交数据。


图 4. 读写缓存机制
图 4. 读写缓存机制  

如上图所示,NFS 文件系统使用 read,writeback,dirty 和 commit 四个队列,每个队列的单元数据结构都是 nfs_page,每个 nfs_page 都有一个 page 变量指向页高速缓存。读方法 nfs_readpage 首先使用异步方式读取数据,如果异步方式失效,才使用同步方式,nfs_readpage_async 所读的数据都进入 read 队列中。写方法 nfs_writepage 如果写数据超过一页(缺省是 4096 字节),使用异步方式提交数据,否则使用同步方式。nfs_writepage_async 所写的数据首先进入 writeback 队列,如果数据发生更改,则进入 dirty 队列,如果将更改的数据提交到 NFS 服务器上,则进入 commit 队列。这些队列或者因为超时,或者因为单元数量多于最大值,将被释放掉。

权限认证和并发锁

NFSv3 版本使用 nfs_permission 做用户权限认证,用 nfs_revalidate 做文件合法性检查。前者调用 access 系统调用同步完成,后者调用 getattr 同步完成。为了使多个 NFS 客户端或者 NFS 客户端与 NFS 服务器对相同文件可以实现并发操作,NFS 使用 NLM(network lock management,网络锁管理)协议在 NFS 服务器上对文件进行打开,读写和移除,使不同的访问都有及时和同一的语义理解。

总结

本文分析了 NFS 文件系统的设计,主要分为三个部分,NFS 客户端,NFS 服务器和网络协议,并阐述了三者的功能划分,介绍了它们是如何组织起来,为用户或者应用程序提供文件服务。进一步的,本文使用 Linux 2.4.9 内核剖析了 NFSv3 的源代码实现,从源代码层次说明了 NFS 文件系统的实现细节,重点介绍了它与传统桌面文件系统的相同和不同之处,使读者能够深入理解 NFS 文件系统的本质。pNFS 是 NFS 文件系统从桌面型文件系统到集群型文件系统的一个转折性版本,读者可自行阅读 pNFS 的源代码实现。在阅读之前,推荐读者首先阅读 Luster/CephFS/GFS 等文件系统相关的论文和资料,以便对集群文件系统的设计架构有个基本的认识。

#include <stdio.h> #include <memory.h> #include <string> #include <iostream> using namespace std; //1代表普通文件2代表目录文件0表示空文件 #define GENERAL 1 #define DIRECTORY 2 #define HXSH 0 struct FCB { char fname[16]; //文件名 char type; int size; //文件大小 int fatherBlockNum; //当前的父目录盘块号 int currentBlockNum; //当前的盘块 void initialize() { strcpy(fname,"\0"); type = HXSH; size =0; fatherBlockNum = currentBlockNum = 0; } }; /*常量设置*/ const char* FilePath = "C:\\myfiles"; const int BlockSize = 512; //盘块大小 const int OPEN_MAX = 5; //能打开最多的文件数 const int BlockCount = 128; //盘块数 const int DiskSize = BlockSize*BlockCount; //磁盘大小 const int BlockFcbCount = BlockSize/sizeof(FCB);//目录文件的最多FCB数 int OpenFileCount = 0; struct OPENLIST //用户文件打开表 { int files; //当前打开文件数 FCB f[OPEN_MAX]; //FCB拷贝 OPENLIST() { files=0; for(int i=0;i<OPEN_MAX;i++){ f[i].fatherBlockNum=-1;//为分配打开 f[i].type=GENERAL; } } }; /*-------------目录文件结构---------------*/ struct dirFile { struct FCB fcb[BlockFcbCount]; void init(int _FatherBlockNum,int _CurrentBlockNum,char *name)//父块号,当前块号,目录名 { strcpy(fcb[0].fname,name); //本身的FCB fcb[0].fatherBlockNum=_FatherBlockNum; fcb[0].currentBlockNum=_CurrentBlockNum; fcb[0].type=DIRECTORY; //标记目录文件 for(int i=1;i<BlockFcbCount;i++){ fcb[i].fatherBlockNum=_CurrentBlockNum; //标记为子项 fcb[i].type=HXSH; // 标记为空白项 } } }; /**********************************************************************/ struct DISK { int FAT1[BlockCount]; //FAT1 int FAT2[BlockCount]; //FAT2 struct dirFile root; //根目录 char data[BlockCount-3][BlockSize]; void format() { memset(FAT1,0,BlockCount); //FAT1 memset(FAT2,0,BlockCount); //FAT2 FAT1[0]=FAT1[1]=FAT1[2]=-2; //0,1,2盘块号依次代表FAT1,FAT2,根目录区 FAT2[0]=FAT2[1]=FAT2[2]=-2; //FAT作备份 root.init(2,2,"C:\\");//根目录区 memset(data,0,sizeof(data));//数据区 } }; /*-----------------全局变量--------------------------*/ FILE *fp; //磁盘文件地址 char * BaseAddr; //虚拟磁盘空间基地址 string currentPath="C:\\\\"; //当前路径 int current=2; //当前目录的盘块号 string cmd; //输入指令 struct DISK *osPoint; //磁盘操作系统指针 char command[16]; //文件名标识 struct OPENLIST* openlist; //用户文件列表指针 /*-----------函数事先申明--------------------*/ int format(); int mkdir(char *sonfname); int rmdir(char *sonfname); int create(char *name); int listshow(); int delfile(char *name); int changePath(char *sonfname); int write(char *name); int exit(); int open(char *file); int close(char *file); int read(char *file); /*------------初始化-----------------------*/ int format() { current = 2; currentPath="C:\\\\"; //当前路径 osPoint->format();//打开文件列表初始化 delete openlist; openlist=new OPENLIST; /*-------保存到磁盘上myfiles--------*/ fp = fopen(FilePath,"w+"); fwrite(BaseAddr,sizeof(char),DiskSize,fp); fclose(fp); printf("----------------------------------------------------------\n\n"); return 1; } /*-----------------------创建子目录-------------------*/ int mkdir(char *sonfname) { //判断是否有重名 //寻找空白子目录项 //寻找空白盘块号 //当前目录下增加该子目录项 //分配子目录盘块,并且初始化 //修改fat表 int i,temp,iFAT; struct dirFile *dir; //当前目录的指针 if(current==2) dir=&(osPoint->root); else dir=(struct dirFile *)(osPoint->data [current-3]); /*--------为了避免该目录下同名文件夹--------*/ for(i = 1;i<BlockFcbCount;i++) { if(dir->fcb[i].type==DIRECTORY && strcmp(dir->fcb[i].fname,sonfname)==0 ) { printf("该文件夹下已经有同名的文件夹存在了!\n"); return 0; } } //查找空白fcb序号 for(i=1;i<BlockFcbCount;i++) { if(dir->fcb[i].type==HXSH) break; } if(i==BlockFcbCount) { printf("该目录已满!请选择新的目录下创建!\n"); return 0; } temp=i; for(i = 3;i < BlockCount;i++) { if(osPoint->FAT1[i] == 0) break; } if(i == BlockCount) { printf("磁盘已满!\n"); return 0; } iFAT=i; /*-------------接下来进行分配----------*/ osPoint->FAT1[iFAT]=osPoint->FAT2[iFAT] = 2; //2表示分配给下级目录文件 //填写该分派新的盘块的参数 strcpy(dir->fcb[temp].fname,sonfname); dir->fcb[temp].type=DIRECTORY; dir->fcb[temp].fatherBlockNum=current; dir->fcb[temp].currentBlockNum=iFAT; //初始化子目录文件盘块 dir=(struct dirFile*)(osPoint->data [iFAT-3]); //定位到子目录盘块号 dir->init (current,iFAT,sonfname);//iFAT是要分配的块号,这里的current用来指要分配的块的父块号 printf("---------------------------------------------------------------\n\n"); return 1; } /*-------删除当前目录下的文件夹--------*/ int rmdir(char *sonfname) { int i,temp,j;//确保当前目录下有该文件,并记录下该FCB下标 struct dirFile *dir; //当前目录的指针 if(current==2) dir=&(osPoint->root); else dir=(struct dirFile *)(osPoint->data [current-3]); for(i=1;i<BlockFcbCount;i++) { //查找该目录文件 if(dir->fcb[i].type==DIRECTORY && strcmp(dir->fcb[i].fname,sonfname)==0) { break; } } temp=i; if(i==BlockFcbCount) { printf("当前目录下不存在该子目录!\n"); return 0; } j = dir->fcb[temp].currentBlockNum; struct dirFile *sonDir; //当前子目录的指针 sonDir=(struct dirFile *)(osPoint->data [ j - 3]); for(i=1;i<BlockFcbCount;i++) //查找子目录是否为空目录 { if(sonDir->fcb[i].type!=HXSH) { printf("该文件夹为非空文件夹,为确保安全,请清空后再删除!\n"); return 0; } } /*开始删除子目录操作*/ osPoint->FAT1[j] = osPoint->FAT2[j]=0; //fat清空 char *p=osPoint->data[j-3]; //格式化子目录 memset(p,0,BlockSize); dir->fcb[temp].initialize(); //回收子目录占据目录项 printf("---------------------------------------------------------------\n\n"); return 1; } /*-----------在当前目录下创建文本文件---------------*/ int create(char *name) { int i,iFAT;//temp, int emptyNum = 0,isFound = 0; //空闲目录项个数 struct dirFile *dir; //当前目录的指针 if(current==2) dir=&(osPoint->root); else dir=(struct dirFile *)(osPoint->data [current-3]); //查看目录是否已满 //为了避免同名的文本文件 for(i=1;i<BlockFcbCount;i++) { if(dir->fcb[i].type == HXSH && isFound == 0) { emptyNum = i; isFound = 1; } else if(dir->fcb[i].type==GENERAL && strcmp(dir->fcb[i].fname,name)==0 ) { printf("无法在同一目录下创建同名文件!\n"); return 0; } } if(emptyNum == 0) { printf("已经达到目录项容纳上限,无法创建新目录!\n"); return 0; } //查找FAT表寻找空白区,用来分配磁盘块号j for(i = 3;i<BlockCount;i++) { if(osPoint->FAT1[i]==0) break; } if(i==BlockCount) { printf("磁盘已满!\n"); return 0; } iFAT=i; /*------进入分配阶段---------*/ //分配磁盘块 osPoint->FAT1[iFAT] = osPoint->FAT2[iFAT] = 1; /*-----------接下来进行分配----------*/ //填写该分派新的盘块的参数 strcpy(dir->fcb[emptyNum].fname,name); dir->fcb[emptyNum].type=GENERAL; dir->fcb[emptyNum].fatherBlockNum=current; dir->fcb[emptyNum].currentBlockNum=iFAT; dir->fcb[emptyNum].size =0; char* p = osPoint->data[iFAT -3]; memset(p,4,BlockSize); printf("----------------------------------------------------------------\n\n"); return 1; } /*-------查询子目录------------*/ int listshow() { int i,DirCount=0,FileCount=0; //搜索当前目录 struct dirFile *dir; //当前目录的指针 if(current==2) dir=&(osPoint->root); else dir=(struct dirFile *)(osPoint->data [current-3]); for(i=1;i<BlockFcbCount;i++) { if(dir->fcb[i].type==GENERAL) { //查找普通文件 FileCount++; printf("%s 文本文件.\n",dir->fcb[i].fname); } if(dir->fcb[i].type==DIRECTORY) { //查找目录文件 DirCount++; printf("%s 文件夹.\n",dir->fcb[i].fname); } } printf("\n该目录下共有 %d 个文本文件, %d 个文件夹\n\n",FileCount,DirCount); printf("--------------------------------------------------------\n\n"); return 1; } /*---------在当前目录下删除文件-----------*/ int delfile(char *name) { int i,temp,j; //确保当前目录下有该文件,并且记录下它的FCB下标 struct dirFile *dir; //当前目录的指针 if(current==2) dir=&(osPoint->root); else dir=(struct dirFile *)(osPoint->data [current-3]); for(i=1;i<BlockFcbCount;i++) //查找该文件 { if(dir->fcb[i].type==GENERAL && strcmp(dir->fcb[i].fname,name)==0) { break; } } if(i==BlockFcbCount) { printf("当前目录下不存在该文件!\n"); return 0; } int k; for(k=0;k<OPEN_MAX;k++) { if((openlist->f [k].type = GENERAL)&& (strcmp(openlist->f [k].fname,name)==0)) { if(openlist->f[k].fatherBlockNum == current) { break; } else { printf("该文件未在当前目录下!\n"); return 0; } } } if(k!=OPEN_MAX) { close(name); } //从打开列表中删除 temp=i; /*开始删除文件操作*/ j = dir->fcb [temp].currentBlockNum ; //查找盘块号j osPoint->FAT1[j]=osPoint->FAT2[j]=0; //fat1,fat2表标记为空白 char *p=osPoint->data[j - 3]; memset(p,0,BlockSize); //清除原文本文件的内容 dir->fcb[temp].initialize(); //type=0; //标记该目录项为空文件 printf("------------------------------------------------------------\n\n"); return 1; } /*--------------进入当前目录下的子目录--------------*/ int changePath(char *sonfname) { struct dirFile *dir; //当前目录的指针 if(current==2) dir=&(osPoint->root); else dir=(struct dirFile *)(osPoint->data [current-3]); /*回到父目录*/ if(strcmp(sonfname,"..")==0) { if(current==2) { printf("你现已经在根目录下!\n"); return 0; } current = dir->fcb[0].fatherBlockNum ; currentPath = currentPath.substr(0,currentPath.size() - strlen(dir->fcb[0].fname )-1); return 1; } /*进入子目录*/ int i,temp; //确保当前目录下有该目录,并且记录下它的FCB下标 for(i = 1; i < BlockFcbCount; i++) { //查找该文件 if(dir->fcb[i].type==DIRECTORY && strcmp(dir->fcb[i].fname,sonfname)==0 ) { temp=i; break; } } if(i==BlockFcbCount) { printf("不存在该目录!\n"); return 0; } //修改当前文件信息 current=dir->fcb [temp].currentBlockNum ; currentPath = currentPath+dir->fcb [temp].fname +"\\"; printf("-------------------------------------------------------------\n\n"); return 1; } /*--------System exit---------------------*/ int exit() { //将所有文件都关闭 //保存到磁盘上C:\myfiles fp=fopen(FilePath,"w+"); fwrite(BaseAddr,sizeof(char),DiskSize,fp); fclose(fp); //释放内存上的虚拟磁盘 free(osPoint); //释放用户打开文件表 delete openlist; printf("---------------------------------------------------------\n\n"); return 1; } /*-------------在指定的文件里记录信息---------------*/ int write(char *name) { int i; char *startPoint,*endPoint; //在打开文件列表中查找 file(还需要考虑同名不同目录文件的情况!!!) for(i=0;i<OPEN_MAX;i++) { if(strcmp(openlist->f [i].fname,name)==0 ) { if(openlist->f[i].fatherBlockNum ==current) { break; } else { printf("该文件处于打开列表中,本系统只能改写当前目录下文件!\n"); return 0; } } } if(i==OPEN_MAX) { printf("该文件尚未打开,请先打开后写入信息!!\n"); return 0; } int active=i; int fileStartNum = openlist->f[active].currentBlockNum - 3 ; startPoint = osPoint->data[fileStartNum]; endPoint = osPoint->data[fileStartNum + 1]; printf("请输入文本以Ctrl D号结束:\t"); char input; while(((input=getchar())!=4)) { if(startPoint < endPoint-1) { *startPoint++ = input; } else { printf("达到单体文件最大容量!"); *startPoint++ = 4; break; } } return 1; } /*---------选择一个打开的文件读取信息----------*/ int read(char *file) { int i,fileStartNum; char *startPoint,*endPoint; //struct dirFile *dir; //在打开文件列表中查找 file(还需要考虑同名不同目录文件的情况!!!) for(i=0;i<OPEN_MAX;i++) { if(strcmp(openlist->f [i].fname,file)==0 ) { if(openlist->f[i].fatherBlockNum ==current) { break; } else { printf("该文件处于打开列表中,本系统只能阅读当前目录下文件!\n"); return 0; } } } if(i==OPEN_MAX) { printf("该文件尚未打开,请先打开后读取信息!\n"); return 0; } int active=i; //计算文件物理地址 fileStartNum = openlist->f[active].currentBlockNum - 3 ; startPoint = osPoint->data[fileStartNum]; endPoint = osPoint->data[fileStartNum + 1]; //end_dir=(struct dirFile *)[BlockSize-1]; //q=(char *)end_dir; printf("该文件的内容为: "); while((*startPoint)!=4&& (startPoint < endPoint)) { putchar(*startPoint++); } printf("\n"); return 1; } /*当前目录下添加一个打开文件*/ int open(char *file)//打开文件 { int i,FcbIndex; //确保没有打开过该文件 = 相同名字 + 相同目录 for(i=0;i<OPEN_MAX;i++) { if(openlist->f[i].type ==GENERAL && strcmp(openlist->f [i].fname,file)==0 &&openlist;->f[i].fatherBlockNum == current) { printf("该文件已经被打开!\n"); return 0; } } //确保有空的打开文件项 if(openlist->files == OPEN_MAX) { printf("打开文件数目达到上限!无法再打开新文件.\n"); return 0; } //确保当前目录下有该文件,并且记录下它的FCB下标 struct dirFile *dir; //当前目录的指针 if(current==2) dir=&(osPoint->root); else dir=(struct dirFile *)(osPoint->data [current-3]); for(i = 1;i< BlockFcbCount;i++) { //查找该文件 if(dir->fcb[i].type==GENERAL && strcmp(dir->fcb[i].fname,file)==0 ) { FcbIndex=i; break; } } if(i==BlockFcbCount) { printf("当前目录下不存在该文件!\n"); return 0; } //装载新文件进入打开文件列表,(FCB信息,文件数++) ??难道名字过不来? openlist->f[OpenFileCount] = dir->fcb[FcbIndex]; //FCB拷贝 openlist->files ++; printf("文件打开成功!\n"); OpenFileCount++; return 1; } int close(char *file) { //释放该文件所占内存 //释放用户打开文件列表表项 int i; //在打开文件列表中查找 file(还需要考虑同名不同目录文件的情况!!!) for(i=0;i<OPEN_MAX;i++) { if((openlist->f [i].type = GENERAL)&& (strcmp(openlist->f [i].fname,file)==0)) { if(openlist->f[i].fatherBlockNum == current) { break; } else { printf("该文件已打开,但未在当前目录下,无法关闭!\n"); return 0; } } } if(i==OPEN_MAX) { printf("该文件未在打开列表中!\n"); return 0; } int active=i; openlist->files --; openlist->f[active].initialize(); OpenFileCount--; printf("该文件已关闭!\n"); return 1; } int main() { /*********************************************************************/ printf("-----Welcome To My Operate System Of File(FAT)-----\n"); //使用说明书 printf("\n 以下是使用说明书:\n"); printf("--------------------------------------------------------------\n"); printf("|| format :对磁盘格式化. || \n"); printf("|| exit :安全退出该文件系统,保存信息. || \n"); printf("|| mkdir dirname :创建子目录. || \n"); printf("|| rmdir dirname :删除子目录. || \n"); printf("|| ls dirname :显示当前目录下信息. || \n"); printf("|| cd dirname :更改当前目录. || \n"); printf("|| create filename :创建一个新文件,并且打开. || \n"); printf("|| write filename :选择一个打开的文件写入信息 || \n"); printf("|| read filename :选择一个打开的文件读取信息. || \n"); printf("|| rm filename :删除文件. || \n"); printf("|| open filename :打开文件. || \n"); printf("|| close filename :关闭文件. || \n"); printf("-------------------------------------------------------------\n\n"); //创建用户文件打开表 openlist=new OPENLIST; //申请虚拟空间并且初始化 BaseAddr=(char *)malloc(DiskSize); //虚拟磁盘初始化 osPoint=(struct DISK *)(BaseAddr); //加载磁盘文件 if((fp=fopen(FilePath,"r"))!=NULL){ fread(BaseAddr,sizeof(char),DiskSize,fp); printf("加载磁盘文件( %s )成功,现在可以进行操作了!\n\n",FilePath); } else{ printf("这是你第一次使用该文件管理系统!\t正在初始化...\n"); format(); printf("初始化已经完成,现在可以进行操作了!\n\n"); } printf("--------------------------------------------------------------\n\n"); while(1){ cout<<currentPath; cin>>cmd; if(cmd=="format"){ format(); } else if(cmd=="mkdir"){ cin>>command; mkdir(command); } else if(cmd=="rmdir"){ cin>>command; rmdir(command); } else if(cmd=="ls"){ listshow(); } else if(cmd=="cd"){ cin>>command; changePath(command); } else if(cmd=="create"){ cin>>command; create(command); } else if(cmd=="write"){ cin>>command; write(command); } else if(cmd=="read"){ cin>>command; read(command); } else if(cmd=="rm"){ cin>>command; delfile(command); } else if(cmd=="open"){ cin>>command; open(command); } else if(cmd=="close"){ cin>>command; close(command); } else if(cmd=="exit"){ exit(); break; } else cout<<"无效指令,请重新输入:"<<endl; } printf("Thank you for using my file system!\n"); return 0; }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值