RT-Thread進階筆記之虛擬文件系統

1.引入

1.1 文件系統引入

在早期的嵌入式系統中,需要存儲的數據比較少,數據類型也比較單一,往往使用直接在存儲設備中的指定地址寫入數據的方法來存儲數據。然而隨着嵌入式設備功能的發展,需要存儲的數據越來越多,也越來越複雜,這時仍使用舊方法來存儲並管理數據就變得非常繁瑣困難。因此我們需要新的數據管理方式來簡化存儲數據的組織形式,這就是文件系統的由來。

1.2 虛擬文件系統引入

爲了統一衆多不同類型的文件系統,虛擬文件系統對實際文件系統進行抽象,採用統一的文件系統向用戶提供相應的一組統一的標準的文件操作接口(open,read,close,select,poll等)。

2 DFS 簡介

DFS( Device File System)是一種抽象的文件機制,RT-Thread中對文件系統的相關操作實際上都是通過操作DFS實現,也就是說DFS是對各種文件系統的抽象。DFS似的其他部分無須關心不同文件系統之間的差異,使得RT-Thread可以支持多種類型的文件系統。

3 DFS 框架

RT-Thread DFS 組件的主要功能特點有:

  • 爲應用程序提供統一的 POSIX 文件和目錄操作接口:read、write、poll/select 等。
  • 支持多種類型的文件系統,如 FatFS、RomFS、DevFS 等,並提供普通文件、設備文件、網絡文件描述符的管理。
  • 支持多種類型的存儲設備,如 SD Card、SPI Flash、Nand Flash 等。

DFS 的層次架構如下圖所示,主要分爲 POSIX 接口層、虛擬文件系統層和設備抽象層。

POSIX 接口層:
POSIX 表示可移植操作系統接口(Portable Operating System Interface of UNIX,縮寫 POSIX),POSIX 標準定義了操作系統應該爲應用程序提供的接口標準,是 IEEE 爲要在各種 UNIX 操作系統上運行的軟件而定義的一系列 API 標準的總稱。
虛擬文件系統層:
用戶可以將具體的文件系統註冊到 DFS 中,如 FatFS、RomFS、DevFS 等。
設備抽象層:
設備抽象層將物理設備如 SD Card、SPI Flash、Nand Flash,抽象成符合文件系統能夠訪問的設備,例如 FAT 文件系統要求存儲設備必須是塊設備類型。
不同文件系統類型是獨立於存儲設備驅動而實現的,因此把底層存儲設備的驅動接口和文件系統對接起來之後,纔可以正確地使用文件系統功能。

4 DFS 數據結構

文件系統操作表:

const struct dfs_filesystem_ops *filesystem_operation_table[DFS_FILESYSTEM_TYPES_MAX];


文件系統表:

struct dfs_filesystem filesystem_table[DFS_FILESYSTEMS_MAX];


文件描述符:

static struct dfs_fdtable _fdtab;

5 虛擬文件系統使用步驟

  1. 初始化 DFS 組件。
  2. 註冊具體類型的文件系統。
  3. 掛載文件系統
  4. 當文件系統不再使用,可以將它卸載。

5.1 初始化 DFS 組件

dfs_init() 函數會初始化 DFS 所需的相關資源,創建一些關鍵的數據結構, 有了這些數據結構,DFS 便能在系統中找到特定的文件系統,並獲得對特定存儲設備內文件的操作方法。
dfs_init()加入了自動初始化機制,在系統上電後會自動運行dfs_init()。

INIT_PREV_EXPORT(dfs_init);

實例化DFS 組件相關的數據結構

const struct dfs_filesystem_ops *filesystem_operation_table[DFS_FILESYSTEM_TYPES_MAX];
struct dfs_filesystem filesystem_table[DFS_FILESYSTEMS_MAX]
static struct dfs_fdtable _fdtab

創建當前目錄表

char working_directory[DFS_PATH_MAX] = {"/"}

初始化 DFS:

  • 清除文件系統操作表
  • 清除文件系統表
  • 清除文件描述符表
  • 初始化互斥量
  • 設置當前工作目錄爲“/”

