这两天看了一下NuttX文件系统相关的代码,由于NuttX文件系统代码架构有点类似Linux内核中的VFS(或许是简化版或者轻量级的Virtual FileSystem),
所以看起来有点头疼,趁着有点感觉,尽快小结一下,否则很快就会忘记的;错误之处请大家多多指点!
提到文件系统不得不和设备关联在一起,比如常见的块设备,下面按照文件系统与设备相关的关键数据结构、设备的注册、文件系统的mount以及文件的Open几个方面,分别做简要介绍
一、关键数据结构
1.描述设备节点的数据结构
struct inode
{
FAR struct inode *i_peer; /* 指向同peer别的设备节点*/
FAR struct inode *i_child; /* 指向child级别的设备节点*/
int16_t i_crefs; /*设备节点的引用次数 */
uint16_t i_flags; /* Flags for inode */
union inode_ops_u u; /* Inode操作方法,见下面详细说明 */
mode_t i_mode; /* 设备访问模式*/
FARvoid *i_private; /*指向每种具体设备私有的成员,以ramdisk设备类型为例,该成员指向ramdisk相关的rd_struct_s类型设备*/
char i_name[1]; /* 设备名字,每次遍历设备时,根据此名字进行匹配*/
};
由于inode是对所有设备的一个抽象,因此不通类型的设备,他们的操作方法也不一样,所以此处ops_u采用共用体类型来描述,下面单独介绍类型inode_ops_u
2. 描述inode对各种设备操作方法的数据结构
union inode_ops_u
{
FAR const structfile_operations *i_ops; /* 字符设备的操作方法*/
#ifndef CONFIG_DISABLE_MOUNTPOINT
FAR const struct block_operations *i_bops; /* 块设备的操作方法*/
FAR const struct mountpt_operations *i_mops; /* mountpoint类型设备的操作方法 */
#endif
#ifdef CONFIG_FS_NAMED_SEMAPHORES
FAR structnsem_inode_s *i_nsem; /* 用于资源保护的semaphore */
#endif
#ifndef CONFIG_DISABLE_MQUEUE
FAR structmqueue_inode_s *i_mqueue; /* POSIX消息队列*/
#endif
};
说明:
再次强调,inode是对所有设备的一个抽象,因此不通设备差异主要体现在两个成员:
Inode_ops_u:操作各种设备的方法
I_private: 指向每种具体设备私有的成员,一般为具体每中类型设备的数据结构
3. 描述ramdisk文件系统的数据结构
struct rd_struct_s
{
uint32_t rd_nsectors; /*此成员表示此ramdisk总共有个sector,说明secotr是文件系统读写的最小单位,类似Nand flash最小读写单位是page一样*/
uint16_trd_sectsize; /* 此ramdisk每个扇区的size,一般是512Byte*/
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
uint8_trd_crefs; /* ramdisk 被打开的次数*/
#endif
uint8_trd_flags; /* ramdisk的属性,比如此文件系统是只读的还是读写*/
#ifdef CONFIG_FS_WRITABLE
FAR uint8_t*rd_buffer; /*ramdisk 备份缓冲,即Mount时会把ramdisk中的数据copy至此缓冲中*/
#else
FAR const uint8_t *rd_buffer; /* 功能同上,用于ROMdisk文件系统的缓冲 */
#endif
};
4.块设备操作方法数据结构简介
struct block_operations
{
int (*open)(FAR structinode *inode); /*ramdisk设备的open函数*/
int (*close)(FAR structinode *inode); /*ramdisk设备的release函数*/
ssize_t (*read)(FAR struct inode *inode, FARunsigned char *buffer,
size_t start_sector, unsigned int nsectors); /*ramdisk设备的读操作函数*/
ssize_t (*write)(FAR struct inode *inode, FARconst unsigned char *buffer,
size_t start_sector, unsigned int nsectors); /*ramdisk设备的写操作函数*/
int (*geometry)(FAR structinode *inode, FAR struct geometry *geometry);/*记录块设备的状态,ramdisk本身属于块设备*/
int (*ioctl)(FAR structinode *inode, int cmd, unsigned long arg); /*ramdisk的ioctl函数*/
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
int (*unlink)(FAR structinode *inode);
#endif
};
5.块设备状态管理数据结构
struct geometry
{
bool geo_available; /* true设备能否操作*/
bool geo_mediachanged; /* true: 设备最后一次访问修改过数据*/
bool geo_writeenabled; /* true: 设备可以写入数据*/
size_t geo_nsectors; /* 块设备扇区总数*/
size_t geo_sectorsize; /* 块设备扇区大小 */
};
6.文件系统Mount操作方法相关的数据结构
struct mountpt_operations
{
int (*open)(FAR structfile *filep, FAR const char *relpath, int oflags, mode_t mode); /*指定类型文件系统的open文件接口*/
int (*close)(FAR structfile *filep); /*指定类型文件系统的close文件接口*/
ssize_t (*read)(FAR struct file *filep, FAR char*buffer, size_t buflen); /*指定类型文件系统的read文件接口*/
ssize_t (*write)(FAR struct file *filep, FAR constchar *buffer,size_t buflen); /*指定类型文件系统的write文件接口*/
off_t (*seek)(FAR struct file *filep,off_t offset, int whence); /*指定类型文件系统的sleek文件接口*/
int (*ioctl)(FAR structfile *filep, int cmd, unsigned long arg);
int (*sync)(FAR structfile *filep);
int (*dup)(FAR conststruct file *oldp, FAR struct file *newp);
int (*opendir)(FAR structinode *mountpt, FAR const char *relpath, FAR struct fs_dirent_s *dir);
int (*closedir)(FAR structinode *mountpt,FAR struct fs_dirent_s *dir);
int (*readdir)(FAR structinode *mountpt,FAR struct fs_dirent_s *dir);
int (*rewinddir)(FARstruct inode *mountpt,FAR struct fs_dirent_s *dir);
int (*bind)(FAR structinode *blkdriver, FAR const void *data,FAR void **handle); /*指定类型文件系统的初始化接口,此处成为bind,类似于FILEX文件系统中的disk_init接口*/
int (*unbind)(FAR void*handle, FAR struct inode **blkdriver,unsigned int flags);
int (*statfs)(FAR structinode *mountpt, FAR struct statfs *buf);
int (*unlink)(FAR structinode *mountpt, FAR const char *relpath);
int (*mkdir)(FAR structinode *mountpt, FAR const char *relpath,mode_t mode);
int (*rmdir)(FAR structinode *mountpt, FAR const char *relpath);
int (*rename)(FAR structinode *mountpt, FAR const char *oldrelpath,FAR const char *newrelpath);
int (*stat)(FAR structinode *mountpt, FAR const char *relpath,FAR struct stat *buf);
};
7.描述文件系统属性的数据结构
struct fsmap_t
{
Char *filesystem_type; /*文件系统类型*/
struct mountpt_operations fs_operations; /*文件系统的操作方法*/
}
二、设备的注册流程
1. int ramdisk_register(intminor, FAR uint8_t *buffer, uint32_t nsectors,uint16_t sectsize, uint8_trdflags)
在NuttX系统中,无论rom fs或者ram fs在mount之前,一定要进行注册(如果设备是新建的设备,即没有文件系统,那么注册完设备后需要立刻创建文件系统,否则Mount失败),所谓注册就是在申请一块内存空间作为disk,然后创建一个设备节点用于操作这块内存,
接着根据传入该文件系统的关联的sector size,总共多少个sector、文件系统的是否支持读写等信息生成一个内存文件系统;
以及该内存区域生成的设备节点的设备号
Minor:设备号,生成设备节点时,比如/dev/ram1 /dev/ram2中的1或者2
Buffer: ramdisk缓冲,在调用此接口前先申请好或者在文件中定义好,
注意:此buffer的大小必须是扇区的整数倍(即能被512整除)
Nsectors: ramdisk扇区的总数,
Sectorsize: ramdisk扇区大小
说明:buffer的size应该等于nsectors * sectorsize
Rdflag:ramdisk的属性
代码主要流程简介:
接口内部先分配一个struct rd_struct_s类型的device内存,根据设备号minor生成设备名字“/dev/ramx”,根据buffer、nsectors、sectorsize以及rdflags初始化上述分配的ramdisk结构体成员;
最后把设备名字,ramdisk设备,以及ramdisk设备的操作方法g_bops作为参数,调用register_blockdriver函数注册到block设备链表中;
Ramdisk操作方法数据结构:
static const struct block_operations g_bops =
{
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
rd_open, /*open */
rd_close, /*close */
#else
0, /*open */
0, /*close */
#endif
rd_read, /*read */
#ifdef CONFIG_FS_WRITABLE
rd_write, /*write */
#else
NULL, /*write */
#endif
rd_geometry, /* geometry */
rd_ioctl, /*ioctl */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
rd_unlink /* unlink */
#endif
};
2. int register_blockdriver(FARconst char *path,FAR const struct block_operations *bops,mode_t mode, FAR void*priv)
参数说明:
Path: 块设备的名字
Bops: block device operations,块设备的操作方法,即open, close, read, write, ioctl等
Mode: 设备模式,
Priv: 描述所注册块设备详细信息的结构体,以ramdisk为例,priv指向struct rd_struct_s类型的device,
注意:
由于不同类型的块设备,结构体类型不同,此处priv类型是一个void *型的成员;
块设备只有注册到系统中(记录块设备的全局结构体链表g_root_inode),那么在上层根据设备路径和名字操作此设备时,才能被遍历到,否则找不到此块设备;
代码主要流程简介:
接口内部先根据设备名字遍历记录块设备的全局结构体链表中所有的node,如果链表中已经存在与此名字相同的node,则表明此设备已经注册,反之则根据设备名字分配一个structinode *类型的inode内存;
然后根据设备的name,bops, mode以及priv初始化inode的相关成员;从而完成块设备的注册;
node->i_name
node->u.i_bops = bops;
node->i_mode =mode;
node->i_private = priv;
说明:此inode很关键,后面多块设备的所有操作,都围绕着这inode操作;
三、文件系统mount流程
1. int mount(FAR const char *source,FAR const char *target,FAR const char *filesystemtype, unsigned longmountflags,FAR const void *data)
参数说明:
Source:设备节点名字,比如/dev/ram1
Target:mount到文件系统的路径,比如/mnt/ramdisk0
Filesystemtype:文件系统类型,比如procfs, tmpfs, vfat, smartfs等等
Mountflags:文件系统被mount时的属性,比如只读等
Data: private data,用于扩展,未使用时传NULL
代码主要流程简介:
1.1接口内部先根据文件系统类型,去遍历记录各种文件系统属性(各种文件系统的类型和操作方法)的结构体全局数组,找出待mount类型文件系统的operation,即读写等操作的方法;
static const struct fsmap_t g_bdfsmap[] =
{
{ "vfat",&fat_operations },
{ "romfs",&romfs_operations },
{ "smartfs",&smartfs_operations },
{ NULL, NULL },
};
static const struct fsmap_t g_nonbdfsmap[] =
{
{ "nxffs",&nxffs_operations },
{ "tmpfs",&tmpfs_operations },
{ "nfs",&nfs_operations },
{ "binfs",&binfs_operations },
{ "procfs",&procfs_operations },
{ "hostfs",&hostfs_operations },
{ NULL, NULL },
};
说明:在遍历各种文件系统属性的结构体数组时,先遍历g_bdfsmap再遍历g_nonbdfsmap,
Procfs_operations的操作方法如下
const struct mountpt_operations procfs_operations =
{
procfs_open, /* open */
procfs_close, /* close */
procfs_read, /* read */
procfs_write, /* write */
NULL, /* seek */
procfs_ioctl, /*ioctl */
NULL, /* sync */
procfs_dup, /* dup */
procfs_opendir, /* opendir */
procfs_closedir, /* closedir */
procfs_readdir, /* readdir */
procfs_rewinddir, /* rewinddir */
procfs_bind, /* bind */
procfs_unbind, /* unbind */
procfs_statfs, /* statfs */
NULL, /* unlink */
NULL, /* mkdir */
NULL, /* rmdir */
NULL, /* rename */
procfs_stat /* stat */
};
1.2 根据mount的设备节点名字比如/dev/ram1,找到描述该设备的inode节点(即到g_root_inode链表中去遍历);
1.3 根据要Mount的路径名字target,为mountpt新分配一个inode节点,并添加至链表g_root_node中;
1.4 根据步骤1中获取的文件系统operation方法,调用其中的bind接口,进行文件系统初始化;
Bind接口简要说明:
每种文件系统都有管理他们文件系统的内部数据结构,虽然管理各自内部文件系统的数据结构不同;
但是他们内部均有一个相同的成员,那就是struct inode *fs_blkdriver,用于指向管理设备的inode成员,
Bind函数内部先分配一块管理各自文件系统的内部数据结构,初始化如下成员
fs->fs_blkdriver = blkdriver
fs->fs_sem
然后调用各种文件系统的mout函数,比如smartfs_mount/procfs_mount/fat_mount函数进行各自文件系统真正的Mount操作;
最后把上述管理各自文件系统内部数据结构,通过参数fshandle指针返回;
说明:每种文件系统内部mount实现均是根据各种类型文件系统原理实现,差异比较大,这里略去说明;
1.5 最后初始化mountpt_inode的如下成员,完成磁盘Mount工作
mountpt_inode->u.i_mops = mops;
mountpt_inode->i_mode = mode;
mountpt_inode->i_private = fshandle;
mountpt_inode->i_flags
注意:新分配的inode用于管理mount文件系统的目录,对应操作方法赋值的是mountpt_inode->u.mops,并非u.iops或者u.ibops成员;
1.6 mount函数的最后,执行了inode_release(blkdrvr_inode),释放了管理设备的inode节点,
这点有点不太理解,后面文件系统的打开和操作还会使用此inode
三、文件open流程
Open函数内部先根据文件路径名,找到匹配的inode,
接着到内核中管理文件的结构体数组中tg_filelist,找到一个未使用的成员,数组下标作为file id,
然后根据inode的i_flags类型(检查此是不是mount过的文件系统),最终调用inode->u.i_mops->open函数或者调用inode->u.i_ops->open去open去open文件,
说明:
此处的open函数指针是每种文件系统自己的open函数,比如procfs_open, fat_open等