rt-thread组件之dfs文件架构浅析

RTT的文件系统主要分为三层,如下图:

                                                                                                                                                图1

RTT的用户手册中也有介绍到,最上层一套面向嵌入式系统,专门优化过的虚拟文件系统(接口)。通过它,RT-thread操作系统能够适配下层不同的文件系统格式。接下来是各种文件系统,目前最新的rt-thread1.1.0源码中已包含8种文件系统:devfs,elmfat,jffs2,nfs,romfs,skeleton,uffs,yaffs2,这些文件系统通常是网上的一些开源的项目,RTT移植过来做些修改适配。最低层就是各位具体设备的驱动层了,如SD Card,spi flash等。

这些东西都是比较理论的东西,下面一起来看看RTT是如何具体实现这些框框架架的。

1 RTT文件系统的应用接口实现(最顶层)

所谓应用层接口其实就是指该以什么样的接口函数形式提供给用户使用,这里的用户就是指编程开发人员,开发者使用这些接口进行文件相关操作,根本不用关心其内部是如何实现或是数据具体是保存在哪个设备中。

在RTT源码文件.\components\dfs\src目录下的dfs_posix.c源文件就是具体实现这些接口的源代码,其共实现了接口有20个与文件目录相关的posix接口函数:

  • chdir     修改工作目录
  • close    关闭一个文件
  • closedir  关闭一个目录
  • fstat   获取一个文件的状态信息
  • getcwd   获取当前的工作目录
  • lseek    移动文件滑标操作
  • mkdir    新建一个目录
  • open   打开一个文件
  • opendir   打开一个目录
  • read    从一个打开的文件中读取数据
  • readdir    读取下一个目录节点
  • rename   重命名一个文件
  • rewinddir    重置目录流
  • rmdir    删除一个目录
  • seekdir  与lseek相似,这里操作的是目录,设置目录滑标的位置
  • stat    获取文件相关信息
  • statfs    获取一个挂载的文件系统相关信息
  • telldir    获取当前目录流中滑标的位置
  • unlink    将一个目录从文件系统中移除
  • write  将数据写入文件

RTT文件系统的顶层应用接口源码文件除了dfs_posix.c这一接口实现之外,还包含dfs.c,dfs_fs.c,dfs_file.c,这几个源文件,这几个源文件的关系如下图所示:

图中的箭头表示调用的关系,即dfs_posix.c源文件中的一些接口函数实现会调用底下dfs.c,dfs_fs,c,dfs_file.c源文件中的一些函数。

从上图可以看出,dfs_posix.c源文件是RTT文件系统最顶层面向使用者的一个接口函数实现文件,它的具体实现是依赖下层三个源文件来实现的。

从上图也可以看出,RTT将文件系统的顶层源文件都以dfsxxx的形式命名,这个dfs暂时看成是directory,file,system三个英文的首字母缩写吧,这里简称为DFS框架。

2 DFS框架

2.1 为什么会有DFS框架?

RTT为了能够支持各种文件系统,在顶层设计了这么一个框架,使各个开源的文件系统能够经过稍微修改匹配到这个框架就能实现移植。

2.2 DFS框架的组成内容

DFS框架内部主要包含三个表:filesystem_operation_table,filesystem_table,fd_table,以及一个文件系统互斥锁fslock用于解决资源冲突。

  • filesystem_operation_table: 这个表每一个表项表示一个文件系统对应的一套操作函数及相关属性。
  • filesystem_table:    这个表记录着挂载的文件系统,即每一个表项表示挂载的一个文件系统。
  • fd_table:      这个表记录着当前打开的文件句柄。

注:往filesystem_operation_table添加一个文件系统的操作集操作叫“注册”,往filesystem_table添加一个文件系统的操作叫"挂载",往fd_table表内添加一个文件句柄的操作为普通添加记录操作。

2.2.1 filesystem_operation_table

filesystem_operation_table是记录各个文件系统操作接口的表,其各个表项的结构如下定义:

/* File system operations struct */
struct dfs_filesystem_operation
{
    char *name;   //文件系统的名称
    rt_uint32_t flags;    //操作标志

    /* mount and unmount file system */
    int (*mount)    (struct dfs_filesystem *fs, unsigned long rwflag, const void *data);  //挂载
    int (*unmount)  (struct dfs_filesystem *fs);                //取消挂载

    /* make a file system */
    int (*mkfs)     (rt_device_t devid);                                  //创建一个文件系统操作,相当于格式化
    int (*statfs)   (struct dfs_filesystem *fs, struct statfs *buf);   //获取文件系统的当前状态信息操作