5.2 註冊具體類型的文件系統

在 DFS 組件初始化之後,還需要初始化使用的具體類型的文件系統,也就是將具體類型的文件系統註冊到 DFS 中。註冊文件系統的接口如下所示:

int dfs_register(const struct dfs_filesystem_ops *ops);
  • 檢查這個文件系統是否已經存在於文件系統操作表的目錄中
  • 在文件系統操作表中找出一個空的文件類型條目
  • 將這個文件系統的數據結構地址賦值給空的文件系統操作表目錄

5.3 掛載文件系統

在掛載文件系統之前,如果是用作存儲設備,還需要先存儲設備註冊爲塊設備,然後格式化成對應的文件系統後,才能掛載。
在 RT-Thread 中,掛載是指將一個存儲設備掛接到一個已存在的路徑上。我們要訪問存儲設備中的文件,必須將文件所在的分區掛載到一個已存在的路徑上,然後通過這個路徑來訪問存儲設備。掛載文件系統的接口如下所示:

int dfs_mount(const char   *device_name,
              const char   *path,
              const char   *filesystemtype,
              unsigned long rwflag,
              const void   *data);
  • 在文件系統操作表中找出特定的文件系統
  • 爲特殊文件系統建立完整路徑
  • 檢查路徑是否存在
  • 檢查文件系統是否掛載在文件系統表中
  • 檢查文件系統表是否有空餘,如果有,把空餘地址指向給此文件系統
  • 註冊文件系統
  • 調用此文件系統的掛載接口

5.4 卸載文件系統

當某個文件系統不需要再使用了,那麼可以將它卸載掉。卸載文件系統的接口如下所示:

int dfs_unmount(const char *specialfile);
  • 檢查路徑是否存在
  • 在文件系統表中找到此文件系統
  • 清除文件系統表的這個條目內容
  • 調用此文件系統的卸載接口

6 文件系統實例演示

6.1 devfs文件系統

6.1.1 簡介

