IOT-OS之RT-Thread(十)--- DFS文件系统管理与devfs/elmfat示例

本文详细介绍了RT-Thread的DFS(Device File System),包括DFS的简介、架构和管理。DFS提供统一的POSIX接口,支持devfs和elmfat等多种文件系统。devfs用于管理I/O设备,elmfat适用于块设备如SPI Flash、SD Card。DFS通过设备抽象层将存储设备转换为文件系统可访问的接口,使得文件系统与底层驱动解耦。
摘要由CSDN通过智能技术生成

在早期的嵌入式系统中,需要存储的数据比较少,数据类型也比较单一,往往使用直接在存储设备中的指定地址写入数据的方法来存储数据。然而随着嵌入式设备功能的发展,需要存储的数据越来越多,也越来越复杂,这时仍使用旧方法来存储并管理数据就变得非常繁琐困难。因此我们需要新的数据管理方式来简化存储数据的组织形式,这种方式就是我们接下来要介绍的文件系统。

文件系统是一套实现了数据的存储、分级组织、访问和获取等操作的抽象数据类型 (Abstract data type),是一种用于向用户提供底层数据访问的机制。文件系统通常存储的基本单位是文件,即数据是按照一个个文件的方式进行组织。当文件比较多时,将导致文件繁多,不易分类、重名的问题。而文件夹作为一个容纳多个文件的容器而存在。

一、DFS设备文件系统简介

1.1 DFS简介

DFS 是 RT-Thread 提供的虚拟文件系统组件,全称为 Device File System,即设备虚拟文件系统,文件系统的名称使用类似 UNIX 文件、文件夹的风格,目录结构如下图所示:
DFS文件系统目录结构
在 RT-Thread DFS 中,文件系统有统一的根目录,使用 / 来表示。而在根目录下的 f1.bin 文件则使用 /f1.bin 来表示,2018 目录下的 f1.bin 目录则使用 /data/2018/f1.bin 来表示。即目录的分割符号是 /,这与 UNIX/Linux 完全相同,与 Windows 则不相同(Windows 操作系统上使用 \ 来作为目录的分割符)。

1.2 DFS架构

DFS 的层次架构如下图所示,主要分为 POSIX 接口层、虚拟文件系统层和设备抽象层。
DFS文件系统架构

  • POSIX 接口层:为应用程序提供统一的 POSIX 文件和目录操作接口:read、write、poll/select 等。

POSIX 表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写 POSIX),POSIX 标准定义了操作系统应该为应用程序提供的接口标准,是 IEEE 为要在各种 UNIX 操作系统上运行的软件而定义的一系列 API 标准的总称。

POSIX 标准意在期望获得源代码级别的软件可移植性。换句话说,为一个 POSIX 兼容的操作系统编写的程序,应该可以在任何其它 POSIX 操作系统(即使是来自另一个厂商)上编译执行。RT-Thread 支持 POSIX 标准接口,因此可以很方便的将 Linux/Unix 的程序移植到 RT-Thread 操作系统上。

在类 Unix 系统中,普通文件、设备文件、网络文件描述符是同一种文件描述符。而在 RT-Thread 操作系统中,使用 DFS 来实现这种统一性。有了这种文件描述符的统一性,我们就可以使用 poll/select 接口来对这几种描述符进行统一轮询,为实现程序功能带来方便。

使用 poll/select 接口可以阻塞地同时探测一组支持非阻塞的 I/O 设备是否有事件发生(如可读,可写,有高优先级的错误输出,出现错误等等),直至某一个设备触发了事件或者超过了指定的等待时间。这种机制可以帮助调用者寻找当前就绪的设备,降低编程的复杂度。

  • 虚拟文件系统层:支持多种类型的文件系统,如 FatFS、RomFS、DevFS 等,并提供普通文件、设备文件、网络文件描述符的管理。

用户可以将具体的文件系统注册到 DFS 中,如 FatFS、RomFS、DevFS 等,下面介绍几种常用的文件系统类型:

文件系统类型 文件系统功能描述
DevFS 设备文件系统,在 RT-Thread 操作系统中开启该功能后,可以将系统中的设备在 /dev 文件夹下虚拟成文件,使得设备可以按照文件的操作方式使用 read、write 等接口进行操作。
elmfat FS 专为小型嵌入式设备开发的一个兼容微软 FAT 格式的文件系统,采用 ANSI C 编写,具有良好的硬件无关性以及可移植性,是 RT-Thread 中最常用的文件系统类型。
Jffs2 一种日志闪存文件系统。主要用于 NOR 型闪存,基于 MTD 驱动层,特点是:可读写的、支持数据压缩的、基于哈希表的日志型文件系统,并提供了崩溃 / 掉电安全保护,提供写平衡支持等。
NFS 网络文件系统(Network File System)是一项在不同机器、不同操作系统之间通过网络共享文件的技术。在操作系统的开发调试阶段,可以利用该技术在主机上建立基于 NFS 的根文件系统,挂载到嵌入式设备上,可以很方便地修改根文件系统的内容。
RamFS 内存文件系统,它不能格式化,可以同时创建多个,在创建时可以指定其最大能使用的内存大小,优点是读写速度很快,但存在掉电丢失的风险。
RomFS 一种简单的、紧凑的、只读的文件系统,不支持动态擦写保存,按顺序存放数据,因而支持应用程序以 XIP(execute In Place,片内运行) 方式运行,在系统运行时, 节省 RAM 空间。
UFFS 超低功耗的闪存文件系统(Ultra-low-cost Flash File System)的简称。它是国人开发的、专为嵌入式设备等小内存环境中使用 Nand Flash 的开源文件系统。与嵌入式中常使用的 Yaffs 文件系统相比具有资源占用少、启动速度快、免费等优势。
  • 设备抽象层:支持多种类型的存储设备,如 SD Card、SPI Flash、Nand Flash 等。