    int (*open)     (struct dfs_fd *fd);                 //打开文件操作
    int (*close)    (struct dfs_fd *fd);                 //关闭文件操作
    int (*ioctl)    (struct dfs_fd *fd, int cmd, void *args);   //文件控制操作
    int (*read)     (struct dfs_fd *fd, void *buf, rt_size_t count);     //文件读取操作
    int (*write)    (struct dfs_fd *fd, const void *buf, rt_size_t count);  //文件写操作
    int (*flush)    (struct dfs_fd *fd);                                   //将文件内容保存到设备上操作
    int (*lseek)    (struct dfs_fd *fd, rt_off_t offset);                  //文件内容定位操作
    int (*getdents) (struct dfs_fd *fd, struct dirent *dirp, rt_uint32_t count);    //获取目录条目操作

    int (*unlink)   (struct dfs_filesystem *fs, const char *pathname);  //将一目录从文件系统移除操作
    int (*stat)     (struct dfs_filesystem *fs, const char *filename, struct stat *buf);   //获取文件状态信息
    int (*rename)   (struct dfs_filesystem *fs, const char *oldpath, const char *newpath);  //文件重命名操作
};

由上面的文件操作定义可知,filesystem_opration_table内每一个表项保存的是一个文件系统的一套操作函数,不管是什么文件系统,其操作函数的形式都是一致的。

2.2.2 filesystem_table

filesystem_table记录的是当前挂载的文件系统,其每一个表项表示的就是一个文件系统。

下面再来看看文件系统的定义.

/* Mounted file system */
struct dfs_filesystem
{
    rt_device_t dev_id;  //此文件系统对应的设备ID

    char *path;          //此文件系统的挂载点
    const struct dfs_filesystem_operation *ops; //此文件系统对应的操作接口集,指向filesystem_operation_table对应的表项.其定义见2.2.1。

    void *data;            //文件系统的私有数据
};
从上面可以看出,文件系统就是将文件系统操作的集合,对应的设备ID以及挂载点整合在一起的数据集合。

2.2.3 fd_table

fd_table记录当前打开的文件集合,每一个表项表示一个打开的文件句柄,其结构如下定义:

/* file descriptor */
#define DFS_FD_MAGIC	 0xfdfd
struct dfs_fd
{
    rt_uint16_t magic;           //文件描述魔数
    rt_uint16_t type;            //文件类型
    char *path;                  //相对于挂载点的路径
    int ref_count;               //当前被关联的次数

    struct dfs_filesystem *fs;   //对应的文件系统

    rt_uint32_t flags;           //标志
    rt_size_t   size;            //文件大小
    rt_off_t    pos;             //当前文件位置

    void *data;                  //私有数据
};

2.3.4 dfs小结

由以上几节内容可知,dfs框架主要由三张表组成,而dfs.c,dfs_fs.c,dfs_file.c这三个源文件主要是围绕这三张表来转的,提供一些在关这三张表的添加,删除,查找,获取等操作,而真正的文件操作并不是在dfs框架内实现,而是由中间层的具体文件系统来实现,中间层的文件系统,比如elmfat文件系统通过向filesystem_operation_table注册其操作集,向filesystem_table挂载其文件系统,这样一来,系统就可以通过这两张表找到对应的具体操作函数了。

3 RTT文件系统初始化过程

RTT在在关文件系统的初始化过程中一般按以下流程来进行:

1 dfs框架初始化(最顶层初始化)

2 中间层具体文件系统初始化(中间层具体文件系统初始化)

3 文件系统对应的具体设备驱动初始化(最底层设备驱动初始化)

4 挂载文件系统(将各层具体关联起来)

3.1 dfs框架初始化(最顶层)

其源码如下:

void dfs_init(void)
{
    rt_memset((void *)filesystem_operation_table, 0, sizeof(filesystem_operation_table));//清空filesystem_operation_table表
    rt_memset(filesystem_table, 0, sizeof(filesystem_table));//清空filesystem_table表
    rt_memset(fd_table, 0, sizeof(fd_table));//清空

    /* create device filesystem lock */
    rt_mutex_init(&fslock, "fslock", RT_IPC_FLAG_FIFO);//文件系统互斥锁初始化

#ifdef DFS_USING_WORKDIR
    /* set current working directory */
    rt_memset(working_directory, 0, sizeof(working_directory));//工作路径初始化
    working_directory[0] = '/';
#endif
}
由上源码可知,dfs框架初始化只是对内部表及资源进行初始化。

3.2 中间层具体文件系统初始化(中间层)

