AOS VFS
概要
VFS存在的意义,屏蔽掉底层文件系统的差异,为应用层提供标准的系统调用接口。
VFS提供的标准接口
基本通用的UNIX接口都已经实现了。之所以具有前缀aos_
。是因为这些都是对外的接口,在AliOS
Things的代码命名规则中规定:所有的对外接口都需要加上前缀aos_
aos_open
aos_close
aos_read
aos_write
aos_ioctl
aos_poll
aos_fcnt
aos_lseek
aos_sync
aos_stat
aos_unlink
aos_rename
aos_opendir
aos_closedir
aos_readdir
aos_mkdir
VFS的数据结构
inode_t
数据结构
/* this structure represents inode for driver and fs*/
typedef struct {
union inode_ops_t ops; /* inode operations */
void *i_arg; /* per inode private data */
char *i_name; /* name of inode */
int i_flags; /* flags for inode */
uint8_t type; /* type for inode */
uint8_t refs; /* refs for inode */
} inode_t;
因为VFS虚拟文件系统将文件和目录都当做文件来看待,上述的数据结构是索引节点类型,其具有节点具有的操作方法、节点的数据存放指针、节点的名称(即节点路径名/dev/null)、节点类型、节点被引用的次数。
在inode_t
结构体当中,ops是针对索引节点的操作方法,具体如下:
union inode_ops_t {
const file_ops_t *i_ops; /* char driver operations */
const fs_ops_t *i_fops; /* FS operations */
};
typedef const struct file_ops file_ops_t; /* 针对文件的操作方法 */
typedef const struct fs_ops fs_ops_t; /* 针对目录的操作方法 */
struct file_ops {
int (*open) (inode_t *node, file_t *fp);
int (*close) (file_t *fp);
ssize_t (*read) (file_t *fp, void *buf, size_t nbytes);
ssize_t (*write) (file_t *fp, const void *buf, size_t nbytes);
int (*ioctl) (file_t *fp, int cmd, unsigned long arg);
#ifdef AOS_CONFIG_VFS_POLL_SUPPORT
int (*poll) (file_t *fp, bool flag, poll_notify_t notify, struct pollfd *fd, void *arg);
#endif
};
struct fs_ops {
int (*open) (file_t *fp, const char *path, int flags);
int (*close) (file_t *fp);
ssize_t (*read) (file_t *fp, char *buf, size_t len);
ssize_t (*write) (file_t *fp, const char *buf, size_t len);
off_t (*lseek) (file_t *fp, off_t off, int whence);
int (*sync) (file_t *fp);
int (*stat) (file_t *fp, const char *path, struct stat *st);
int (*unlink) (file_t *fp, const char *path);
int (*rename) (file_t *fp, const char *oldpath, const char *newpath);
aos_dir_t *(*opendir) (file_t *fp, const char *path);
aos_dirent_t *(*readdir) (file_t *fp, aos_dir_t *dir);
int (*closedir) (file_t *fp, aos_dir_t *dir);
int (*mkdir) (file_t *fp, const char *path);
int (*rmdir) (file_t *fp, const char *path);
void (*rewinddir)(file_t *fp, aos_dir_t *dir);
long (*telldir) (file_t *fp, aos_dir_t *dir);
void (*seekdir) (file_t *fp, aos_dir_t *dir, long loc);
int (*ioctl) (file_t *fp, int cmd, unsigned long arg);
int (*statfs) (file_t *fp, const char *path, struct statfs *suf);
int (*access) (file_t *fp, const char *path, int amode);
};
file_t
数据结构
typedef struct {
inode_t *node; /* node for file */
void *f_arg; /* f_arg for file */
size_t offset; /* offset for file */
} file_t;
上述的file_t
数据结构用于描述一个被打开的文件,因为系统当中同一个系统当中,同一个文件可能被多个程序打开,但是打开的每一个文件都会唯一的执行特定的索引节点,即最终的物理文件只有一份。
以aos_open为例介绍其文件打开方式
aos_open
是对外的接口,外部函数可以直接使用该接口实现对于文件的打开操作,而不用去关心底层文件系统的实现细节。其代码如下所示:
其输入参数为:
const char *path; 即文件路径名
int flags; 即操作标志 比如只读 只写 读写等
int aos_open(const char *path, int flags)
{
file_t *file;
inode_t *node;
size_t len = 0;
int ret = VFS_SUCCESS;
if (path == NULL) {
return -EINVAL;
}
len = strlen(path);
if (len > PATH_MAX) { /* 文件路径名不允许超过256个字节 */
return -ENAMETOOLONG;
}
/* 获取互斥锁,该互斥锁在vfs_init函数中创建 */
if ((ret = krhino_mutex_lock(&g_vfs_mutex, RHINO_WAIT_FOREVER)) != 0) {
return ret;
}
/* 根据路径名传参,打开索引节点,具体函数实现会在下文介绍 */
node = inode_open(path);
if (node == NULL) {
krhino_mutex_unlock(&g_vfs_mutex);
#ifdef IO_NEED_TRAP
return trap_open(path, flags);
#else
return -ENOENT;
#endif
}
node->i_flags = flags;
/*因为用户操作的文件都是在内存中新建立的文件(文件对象会反过来指向索引节点
即一个文件可能被多个程序打开)。所以需要根据索引接点对象新建立一个文件对象
*/
file = new_file(node);
/* 释放互斥锁 */
krhino_mutex_unlock(&g_vfs_mutex);
if (file == NULL) {
return -ENFILE;
}
/* 根据节点类型判断该路径名指向是一个文件还是一个目录,因为文件对象和目录对象虽然都是节点
但是其操作方法有些差别,见前文中的目录和文件操作方法 */
if (INODE_IS_FS(node)) {
if ((node->ops.i_fops->open) != NULL) {
ret = (node->ops.i_fops->open)(file, path, flags);
}
} else {
if ((node->ops.i_ops->open) != NULL) {
ret = (node->ops.i_ops->open)(node, file);
}
}
if (ret != VFS_SUCCESS) {
del_file(file);
return ret;
}
/* 获得文件句柄 */
return get_fd(file);
}
- inode_open 在inode_open函数用于根据文件路径名打开对应的节点。
其输入参数为:
const char * path; 文件路径名
输出参数为:inode_t; 对应的节点
static inode_t g_vfs_dev_nodes[AOS_CONFIG_VFS_DEV_NODES];
inode_t *inode_open(const char *path)
{
int e = 0;
inode_t *node;
/*AOS_CONFIG_VFS_DEV_NODES该宏定义为25.
即在保存节点的数组g_vfs_dev_nodes中仅仅会保存25个节点
*/
for (e = 0; e < AOS_CONFIG_VFS_DEV_NODES; e++) {
node = &g_vfs_dev_nodes[e];
if (node->i_name == NULL) {
continue;
}
/* 判断该节点是一个目录还是一个文件 */
if (INODE_IS_TYPE(node, VFS_TYPE_FS_DEV)) {
if ((strncmp(node->i_name, path, strlen(node->i_name)) == 0) &&
(*(path + strlen(node->i_name)) == '/')) {
return node;
}
}
if (strcmp(node->i_name, path) == 0) {
return node;
}
}
return NULL;
}
- new_file 在new_file()函数中,完成的主要功能就是新建立一个file_t的结构体定义和初始化。 其输入参数是: inode_t *node; 上个函数中得到的节点 输出参数是: file_t 类型。用于后续获取文件句柄
static file_t files[MAX_FILE_NUM];
#define MAX_FILE_NUM (AOS_CONFIG_VFS_DEV_NODES * 2)
file_t *new_file(inode_t *node)
{
file_t *f;
int idx;
/* 在file数组当中新建立一个数据项。且保证该数组未满。即打开的文件数量是有限的 */
for (idx = 0; idx < MAX_FILE_NUM; idx++) {
f = &files[idx];
if (f->node == NULL) {
goto got_file;
}
}
return NULL;
got_file:
f->node = node;
f->f_arg = NULL;
f->offset = 0;
inode_ref(node);
return f;
}
所有的系统调用函数(类似于aos_open)都位于vfs.c文件中。
将驱动文件或者文件系统加载到VFS当中
在vfs_register.c文件中定义的函数:
int aos_register_driver(const char *path, file_ops_t *ops, void *arg)
int aos_register_fs(const char *path, fs_ops_t *ops, void *arg)
上述两个函数分别是将驱动文件或者是文件系统类型装载到VFS当中的函数。外部程序(例如sensor驱动程序)可以调用这两个接口将驱动文件加载的VFS当中去。
以aos_register_driver为例进行介绍:
其输入参数为: 驱动文件路径名 const char * path (/dev/null)
驱动操作方法 file_ops_t *ops (不需要实现全部的方法,实现必要的方法,其余置NULL即可)
int aos_register_driver(const char *path, file_ops_t *ops, void *arg)
{
inode_t *node = NULL;
int err, ret;
err = krhino_mutex_lock(&g_vfs_mutex, RHINO_WAIT_FOREVER);
if (err != 0) {
return err;
}
//在g_vfs_dev_nodes数组中寻找一个空的数组项,返回其指针给node,并将path的路径名赋给node-->name
ret = inode_reserve(path, &node);
if (ret == VFS_SUCCESS) {
/* now populate it with char specific information */
INODE_SET_CHAR(node);
node->ops.i_ops = ops;
node->i_arg = arg;
}
/* step out critical area for type is allocated */
err = krhino_mutex_unlock(&g_vfs_mutex);
if (err != 0) {
if (node->i_name != NULL) {
krhino_mm_free(node->i_name);
}
memset(node, 0, sizeof(inode_t));
return err;
}
return ret;
}
示例代码
vfs的操作类linux中操作,
这里举例aos_open
、aos_close
、aos_read
、aos_write
void bl_test_uart0(void)
{
int fd;
int length;
char buf_recv[128];
/* 首先打开相关文件,对应到UART0 */
fd = aos_open("/dev/ttyS0", 0);
if (fd < 0) {
log_error("open err.\r\n");
return;
}
while (1) {
/* 读取UART0中的数据 */
length = aos_read(fd, buf_recv, sizeof(buf_recv));
if (length > 0) {
log_info("recv len = %d\r\n", length);
/* 直到收到'exit'才会主动结束循环,并close相关文件 */
if (memcmp(buf_recv, "exit", 5) == 0) {
aos_close(fd);
break;
}
/* UART0将收到的数据回传过去 */
aos_write(fd, buf_recv, length);
}
vTaskDelay(100);
}
}
总结
- VFS的一把重要的互斥锁 对VFS的相关操作都需要获取该互斥锁才能够进行。
kmutex_t g_vfs_mutex;
- VFS的两个重要的数组结构 如下所示:第一个数组是保存节点的数组结构。 第二个数组是保存文件对象的数组结构。和用户直接相关的是第二个数组结构
static inode_t g_vfs_dev_nodes[AOS_CONFIG_VFS_DEV_NODES];
static file_t files[MAX_FILE_NUM];
- 使用者只需关心的文件 vfs_register.c文件用于注册 vfs.c 文件用于各种标准操作。