设备抽象层将物理设备如 SD Card、SPI Flash、Nand Flash,抽象成符合文件系统能够访问的设备,例如 FAT 文件系统要求存储设备必须是块设备类型。

不同文件系统类型是独立于存储设备驱动而实现的,因此把底层存储设备的驱动接口和文件系统对接起来之后,才可以正确地使用文件系统功能。

二、DFS文件系统管理

本文依然按照与I / O设备模型框架类似的形式介绍DFS设备文件系统,逐层介绍其描述数据结构、接口函数、调用过程等。在DFS虚拟文件系统层,我们选择RT-Thread最常用的elmfat作为示例,同时启用DevFS来管理设备。

2.1 DFS POSIX接口层

文件系统至少有三个要素构成:文件系统、文件、目录,其中目录可以看作是文件的一种类型,所以文件系统至少需要提供描述文件系统本身和文件的数据结构及相应的接口函数。

  • 文件系统控制块

要想使用文件系统,需要先对其进行注册或挂载,要对文件系统操作,也需要对文件系统使用合理的数据结构进行描述,RT-Thread在DFS POSIX层对文件系统的描述如下:

// rt-thread-4.0.1\components\dfs\include\dfs_fs.h

/* Mounted file system */
struct dfs_filesystem
{
   
    rt_device_t dev_id;     /* Attached device */

    char *path;             /* File system mount point */
    const struct dfs_filesystem_ops *ops; /* Operations for file system type */

    void *data;             /* Specific file system data */
};

/* File system operations */
struct dfs_filesystem_ops
{
   
    char *name;
    uint32_t flags;      /* flags for file system operations */

    /* operations for file */
    const struct dfs_file_ops *fops;

    /* 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 (*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);
};


// rt-thread-4.0.1\components\dfs\src\dfs.c

/* Global variables */
const struct dfs_filesystem_ops *filesystem_operation_table[DFS_FILESYSTEM_TYPES_MAX];
struct dfs_filesystem filesystem_table[DFS_FILESYSTEMS_MAX];

dfs_filesystem结构体中成员dev_id是该文件系统要挂载的设备句柄,path为该文件系统要挂载的路径,ops则是该文件系统支持的接口函数集合,data则指向其私有数据。

在系统中可能不止挂载一种文件系统,所以多个文件系统的dfs_filesystem结构体与相应的dfs_filesystem_ops接口函数集合以全局数组的形式组织起来。

  • 文件控制块

将需要的文件系统注册挂载成功后,用户使用文件系统主要是对文件的操作,在该文件系统中文件是如何描述的,支持哪些接口函数集合,也都需要相应的数据结构描述,RT-Thread在DFS POSIX层对文件的描述如下:

// rt-thread-4.0.1\components\dfs\include\dfs_file.h

/* file descriptor */
#define DFS_FD_MAGIC     0xfdfd
struct dfs_fd
{
   
    uint16_t magic;              /* file descriptor magic number */
    uint16_t type;               /* Type (regular or socket) */

    char *path;                  /* Name (below mount point) */
    int ref_count;               /* Descriptor reference count */

    struct dfs_filesystem *fs;
    const struct dfs_file_ops *fops;

    uint32_t flags;              /* Descriptor flags */
    size_t   size;               /* Size in bytes */
    off_t    pos;                /* Current file position */

    void *data;                  /* Specific file system data */
};

struct dfs_file_ops
{
   
    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, size_t count);
    int (*write)    (struct dfs_fd *fd, const void *buf, size_t count);
    int (*flush)    (struct dfs_fd *fd);
    int (*lseek)    (struct dfs_fd *fd, off_t offset);
    int (*getdents) (struct dfs_fd *fd, struct dirent *dirp, uint32_t count);

    int (*poll)     (struct dfs_fd *fd, struct rt_pollreq *req);
};


// rt-thread-4.0.1\components\dfs\include\dfs.h

struct dfs_fdtable
{
   
    uint32_t maxfd;
    struct dfs_fd **fds;
};

// rt-thread-4.0.1\components\dfs\src\dfs.c

static struct dfs_fdtable _fdtab;