以RTT的elmfat文件系统为例,此步骤主要是向DFS框架注册elmfat文件系统的操作函数集,即向filesystem_operation_table注册elmfat文件系统的相关操作。

int elm_init(void)//elmfat文件系统初始化
{
    /* register fatfs file system */
    dfs_register(&dfs_elm);//注册elmfat文件系统

    return 0;
}
int dfs_register(const struct dfs_filesystem_operation *ops)
{
    int index, result;
    int free_index;

    result = 0;
    free_index = DFS_FILESYSTEM_TYPES_MAX;

    //首先获取文件操作权限
    dfs_lock();

    //检查该文件系统是否已经注册
    for (index = 0; index < DFS_FILESYSTEM_TYPES_MAX; index++)
    {
        if (filesystem_operation_table[index] == RT_NULL)
        {
            /* find out an empty filesystem type entry */
            if (free_index == DFS_FILESYSTEM_TYPES_MAX)//记录第一个空闲位置
                free_index = index;
        }
        else if (strcmp(filesystem_operation_table[index]->name, ops->name) == 0)//如果已经注册过了,则返回错误
        {
            result = -1;
            goto err;
        }
    }

    /* filesystem type table full */
    if (free_index == DFS_FILESYSTEM_TYPES_MAX)//如果全部已满,则返回错误
    {
        result = -1;
        goto err;
    }

    /* save the filesystem's operations */
    filesystem_operation_table[free_index] = ops;//将当前操作集合记录到空闲位置

err:
    dfs_unlock();//释放文件系统操作权限
    return result;
}

由上述代码可知,上面的操作只是当具体的文件系统注册进filesystem_operation_table中,还没具体的操作。

3.3 文件系统对应的具体设备初始化(底层驱动)

接下来就是要对文件系统具体的设备驱动进行初始化,比如spi flash,sd等,这部分内容在这里不做详情介绍,后续将介绍。这里只要知道经过这么一初始化后,文件系统对应的具体设备就可以操作了。

3.4 挂载文件系统

挂载文件系统在文件系统初始化步骤中是最后的一步,也是最重要的一步。挂载成功后用户就可以通过dfs_posix.c文件提供的文件接口函数对文件系统进行任意操作了。

下面来看看这个文件挂载到底做了哪些事情?

/* mount SPI flash as root directory */
    if (dfs_mount("flash0", "/", "elm", 0, 0) == 0)//挂载名字为elm的文件系统,这个文件系统对应的设备名为flash0,挂载点为/
    {
        rt_kprintf("flash0 mount to /.\n");
    }
    else
    {
        rt_kprintf("flash0 mount to / failed.\n");
    }
由上可知,说明挂载的过程可以看成是将某某名的文件系统和某某名的设备驱动相关起来,并挂载到某个挂载点,这个挂载点即为这个文件系统的根目录。

dfs_mount函数如下定义:

int dfs_mount(const char   *device_name,
              const char   *path,
              const char   *filesystemtype,
              unsigned long rwflag,
              const void   *data)
{
    const struct dfs_filesystem_operation *ops;
    struct dfs_filesystem *fs;
    char *fullpath=RT_NULL;
    rt_device_t dev_id;
    int index, free_index;

    //查找对应的设备ID
    /* open specific device */
    if (device_name != RT_NULL)
    {
        dev_id = rt_device_find(device_name);
        if (dev_id == RT_NULL)
        {
            /* no this device */
            rt_set_errno(-DFS_STATUS_ENODEV);

            return -1;
        }
    }
    else
    {
        /* which is a non-device filesystem mount */
        dev_id = RT_NULL;
    }

    /* find out specific filesystem */
    dfs_lock();
	//在filesystem_operation_table中想找对应的文件系统
    for (index = 0; index < DFS_FILESYSTEM_TYPES_MAX; index++)
    {
        if (filesystem_operation_table[index] == RT_NULL)
            continue;

        if (strcmp(filesystem_operation_table[index]->name, filesystemtype) == 0)
            break;
    }
    dfs_unlock();

    /* can't find filesystem */
    if (index == DFS_FILESYSTEM_TYPES_MAX)//如果没有找到,则说明当前未注册此文件系统
    {
        rt_set_errno(-DFS_STATUS_ENODEV);

        return -1;
    }
    //ops记录相应的文件系统
    ops = filesystem_operation_table[index];

    /* make full path for special file */
    //标准化路径
    fullpath = dfs_normalize_path(RT_NULL, path);
    if (fullpath == RT_NULL) /* not an abstract path */
    {
        rt_set_errno(-DFS_STATUS_ENOTDIR);

        return -1;
    }

    /* Check if the path exists or not, raw APIs call, fixme */
    //如果该目录不为/或/dev,则检查该路径是否存在,如果不存在,则返回错误
    if ((strcmp(fullpath, "/") != 0) && (strcmp(fullpath, "/dev") != 0))
    {
        struct dfs_fd fd;

        if (dfs_file_open(&fd, fullpath, DFS_O_RDONLY | DFS_O_DIRECTORY) < 0)
        {
            rt_free(fullpath);
            rt_set_errno(-DFS_STATUS_ENOTDIR);

            return -1;
        }
        dfs_file_close(&fd);
    }

    //在挂载文件系统中查找该文件系统是否已经挂载过
    free_index = DFS_FILESYSTEMS_MAX;
    /* check whether the file system mounted or not */
    dfs_lock();
    for (index = 0; index < DFS_FILESYSTEMS_MAX; index ++)
    {
        if (filesystem_table[index].ops == RT_NULL)
        {
            /* find out an empty filesystem table entry */
            if (free_index == DFS_FILESYSTEMS_MAX)
                free_index = index;//记录第一个空闲位置
        }
        else if (strcmp(filesystem_table[index].path, path) == 0)//ÒѾ­¹ÒÔØ
        {
            rt_set_errno(-DFS_STATUS_EINVAL);
            goto err1;
        }
    }

    /* can't find en empty filesystem table entry */
    if (free_index == DFS_FILESYSTEMS_MAX)//已满,则返回错误
    {
        rt_set_errno(-DFS_STATUS_ENOSPC);
        goto err1;
    }

    /* register file system */
	//注册该文件系统
    fs         = &(filesystem_table[free_index]);//文件系统
    fs->path   = fullpath;//挂载点
    fs->ops    = ops;//操作集合
    fs->dev_id = dev_id;//设备ID
    /* release filesystem_table lock */
    dfs_unlock();//相关的文件互斥锁

    /* open device, but do not check the status of device */
    if (dev_id != RT_NULL)
        rt_device_open(fs->dev_id, RT_DEVICE_OFLAG_RDWR); //以读写方式打开该设备

    //执行挂载操作
    /* there is no mount implementation */
    if (ops->mount == RT_NULL)//如果挂载函数为空,则返回错误
    {
        if (dev_id != RT_NULL)
            rt_device_close(dev_id); 
        dfs_lock();
        /* clear filesystem table entry */
        rt_memset(fs, 0, sizeof(struct dfs_filesystem));
        dfs_unlock();

        rt_free(fullpath);
        rt_set_errno(-DFS_STATUS_ENOSYS);

        return -1;
    }
    /* call mount of this filesystem */
    else if (ops->mount(fs, rwflag, data) < 0)//如果挂载操作失败,则返回错误
    {
        /* close device */
        if (dev_id != RT_NULL)
            rt_device_close(fs->dev_id);

        /* mount failed */
        dfs_lock();
        /* clear filesystem table entry */
        rt_memset(fs, 0, sizeof(struct dfs_filesystem));
        dfs_unlock();

        rt_free(fullpath);

        return -1;
    }

    return 0;

err1:
    dfs_unlock();
    if (fullpath != RT_NULL)
        rt_free(fullpath);

    return -1;
}

由上可知,挂载操作实际上是首先在filesystem_oeration_table中查找该文件系统是否已经注册过,如未注册则返回错误,接下来在filesystem_table中检查该文件系统是否已经挂载,如果已经挂载过,则返回错误。接着打开对应设备,最后执行该文件系统操作集中提供的mount函数。

只有挂载成功后的文件系统,RTT才能通过其提供的一系统操作接口来对对应设备进行文件操作了。

4 dfs框架总结

由以上内容可知,dfs框架并未提供具体的文件操作实现,而只是提供了一个框架,可以让各种各样的文件系统适配到这个框架上来,而保持顶层的dfs_posix接口不变,对于用户来说,只要知道posix文件接口就可以了,而不用关心内部实现细节。文件操作的真正实现细节是在各种文件系统中,RTT的elmfat就是其中一种,其相关操作由filesystem_operation_table来管理。RTT通过向filesysytem_operation_table注册文件操作集和向filesystem_table挂载文件系统来实现上层与中间层具体文件系统的脱离,从而实现dfs模块化,便于移植多种类型的文件系统。

暂时写到这,接下来将会讨论下RTT的elmfat文件系统:http://blog.csdn.net/flydream0/article/details/8841770

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章