文件系统,是对一个存储设备上的数据和元数据进行组织的机制,它是操作系统管理持久性数据的子系统,提供数据存储和访问功能。
将一个文件系统与一个存储设备关联起来的过程叫做挂载(mount),挂载时会将一个文件系统附着到当前文件系统层次结构中(根),在执行挂载时,需要提供文件系统类型、文件系统和一个挂载点。
1.1 Nuttx文件系统介绍
Nuttx包含了一个可选的、可扩展的文件系统,这个文件系统可以完全省略掉,Nuttx不依赖于任何文件系统的存在。
伪根文件系统
可以通过将CONFIG_NFILE_DESCRIPTOS设置成非零值,来使能这个内存中的伪文件系统。它是一个内存文件系统,因为它不需要任何存储介质或块驱动程序的支持。文件系统内容是通过标准文件系统操作(open, close, read, write, etc.)实时生成的。在这个意义上,它是一个伪文件系统(Linux的/proc也称为伪文件系统)。
可以通过伪文件系统访问用户提供的任何数据或逻辑。支持对字符设备驱动及块设备驱动节点在伪文件系统任何目录中的内建,不过按照惯例,都习惯放在/dev伪文件系统目录中。文件系统挂载
简单的内存文件系统,可通过挂载块设备来扩展,这些块设备提供大容量存储设备支持以实现真正的文件系统访问。Nuttx支持标准的mount()命令,该命令允许块驱动程序将文件系统绑定到伪文件系统中的挂载点上。目前,Nuttx支持VFAT文件系统。与Linux比较
从编程的角度来看,Nuttx文件系统看起来与Linux文件系统非常类似,但是,有一个根本的区别:Nuttx根文件系统是一个伪文件系统,而真正的文件系统可以挂载在伪文件系统中;相比之下,在典型的Linux安装中,Linux根文件系统是一个真正的文件系统,伪文件系统挂载在真正的根文件系统中。Nuttx选择的方法,旨在提供从非常小的平台到中等平台等的支持,以便具备更好的可扩展性。
2.1 struct inode
inode是文件系统中最重要的结构,存放基本的数据:
struct inode{ FAR struct inode *i_peer; /* Link to same level inode */ FAR struct inode *i_child; /* Link to lower level inode */ int16_t i_crefs; /* References to inode */ uint16_t i_flags; /* Flags for inode */ union inode_ops_u u; /* Inode operations */#ifdef CONFIG_FILE_MODE mode_t i_mode; /* Access mode flags */#endif FAR void *i_private; /* Per inode driver private data */ char i_name[1]; /* Name of inode (variable) */};
i_peer和i_child会将inode组织成树状结构;
i_flags用于表明文件类型,比如Character driver/Block driver/Mount point/Special OS type/Named semaphore/Message Queue/Shared memory region/Soft link等
i_private在驱动中,通常用于存放私有数据;
union inode_ops_u u,存放对inode的操作函数集,而针对不同的inode类型,对应不同的操作函数;
2.2 union inode_ops_u
union inode_ops_u { FAR const struct file_operations *i_ops; /* Driver operations for inode */#ifndef CONFIG_DISABLE_MOUNTPOINT FAR const struct block_operations *i_bops; /* Block driver operations */ FAR const struct mountpt_operations *i_mops; /* Operations on a mountpoint */#endif#ifdef CONFIG_FS_NAMED_SEMAPHORES FAR struct nsem_inode_s *i_nsem; /* Named semaphore */#endif#ifndef CONFIG_DISABLE_MQUEUE FAR struct mqueue_inode_s *i_mqueue; /* POSIX message queue */#endif#ifdef CONFIG_PSEUDOFS_SOFTLINKS FAR char *i_link; /* Full path to link target */#endif};
主要有三个操作函数集,此外由于VFS也维护了像Named semaphores/Message Queues/Shared memory等资源,但是这些资源又不像其他类型的文件有函数操作集,算是special case,也放在这个结构中。
struct file_operations,存放对驱动的操作,一般在实现驱动程序中,都会去实现对应的函数操作;
struct file_operations{ /* The device driver open method differs from the mountpoint open method */ int (*open)(FAR struct file *filep); /* The following methods must be identical in signature and position because * the struct file_operations and struct mountp_operations are treated like * unions. */ int (*close)(FAR struct file *filep); ssize_t (*read)(FAR struct file *filep, FAR char *buffer, size_t buflen); ssize_t (*write)(FAR struct file *filep, FAR const char *buffer, size_t buflen); off_t (*seek)(FAR struct file *filep, off_t offset, int whence); int (*ioctl)(FAR struct file *filep, int cmd, unsigned long arg); /* The two structures need not be common after this point */#ifndef CONFIG_DISABLE_POLL int (*poll)(FAR struct file *filep, struct pollfd *fds, bool setup);#endif#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS int (*unlink)(FAR struct inode *inode);#endif};
struct block_operations,存放块设备的操作函数集,用于文件系统转换;
struct block_operations { int (*open)(FAR struct inode *inode); int (*close)(FAR struct inode *inode); ssize_t (*read)(FAR struct inode *inode, FAR unsigned char *buffer, size_t start_sector, unsigned int nsectors); ssize_t (*write)(FAR struct inode *inode, FAR const unsigned char *buffer, size_t start_sector, unsigned int nsectors); int (*geometry)(FAR struct inode *inode, FAR struct geometry *geometry); int (*ioctl)(FAR struct inode *inode, int cmd, unsigned long arg);#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS int (*unlink)(FAR struct inode *inode);#endif};
struct mountpt_operations,由一个文件系统提供,用于描述挂载点;
struct inode;struct fs_dirent_s;struct stat;struct statfs;struct mountpt_operations { /* The mountpoint open method differs from the driver open method * because it receives (1) the inode that contains the mountpoint * private data, (2) the relative path into the mountpoint, and (3) * information to manage privileges. */ int (*open)(FAR struct file *filep, FAR const char *relpath, int oflags, mode_t mode); /* The following methods must be identical in signature and position * because the struct file_operations and struct mountp_operations are * treated like unions. */ int (*close)(FAR struct file *filep); ssize_t (*read)(FAR struct file *filep, FAR char *buffer, size_t buflen); ssize_t (*write)(FAR struct file *filep, FAR const char *buffer, size_t buflen); off_t (*seek)(FAR struct file *filep, off_t offset, int whence); int (*ioctl)(FAR struct file *filep, int cmd, unsigned long arg); /* The two structures need not be common after this point. The following * are extended methods needed to deal with the unique needs of mounted * file systems. * * Additional open-file-specific mountpoint operations: */ int (*sync)(FAR struct file *filep); int (*dup)(FAR const struct file *oldp, FAR struct file *newp); int (*fstat)(FAR const struct file *filep, FAR struct stat *buf); /* Directory operations */ int (*opendir)(FAR struct inode *mountpt, FAR const char *relpath, FAR struct fs_dirent_s *dir); int (*closedir)(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir); int (*readdir)(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir); int (*rewinddir)(FAR struct inode *mountpt, FAR struct fs_dirent_s *dir); /* General volume-related mountpoint operations: */ int (*bind)(FAR struct inode *blkdriver, FAR const void *data, FAR void **handle); int (*unbind)(FAR void *handle, FAR struct inode **blkdriver, unsigned int flags); int (*statfs)(FAR struct inode *mountpt, FAR struct statfs *buf); /* Operations on paths */ int (*unlink)(FAR struct inode *mountpt, FAR const char *relpath); int (*mkdir)(FAR struct inode *mountpt, FAR const char *relpath, mode_t mode); int (*rmdir)(FAR struct inode *mountpt, FAR const char *relpath); int (*rename)(FAR struct inode *mountpt, FAR const char *oldrelpath, FAR const char *newrelpath); int (*stat)(FAR struct inode *mountpt, FAR const char *relpath, FAR struct stat *buf); /* NOTE: More operations will be needed here to support: disk usage * stats file stat(), file attributes, file truncation, etc. */};
2.3 struct file
一个打开的文件对应一个struct file结构,在该结构中包含了inode,用于描述文件的类型以及对应的函数操作集。
struct file{ int f_oflags; /* Open mode flags */ off_t f_pos; /* File position */ FAR struct inode *f_inode; /* Driver or file system interface */ void *f_priv; /* Per file driver private data */};
在每个进程中,struct tcb_s结构中都有一个struct filelist结构,用于维护打开的文件,当一个进程调用POSIX接口open来打开时,会得到文件描述符,文件描述符对应的就是这个文件数组的索引值。
struct filelist{ sem_t fl_sem; /* Manage access to the file list */ struct file fl_files[CONFIG_NFILE_DESCRIPTORS];}3. 原理分析
3.1 框架分析
架构框图如下所示:
文件系统框图
用户层,通过系统调用调到VFS层的通用接口;
VFS层,相当于一个适配层,用于对接不同的实际文件系统;
实际文件系统层,典型的情况下一个文件系统都需要绑定到块设备驱动程序上,而一些不太典型的情况是不需要块设备驱动,比如伪文件系统(BINFS、PROCFS)和MTD文件系统(NXFFS)。在Nuttx中,需要块设备驱动的文件系统为:FAT、ROMFS、SMARTFS;而不需要块设备驱动的文件系统为:NXFFS、BINFS、PROCFS、NFS、TMPFS等;
MTD,Memory Technology Devices,向上提供MTD接口,向下对接不同的硬件设备;
3.2 mount流程
mount()函数用于将source块设备指定的文件系统与根文件系统中target指定的路径名关联在一起。
在讲mount()之前,需要先了解一下数据结构:
struct fsmap_t
struct fsmap_t{ FAR const char *fs_filesystemtype; FAR const struct mountpt_operations *fs_mops;};
这个结构完成的就是文件系统名字和对应的操作函数集的映射,在mount()函数中会根据对应的文件系统名字去查找struct mountpt_operations。
struct mountpt_operations
这个数据结构在上文中介绍过了,有一个函数需要特别注意一下:
int (*bind)(FAR struct inode *blkdriver, FAR const void *data, FAR void **handle);
该函数用于将文件系统与某个块设备驱动进行绑定。
mount()关键代码如下:
int mount(FAR const char *source, FAR const char *target, FAR const char *filesystemtype, unsigned long mountflags, FAR const void *data){#if defined(BDFS_SUPPORT) || defined(NONBDFS_SUPPORT)#ifdef BDFS_SUPPORT FAR struct inode *blkdrvr_inode = NULL;#endif FAR struct inode *mountpt_inode; FAR const struct mountpt_operations *mops;#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS struct inode_search_s desc;#endif void *fshandle; int errcode; int ret; /* Verify required pointer arguments */ DEBUGASSERT(target && filesystemtype); /* Find the specified filesystem. Try the block driver file systems first */#ifdef BDFS_SUPPORT if (source && (mops = mount_findfs(g_bdfsmap, filesystemtype)) != NULL) { /* Make sure that a block driver argument was provided */ DEBUGASSERT(source); /* Find the block driver */ ret = find_blockdriver(source, mountflags, &blkdrvr_inode); if (ret < 0) { ferr("ERROR: Failed to find block driver %s\n", source); errcode = -ret; goto errout; } } else#endif /* BDFS_SUPPORT */#ifdef NONBDFS_SUPPORT if ((mops = mount_findfs(g_nonbdfsmap, filesystemtype)) != NULL) { } else#endif /* NONBDFS_SUPPORT */ { ferr("ERROR: Failed to find file system %s\n", filesystemtype); errcode = ENODEV; goto errout; } inode_semtake();#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS /* Check if the inode already exists */ SETUP_SEARCH(&desc, target, false); ret = inode_find(&desc); if (ret >= 0) { /* Successfully found. The reference count on the inode has been * incremented. */ mountpt_inode = desc.node; DEBUGASSERT(mountpt_inode != NULL); /* But is it a directory node (i.e., not a driver or other special * node)? */ if (INODE_IS_SPECIAL(mountpt_inode)) { ferr("ERROR: target %s exists and is a special node\n", target); errcode = -ENOTDIR; inode_release(mountpt_inode); goto errout_with_semaphore; } } else#endif /* Insert a dummy node -- we need to hold the inode semaphore * to do this because we will have a momentarily bad structure. * NOTE that the new inode will be created with an initial reference * count of zero. */ { ret = inode_reserve(target, &mountpt_inode); if (ret < 0) { /* inode_reserve can fail for a couple of reasons, but the most likely * one is that the inode already exists. inode_reserve may return: * * -EINVAL - 'path' is invalid for this operation * -EEXIST - An inode already exists at 'path' * -ENOMEM - Failed to allocate in-memory resources for the operation */ ferr("ERROR: Failed to reserve inode for target %s\n", target); errcode = -ret; goto errout_with_semaphore; } } /* Bind the block driver to an instance of the file system. The file * system returns a reference to some opaque, fs-dependent structure * that encapsulates this binding. */ if (!mops->bind) { /* The filesystem does not support the bind operation ??? */ ferr("ERROR: Filesystem does not support bind\n"); errcode = EINVAL; goto errout_with_mountpt; } /* Increment reference count for the reference we pass to the file system */#ifdef BDFS_SUPPORT#ifdef NONBDFS_SUPPORT if (blkdrvr_inode)#endif { blkdrvr_inode->i_crefs++; }#endif /* On failure, the bind method returns -errorcode */#ifdef BDFS_SUPPORT ret = mops->bind(blkdrvr_inode, data, &fshandle);#else ret = mops->bind(NULL, data, &fshandle);#endif if (ret != 0) { /* The inode is unhappy with the blkdrvr for some reason. Back out * the count for the reference we failed to pass and exit with an * error. */ ferr("ERROR: Bind method failed: %d\n", ret);#ifdef BDFS_SUPPORT#ifdef NONBDFS_SUPPORT if (blkdrvr_inode)#endif { blkdrvr_inode->i_crefs--; }#endif errcode = -ret; goto errout_with_mountpt; } /* We have it, now populate it with driver specific information. */ INODE_SET_MOUNTPT(mountpt_inode); mountpt_inode->u.i_mops = mops;#ifdef CONFIG_FILE_MODE mountpt_inode->i_mode = mode;#endif mountpt_inode->i_private = fshandle; inode_semgive(); /* We can release our reference to the blkdrver_inode, if the filesystem * wants to retain the blockdriver inode (which it should), then it must * have called inode_addref(). There is one reference on mountpt_inode * that will persist until umount2() is called. */... }
完成的工作主要有:
调用mount_findfs()函数,根据传入的参数filesystemtype来找到对应的文件系统操作函数集mops,如果是需要块设备支持的文件系统,则需要调用find_blockdriver()来查找传入参数source对应的块设备驱动;
根据传入参数target,来查找需要mount的路径对应的inode节点,如果没有的话需要调用inode_reserve()创建一个mountpt_inode;
调用mops->bind()函数将文件系统与块设备驱动进行绑定,如果不需要块设备支持的文件系统,bind()函数可能不需要做特殊处理, 而在需要块设备支持的文件系统中,bind()函数最终会将该文件系统的整体状态都传出来,保存在fshandle中;
更新挂载点mountpt_inode的内容,包括操作函数集mops,以及将fshandle保存的文件系统的整体状态放置到mountpt_inode结构中的i_private字段中。当打开这个挂载点mountpt_inode时,便可以根据这个字段来取出对应的信息。
作者:Loyen