dfs_fd文件描述结构体包含magic幻数、type文件类型、path文件路径包括文件名,ref_count文件引用次数、fs文件系统句柄、fops文件操作接口函数集合、flags文件打开标识、size文件占用字节数、pos当前文件位置、data文件私有数据指针等。

一个文件系统一般会管理多个文件,这些文件也以全局数组的形式组织管理,文件描述符表结构体dfs_fdtable有两个成员:最大文件数量和文件描述符表的首地址,这两个成员可以描述一个数组的元素个数与首地址。

  • 文件系统初始化与注册

要想使用文件系统,需要先对其进行初始化,包括初始化文件系统的数据结构、将需要的文件系统挂载到指定设备的指定路径上。在DFS POSIC层的文件系统初始化过程如下:

// rt-thread-4.0.1\components\dfs\src\dfs.c

/**
 * this function will initialize device file system.
 */
int dfs_init(void)
{
   
    static rt_bool_t init_ok = RT_FALSE;

    if (init_ok)
    {
   
        rt_kprintf("dfs already init.\n");
        return 0;
    }

    /* clear filesystem operations table */
    memset((void *)filesystem_operation_table, 0, sizeof(filesystem_operation_table));
    /* clear filesystem table */
    memset(filesystem_table, 0, sizeof(filesystem_table));
    /* clean fd table */
    memset(&_fdtab, 0, sizeof(_fdtab));

    /* create device filesystem lock */
    rt_mutex_init(&fslock, "fslock", RT_IPC_FLAG_FIFO);

#ifdef DFS_USING_WORKDIR
    /* set current working directory */
    memset(working_directory, 0, sizeof(working_directory));
    working_directory[0] = '/';
#endif

#ifdef RT_USING_DFS_DEVFS
    {
   
        extern int devfs_init(void);

        /* if enable devfs, initialize and mount it as soon as possible */
        devfs_init();

        dfs_mount(NULL, "/dev", "devfs", 0, 0);
    }
#endif

    init_ok = RT_TRUE;

    return 0;
}
INIT_PREV_EXPORT(dfs_init);

dfs_init函数完成了filesystem_operation_table、filesystem_table、_fdtab三个表的重置初始化操作,同时完成了devfs设备文件系统的初始化与挂载操作(devfs初始化与挂载操作需要下面devfs文件系统层的支持),当使能宏定义RT_USING_DFS_DEVFS时devfs不需要用户再次挂载即可使用。dfs_init函数使用RT-Thread自动初始化组件,在系统启动前自动调用完成DFS框架初始化。

参考I / O设备管理模型,一个对象初始化后需要将该对象的接口函数集合注册到RT-Thread对象管理层后才能通过统一的对象管理接口操作该对象。对于DFS设备文件系统来说,在对文件系统对象完成初始化后,需要将该文件系统的操作函数集合dfs_filesystem_ops注册到DFS设备文件系统层后,用户才能通过DFS设备文件系统接口函数来操作该文件系统。DFS注册过程如下:

// rt-thread-4.0.1\components\dfs\src\dfs_fs.c

/**
 * this function will register a file system instance to device file system.
 *  * @param ops the file system instance to be registered.
 *  * @return 0 on successful, -1 on failed.
 */
int dfs_register(const struct dfs_filesystem_ops *ops)
{
   
    int ret = RT_EOK;
    const struct dfs_filesystem_ops **empty = NULL;
    const struct dfs_filesystem_ops **iter;

    /* lock filesystem */
    dfs_lock();
    /* check if this filesystem was already registered */
    for (iter = &filesystem_operation_table[0];
            iter < &filesystem_operation_table[DFS_FILESYSTEM_TYPES_MAX]; iter ++)
    {
   
        /* find out an empty filesystem type entry */
        if (*iter == NULL)
            (empty == NULL) ? (empty = iter) : 0;
        else if (strcmp((*iter)->name, ops->name) == 0)
        {
   
            rt_set_errno(-EEXIST);
            ret = -1;
            break;
        }
    }

    /* save the filesystem's operations */
    if (empty == NULL)
    {
   
        rt_set_errno(-ENOSPC);
        LOG_E("There is no space to register this file system (%s).", ops->name);
        ret = -1;
    }
    else if (ret == RT_EOK)
    {
   
        *empty = ops;
    }

    dfs_unlock();
    return ret;
}

dfs_register函数主要是从filesystem_operation_table数组中找出一个空元素,然后将待注册的dfs_filesystem_ops作为参数赋值给filesystem_operation_table中找到的空元素,后面如果想操作某个文件系统,从filesystem_operation_table数组中根据名称查找到对应的文件系统接口函数集合,然后通过对应的接口函数指针调用该文件系统注册的操作函数即可。

  • 文件系统接口函数

对文件系统的操作主要有创建、挂载、查询等操作。要想使用文件系统来管理文件,需要先将该文件系统挂载到设备某个位置(也即某个路径),要想将文件系统挂载到该设备某个路径下,需要先将该设备按照该文件系统的存储要求进行格式化,对该设备按文件系统需求进行格式化的过程称为在该设备上创建一个文件系统。文件系统在特定设备上创建,并

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流云IoT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值