devfs是設備文件系統,設備文件系統是用來把一切設備都抽象爲像文件那樣操作(如可讀,可寫)。
devfs默認掛載在"/dev"路徑下。但是會發現在根目錄下使用shell 的ls命令不能看到“/dev”目錄, 但是cd “/dev” 能進入dev 目錄 ,並且進入dev目錄後也能顯示dev下的設備。
因爲根目錄“/"下並沒有創建任何文件夾,所以在根目錄下ls命令自然看不到“/dev”目錄。由於掛載文件系統需要掛載在一個已存在的路徑上,devfs屬於特殊文件系統,DFS爲devfs設備文件系統註冊時設置了專門的“/dev”路徑以供設備文件系統掛載。

   /* Check if the path exists or not, raw APIs call, fixme */
    if ((strcmp(fullpath, "/") != 0) && (strcmp(fullpath, "/dev") != 0))
    {
        struct dfs_fd fd;

        if (dfs_file_open(&fd, fullpath, O_RDONLY | O_DIRECTORY) < 0)
        {
            rt_free(fullpath);
            rt_set_errno(-ENOTDIR);

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

6.1.2 註冊devfs文件系統

如果開啓了DFS,devfs設備文件系統會在dfs_init()裏自動初始化和掛載。

#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

設置devfs文件系統的數據結構:_device_fs

static const struct dfs_filesystem_ops _device_fs =
{
    "devfs",
    DFS_FS_FLAG_DEFAULT,
    &_device_fops,

    dfs_device_fs_mount,
    RT_NULL,
    RT_NULL,
    RT_NULL,

    RT_NULL,
    dfs_device_fs_stat,
    RT_NULL,
};

將devfs文件系統的數據結構掛載到文件系統操作表裏

int devfs_init(void)
{
    /* register rom file system */
    dfs_register(&_device_fs);

    return 0;
}
  • 檢查devfs文件系統是否已經存在於文件系統操作表的目錄中
  • 在文件系統操作表中找出一個空的文件類型條目
  • 將devfs文件系統的數據結構_device_fs地址賦值給空的文件系統操作表目錄

文件系統操作表filesystem_operation_table的第一個目錄:

6.1.2 掛載devfs文件系統

dfs_mount(NULL, "/dev", "devfs", 0, 0)
  • 在文件系統操作表中找出devfs文件系統
  • 跳過檢查"/dev"路徑是否存在
  • 檢查devfs文件系統是否已經掛載在文件系統表中
  • 檢查文件系統表是否有空餘,如果有,把空餘地址賦值給devfs文件系統
  • 註冊文件系統
  • 調用devfs文件系統的掛載接口

文件系統表filesystem_table的第一個目錄:

6.1.3 測試devfs文件系統

在根目錄下使用shell 的cd命令切換到/dev目錄,然後使用ls命令:
在這裏插入圖片描述

6.2 RomFS文件系統

6.2.1 簡介

RomFS是在嵌入式設備上常用的一種文件系統,具備體積小,可靠性高,讀取速度快等優點,常用來作爲系統初始文件系統。但也具有其侷限性,RomFS是一種只讀文件系統。

6.2.2 註冊devfs文件系統

把RomFS當作初始文件系統rootfs掛載在根目錄,在RomFS裏創建幾個目錄,用於其他文件系統的掛載點。
更改…\rt-thread\components\dfs\filesystems\romfs\romfs.c文件,添加mnt文件夾和user文件夾。

#include <rtthread.h>
#include <dfs_romfs.h>

static const struct romfs_dirent _romfs_root[] = {
    {ROMFS_DIRENT_DIR, "mnt", RT_NULL, 0},
    {ROMFS_DIRENT_DIR, "user", RT_NULL, 0}
};

const struct romfs_dirent romfs_root = {
    ROMFS_DIRENT_DIR, "/", (rt_uint8_t *)_romfs_root, sizeof(_romfs_root)/sizeof(_romfs_root[0])
};

** 設置romfs文件系統的數據結構:_romfs **

static const struct dfs_filesystem_ops _romfs =
{
    "rom",
    DFS_FS_FLAG_DEFAULT,
    &_rom_fops,

    dfs_romfs_mount,
    dfs_romfs_unmount,
    NULL,
    NULL,

    NULL,
    dfs_romfs_stat,
    NULL,
};

將romfs文件系統的數據結構掛載到文件系統操作表裏

int dfs_romfs_init(void)
{
    /* register rom file system */
    dfs_register(&_romfs);
    return 0;
}
INIT_COMPONENT_EXPORT(dfs_romfs_init);//自動初始化
  • 檢查romfs文件系統是否已經存在於文件系統操作表的目錄中
  • 在文件系統操作表中找出一個空的文件類型條目
  • 將romfs文件系統的數據結構_romfs地址賦值給空的文件系統操作表目錄

文件系統操作表filesystem_operation_table的第二個目錄:

6.2.3 掛載romfs文件系統

int mnt_init(void)
{
   if(dfs_mount (RT_NULL,"/","rom",0,&(romfs_root)) == 0)
   {
       rt_kprintf("ROM file system initializated;\n");
   }
   else
   {
        rt_kprintf("ROM file system initializate failed;\n");
   }
   return 0;
}
INIT_ENV_EXPORT(mnt_init);
  • 在文件系統操作表中找出romfs文件系統
  • 檢查"/"路徑是否存在
  • 檢查romfs文件系統是否已經掛載在文件系統表中
  • 檢查文件系統表是否有空餘,如果有,把空餘地址指向romfs文件系統
  • 註冊文件系統
  • 調用romfs文件系統的掛載接口
    文件系統表filesystem_table的第二個目錄:

6.2.4 測試RomFS文件系統

在根目錄下使用shell 的ls命令:

6.3 RamFS文件系統

6.3.1 簡介

RamFS是內存文件系統,它不能格式化,可以同時創建多個,在創建時可以指定其最大能使用的內存大小。其優點是讀寫速度很快,但存在掉電丟失的風險。如果一個進程的性能瓶頸是硬盤的讀寫,那麼可以考慮在RamFS上進行大文件的讀寫操作。
RT-Thread的RamFS設計之初未考慮支持文件夾,所以不能使用mkdir。

6.3.2 註冊RamFS文件系統

設置ramfs文件系統的數據結構:_ramfs

static const struct dfs_filesystem_ops _ramfs =
{
    "ram",
    DFS_FS_FLAG_DEFAULT,
    &_ram_fops,

    dfs_ramfs_mount,
    dfs_ramfs_unmount,
    NULL, /* mkfs */
    dfs_ramfs_statfs,

    dfs_ramfs_unlink,
    dfs_ramfs_stat,
    dfs_ramfs_rename,
};

將ramfs文件系統的數據結構掛載到文件系統操作表裏

int dfs_ramfs_init(void)
{
    /* register ram file system */
    dfs_register(&_ramfs);

    return 0;
}
INIT_COMPONENT_EXPORT(dfs_ramfs_init);
  • 檢查ramfs文件系統是否已經存在於文件系統操作表的目錄中
  • 在文件系統操作表中找出一個空的文件類型條目
  • 將ramfs文件系統的數據結構_ramfs地址賦值給空的文件系統操作表目錄

文件系統操作表filesystem_operation_table的第三個目錄:

6.3.3 掛載RamFS文件系統

int mnt_ram_elminit(void)
{
   if(dfs_mount (RT_NULL,"/mnt","ram",0,dfs_ramfs_create(rampool, 1024)) == 0)
   {
       rt_kprintf("ram file system initializated;\n");
   }
   else
   {
        rt_kprintf("ram file system initializate failed;\n");
   }
   return 0;
}
INIT_ENV_EXPORT(mnt_ram_elminit);
  • 在文件系統操作表中找出ramfs文件系統
  • 檢查"/mnt"路徑是否存在
  • 檢查ramfs文件系統是否已經掛載在文件系統表中
  • 檢查文件系統表是否有空餘,如果有,把空餘地址指向ramfs文件系統
  • 註冊文件系統
  • 調用ramfs文件系統的掛載接口

文件系統表filesystem_table的第三個目錄:

6.3.4 測試RamFS文件系統

在根目錄下使用shell 的cd命令切換到/mnt目錄,然後使用ls命令:

6.4 elm-FAT文件系統

6.4.1 簡介

FatFs 是一個通用的文件系統(FAT/exFAT)模塊,用於在小型嵌入式系統中實現FAT文件系統。

6.4.2 使用流程

  • 初始化 DFS 組件。
  • 初始化具體類型的文件系統。
  • 在存儲器上創建塊設備。
  • 格式化塊設備。
  • 掛載塊設備到 DFS 目錄中。
  • 當文件系統不再使用,可以將它卸載

6.4.3 註冊elm-FAT文件系統

設置fatfs文件系統的數據結構:dfs_elm

static const struct dfs_filesystem_ops dfs_elm =
{
    "elm",
    DFS_FS_FLAG_DEFAULT,
    &dfs_elm_fops,

    dfs_elm_mount,
    dfs_elm_unmount,
    dfs_elm_mkfs,
    dfs_elm_statfs,

    dfs_elm_unlink,
    dfs_elm_stat,
    dfs_elm_rename,
};

將fatfs文件系統的數據結構掛載到文件系統操作表裏

int elm_init(void)
{
    /* register fatfs file system */
    dfs_register(&dfs_elm);

    return 0;
}
INIT_COMPONENT_EXPORT(elm_init);
  • 檢查fatfs文件系統是否已經存在於文件系統操作表的目錄中
  • 在文件系統操作表中找出一個空的文件類型條目
  • 將fatfs文件系統的數據結構dfs_elm 地址賦值給空的文件系統操作表目錄

elm-FAT文件系統註冊過程如下圖所示:

6.4.4 掛載elm-FAT文件系統

void sd_mount(void *parameter)
{
    while (1)
    {
        rt_thread_mdelay(500);
        if(rt_device_find("sd0") != RT_NULL)
        {
            if (dfs_mount("sd0", "/fatfs", "elm", 0, 0) == RT_EOK)
            {
                LOG_I("sd card mount to '/fatfs'");
                break;
            }
            else
            {
                LOG_W("sd card mount to '/fatfs' failed!");
            }
        }
    }
}

int stm32_sdcard_mount(void)
{
    rt_thread_t tid;

    tid = rt_thread_create("sd_mount", sd_mount, RT_NULL,
                           1024, RT_THREAD_PRIORITY_MAX - 2, 20);
    if (tid != RT_NULL)
    {
        rt_thread_startup(tid);
    }
    else
    {
        LOG_E("create sd_mount thread err!");
    }
    return RT_EOK;
}
INIT_APP_EXPORT(stm32_sdcard_mount);
  • 在文件系統操作表中找出elm文件系統
  • 檢查"/fatfs"路徑是否存在
  • 檢查elm文件系統是否已經掛載在文件系統表中
  • 檢查文件系統表是否有空餘,如果有,把空餘地址指向elm文件系統
  • 註冊文件系統
  • 調用elm文件系統的掛載接口

6.4.5 測試elm-FAT文件系統

在根目錄下使用shell 的cd命令切換到/fatfs目錄,然後使用ls命令:

6.5 littlefs文件系統

6.5.1 簡介

littlefs 是 ARM 官方推出的,專爲嵌入式系統設計的文件系統,相比傳統的文件系統,littlefs 具有以下優點:

  • 自帶擦寫均衡
  • 支持掉電保護
  • 佔用的 RAM/ROM 少

littlefs 自帶的擦寫均衡和掉電保護使開發者可以放心的將文件系統掛載到 nor flash 上。

層級關係
littlefs 在 RT-Thread 上運行的層級關係圖如下所示:

6.5.2 使用流程

  • 初始化 DFS 組件。
  • 使能 littlefs 軟件包。
  • 使能 MTD 設備。
  • 使能 fal,用來創建 MTD 設備。
  • 創建 MTD 設備
  • 掛載MTD設備到 DFS 目錄中。

6.5.3 註冊littlefs文件系統

**設置littlefs文件系統的數據結構:_dfs_lfs_ops **

static const struct dfs_filesystem_ops _dfs_lfs_ops = {
    "lfs",
    DFS_FS_FLAG_DEFAULT,
    &_dfs_lfs_fops,

    _dfs_lfs_mount,
    _dfs_lfs_unmount,

    _dfs_lfs_mkfs,
    _dfs_lfs_statfs,
    _dfs_lfs_unlink,
    _dfs_lfs_stat,
    _dfs_lfs_rename,
};

將littlefs文件系統的數據結構掛載到文件系統操作表裏

int dfs_lfs_init(void)
{
    /* init file system lock */
    rt_mutex_init(&_lfs_lock, "lfsmtx", RT_IPC_FLAG_FIFO);
    /* register ram file system */
    return dfs_register(&_dfs_lfs_ops);
}
INIT_COMPONENT_EXPORT(dfs_lfs_init);
  • 檢查littlefs文件系統是否已經存在於文件系統操作表的目錄中
  • 在文件系統操作表中找出一個空的文件類型條目
  • 將littlefs文件系統的數據結構_dfs_lfs_ops 地址賦值給空的文件系統操作表目錄

6.5.4 掛載littlefs文件系統

    ...
    struct rt_device *mtd_dev = RT_NULL;

    ...
    /* 初始化 fal */    
    fal_init();
    /* 生成 mtd 設備 */
    mtd_dev = fal_mtd_nor_device_create(FS_PARTITION_NAME);
    if (!mtd_dev)
    {
        LOG_E("Can't create a mtd device on '%s' partition.", FS_PARTITION_NAME);
    }
    else
    {
        /* 掛載 littlefs */
        if (dfs_mount(FS_PARTITION_NAME, "/littlefs", "lfs", 0, 0) == 0)
        {
            LOG_I("Filesystem initialized!");
        }
        else
        {
            /* 格式化文件系統 */
            dfs_mkfs("lfs", FS_PARTITION_NAME);
            /* 掛載 littlefs */
            if (dfs_mount("filesystem", "/littlefs", "lfs", 0, 0) == 0)
            {
                LOG_I("Filesystem initialized!");
            }
            else
            {
                LOG_E("Failed to initialize filesystem!");
            }
        }
    }
    ...
    
  • 在文件系統操作表中找出lfs文件系統
  • 檢查"/littlefs"路徑是否存在
  • 檢查littlefs文件系統是否已經掛載在文件系統表中
  • 檢查文件系統表是否有空餘,如果有,把空餘地址指向littlefs文件系統
  • 註冊文件系統
  • 調用littlefs文件系統的掛載接口

6.5.5 測試littlefs文件系統

在根目錄下使用shell 的cd命令切換到/littlefs目錄,然後使用ls命令:

注意:spi_flash.h中缺少一個頭文件,需要自行添加

6.6 文件系統綜合例程

DevFS、RomFS、RamFS、FatFS文件系統配置:
在這裏插入圖片描述
littlefs文件系統配置;
在這裏插入圖片描述
主程序:

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <fal.h>

#include <dfs_fs.h>
#include <dfs_romfs.h>
#include <dfs_ramfs.h>
#include <dfs_posix.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

/* defined the LED0 pin: PH10 */
#define LED0_PIN    GET_PIN(H, 10)

int main(void)
{
    int count = 1;
    rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);

    while (count++)
    {
        rt_pin_write(LED0_PIN, PIN_HIGH);
        rt_thread_mdelay(1000);
        rt_pin_write(LED0_PIN, PIN_LOW);
        rt_thread_mdelay(1000);
    }

    return RT_EOK;
}
rt_uint8_t rampool[128];

void sd_mount(void *parameter)
{
    while (1)
    {
        rt_thread_mdelay(500);
        if(rt_device_find("sd0") != RT_NULL)
        {
            if (dfs_mount("sd0", "/fatfs", "elm", 0, 0) == RT_EOK)
            {
                LOG_I("sd card mount to '/fatfs'");
                break;
            }
            else
            {
                LOG_W("sd card mount to '/fatfs' failed!");
            }
        }
    }
}

int fs_init(void)
{
    /* partition initialized */
    fal_init();

    if(dfs_mount (RT_NULL,"/","rom",0,&(romfs_root)) == 0)
    {
        LOG_I("ROM file system initializated;\n");
    }
    else
    {
        LOG_I("ROM file system initializate failed;\n");
    }

    if(dfs_mount (RT_NULL,"/ram","ram",0,dfs_ramfs_create(rampool, sizeof(rampool))) == 0)
    {
        LOG_I("ram file system initializated;\n");
    }
    else
    {
        LOG_I("ram file system initializate failed;\n");
    }

    /* Create a block device on the file system partition of spi flash */
    struct rt_device *flash_dev = fal_mtd_nor_device_create("filesystem");

    if (flash_dev == RT_NULL)
    {
        LOG_I("Can't create a mtd device on '%s' partition.", "filesystem");
    }
    else
    {
        LOG_I("Create a mtd device on the %s partition of flash successful.", "filesystem");
    }
    /* mount the file system from "filesystem" partition of spi flash. */
    if (dfs_mount(flash_dev->parent.name, "/littlefs", "lfs", 0, 0) == 0)
    {
        LOG_I("littlefs initialized!");
    }
    else
    {
        dfs_mkfs("lfs", flash_dev->parent.name);
        if (dfs_mount(flash_dev->parent.name, "/", "lfs", 0, 0) == 0)
        {
            LOG_I("littlefs initialized!");
        }
    }

    rt_thread_t tid;

    tid = rt_thread_create("sd_mount", sd_mount, RT_NULL,
                           1024, RT_THREAD_PRIORITY_MAX - 2, 20);
    if (tid != RT_NULL)
    {
        rt_thread_startup(tid);
    }
    else
    {
        LOG_E("create sd_mount thread err!");
    }
    return 0;
}
INIT_COMPONENT_EXPORT(fs_init);

測試:

工程地址:https://gitee.com/Aladdin-Wang/RT-FOTA-STM32L431/tree/master/stm32f429_app

歡迎關注本人公衆號:
在這裏插入圖片描述

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