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

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

文件系統是一套實現了數據的存儲、分級組織、訪問和獲取等操作的抽象數據類型 (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數組中根據名稱查找到對應的文件系統接口函數集合,然後通過對應的接口函數指針調用該文件系統註冊的操作函數即可。

  • 文件系統接口函數

對文件系統的操作主要有創建、掛載、查詢等操作。要想使用文件系統來管理文件,需要先將該文件系統掛載到設備某個位置(也即某個路徑),要想將文件系統掛載到該設備某個路徑下,需要先將該設備按照該文件系統的存儲要求進行格式化,對該設備按文件系統需求進行格式化的過程稱爲在該設備上創建一個文件系統。文件系統在特定設備上創建,並掛載到特定路徑後就可以正常使用該文件系統了,比如查詢該文件系統的基本信息、使用該文件系統管理文件,也即在掛載路徑下使用該文件系統提供的文件接口函數訪問文件。

先看文件系統的創建過程:

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

/**
 * make a file system on the special device
 *
 * @param fs_name the file system name
 * @param device_name the special device name
 *
 * @return 0 on successful, otherwise failed.
 */
int dfs_mkfs(const char *fs_name, const char *device_name)
{
    int index;
    rt_device_t dev_id = NULL;

    /* check device name, and it should not be NULL */
    if (device_name != NULL)
        dev_id = rt_device_find(device_name);

    if (dev_id == NULL)
    {
        rt_set_errno(-ENODEV);
        LOG_E("Device (%s) was not found", device_name);
        return -1;
    }

    /* lock file system */
    dfs_lock();
    /* find the file system operations */
    for (index = 0; index < DFS_FILESYSTEM_TYPES_MAX; index ++)
    {
        if (filesystem_operation_table[index] != NULL &&
            strcmp(filesystem_operation_table[index]->name, fs_name) == 0)
            break;
    }
    dfs_unlock();

    if (index < DFS_FILESYSTEM_TYPES_MAX)
    {
        /* find file system operation */
        const struct dfs_filesystem_ops *ops = filesystem_operation_table[index];
        if (ops->mkfs == NULL)
        {
            LOG_E("The file system (%s) mkfs function was not implement", fs_name);
            rt_set_errno(-ENOSYS);
            return -1;
        }

        return ops->mkfs(dev_id);
    }

    LOG_E("File system (%s) was not found.", fs_name);

    return -1;
}

dfs_mkfs函數最終調用的是下面DFS虛擬文件系統層註冊的ops->mkfs函數,該函數最終由要使用的文件系統實現。

下面看文件系統的掛載過程:

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

/**
 * this function will mount a file system on a specified path.
 *
 * @param device_name the name of device which includes a file system.
 * @param path the path to mount a file system
 * @param filesystemtype the file system type
 * @param rwflag the read/write etc. flag.
 * @param data the private data(parameter) for this file system.
 *
 * @return 0 on successful or -1 on failed.
 */
int dfs_mount(const char   *device_name,
              const char   *path,
              const char   *filesystemtype,
              unsigned long rwflag,
              const void   *data)
{
    const struct dfs_filesystem_ops **ops;
    struct dfs_filesystem *iter;
    struct dfs_filesystem *fs = NULL;
    char *fullpath = NULL;
    rt_device_t dev_id;

    /* open specific device */
    if (device_name == NULL)
    {
        /* which is a non-device filesystem mount */
        dev_id = NULL;
    }
    else if ((dev_id = rt_device_find(device_name)) == NULL)
    {
        /* no this device */
        rt_set_errno(-ENODEV);
        return -1;
    }

    /* find out the specific filesystem */
    dfs_lock();

    for (ops = &filesystem_operation_table[0];
            ops < &filesystem_operation_table[DFS_FILESYSTEM_TYPES_MAX]; ops++)
        if ((*ops != NULL) && (strcmp((*ops)->name, filesystemtype) == 0))
            break;

    dfs_unlock();

    if (ops == &filesystem_operation_table[DFS_FILESYSTEM_TYPES_MAX])
    {
        /* can't find filesystem */
        rt_set_errno(-ENODEV);
        return -1;
    }

    /* check if there is mount implementation */
    if ((*ops == NULL) || ((*ops)->mount == NULL))
    {
        rt_set_errno(-ENOSYS);
        return -1;
    }

    /* make full path for special file */
    fullpath = dfs_normalize_path(NULL, path);
    if (fullpath == NULL) /* not an abstract path */
    {
        rt_set_errno(-ENOTDIR);
        return -1;
    }

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

    /* check whether the file system mounted or not  in the filesystem table
     * if it is unmounted yet, find out an empty entry */
    dfs_lock();

    for (iter = &filesystem_table[0];
            iter < &filesystem_table[DFS_FILESYSTEMS_MAX]; iter++)
    {
        /* check if it is an empty filesystem table entry? if it is, save fs */
        if (iter->ops == NULL)
            (fs == NULL) ? (fs = iter) : 0;
        /* check if the PATH is mounted */
        else if (strcmp(iter->path, path) == 0)
        {
            rt_set_errno(-EINVAL);
            goto err1;
        }
    }

    if ((fs == NULL) && (iter == &filesystem_table[DFS_FILESYSTEMS_MAX]))
    {
        rt_set_errno(-ENOSPC);
        LOG_E("There is no space to mount this file system (%s).", filesystemtype);
        goto err1;
    }

    /* register file system */
    fs->path   = fullpath;
    fs->ops    = *ops;
    fs->dev_id = dev_id;
    /* release filesystem_table lock */
    dfs_unlock();

    /* open device, but do not check the status of device */
    if (dev_id != NULL)
    {
        if (rt_device_open(fs->dev_id,
                           RT_DEVICE_OFLAG_RDWR) != RT_EOK)
        {
            /* The underlaying device has error, clear the entry. */
            dfs_lock();
            memset(fs, 0, sizeof(struct dfs_filesystem));

            goto err1;
        }
    }

    /* call mount of this filesystem */
    if ((*ops)->mount(fs, rwflag, data) < 0)
    {
        /* close device */
        if (dev_id != NULL)
            rt_device_close(fs->dev_id);

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

        goto err1;
    }

    return 0;

err1:
    dfs_unlock();
    rt_free(fullpath);

    return -1;
}

dfs_mount函數最終也是通過調用下層註冊的(*ops)->mount函數實現,該函數最終由要使用的文件系統實現。

文件系統操作還有其他的一些接口函數,下面只給出函數聲明:

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

/**
 * this function will unmount a file system on specified path.
 *
 * @param specialfile the specified path which mounted a file system.
 *
 * @return 0 on successful or -1 on failed.
 */
int dfs_unmount(const char *specialfile);

/**
 * this function will return the information about a mounted file system.
 *
 * @param path the path which mounted file system.
 * @param buffer the buffer to save the returned information.
 *
 * @return 0 on successful, others on failed.
 */
int dfs_statfs(const char *path, struct statfs *buffer);


// rt-thread-4.0.1\components\dfs\include\dfs.h
struct statfs
{
    size_t f_bsize;   /* block size */
    size_t f_blocks;  /* total data blocks in file system */
    size_t f_bfree;   /* free blocks in file system */
};

DFS POSIX接口主要是針對文件和目錄的操作,對文件系統的操作比較少,也只提供了對文件系統的信息查詢接口,實際調用的還是上面的dfs_statfs函數,DFS POSIX文件系統API如下:

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

/**
 * this function is a POSIX compliant version, which will return the
 * information about a mounted file system.
 *
 * @param path the path which mounted file system.
 * @param buf the buffer to save the returned information.
 *
 * @return 0 on successful, others on failed.
 */
int statfs(const char *path, struct statfs *buf)
{
    int result;

    result = dfs_statfs(path, buf);
    if (result < 0)
    {
        rt_set_errno(result);

        return -1;
    }

    return result;
}
RTM_EXPORT(statfs);
  • 文件訪問接口函數

文件系統註冊接口函數集合時,連同文件接口函數集合也一起註冊了,回顧下前面dfs_filesystem_ops結構體中有一個成員dfs_file_ops *指向該文件系統中文件操作接口函數集合的地址。當文件系統掛載成功後,就可以直接使用該文件系統提供的文件操作接口函數對文件進行訪問了。

對文件的訪問最終還是通過調用該文件系統向上層註冊的文件訪問函數實現的,下面以文件打開函數爲例,看下這個過程:

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

/**
 * this function will open a file which specified by path with specified flags.
 *
 * @param fd the file descriptor pointer to return the corresponding result.
 * @param path the specified file path.
 * @param flags the flags for open operator.
 *
 * @return 0 on successful, -1 on failed.
 */
int dfs_file_open(struct dfs_fd *fd, const char *path, int flags)
{
    struct dfs_filesystem *fs;
    char *fullpath;
    int result;

    /* parameter check */
    if (fd == NULL)
        return -EINVAL;

    /* make sure we have an absolute path */
    fullpath = dfs_normalize_path(NULL, path);
    if (fullpath == NULL)
    {
        return -ENOMEM;
    }

    LOG_D("open file:%s", fullpath);

    /* find filesystem */
    fs = dfs_filesystem_lookup(fullpath);
    if (fs == NULL)
    {
        rt_free(fullpath); /* release path */

        return -ENOENT;
    }

    LOG_D("open in filesystem:%s", fs->ops->name);
    fd->fs    = fs;             /* set file system */
    fd->fops  = fs->ops->fops;  /* set file ops */

    /* initialize the fd item */
    fd->type  = FT_REGULAR;
    fd->flags = flags;
    fd->size  = 0;
    fd->pos   = 0;
    fd->data  = fs;

    if (!(fs->ops->flags & DFS_FS_FLAG_FULLPATH))
    {
        if (dfs_subdir(fs->path, fullpath) == NULL)
            fd->path = rt_strdup("/");
        else
            fd->path = rt_strdup(dfs_subdir(fs->path, fullpath));
        rt_free(fullpath);
        LOG_D("Actual file path: %s", fd->path);
    }
    else
    {
        fd->path = fullpath;
    }

    /* specific file system open routine */
    if (fd->fops->open == NULL)
    {
        /* clear fd */
        rt_free(fd->path);
        fd->path = NULL;

        return -ENOSYS;
    }

    if ((result = fd->fops->open(fd)) < 0)
    {
        /* clear fd */
        rt_free(fd->path);
        fd->path = NULL;

        LOG_D("%s open failed", fullpath);

        return result;
    }

    fd->flags |= DFS_F_OPEN;
    if (flags & O_DIRECTORY)
    {
        fd->type = FT_DIRECTORY;
        fd->flags |= DFS_F_DIRECTORY;
    }

    LOG_D("open successful");
    return 0;
}

在dfs_file_open中調用fd->fops->open(見:result = fd->fops->open(fd))函數,足以說明DFS層的文件訪問接口函數最終是通過調用所掛載文件系統內部提供的文件訪問函數實現的。

從上面函數傳入的參數可以看出,跟我們在Linux上使用的文件訪問接口函數並不一致,我們更習慣把文件名作爲參數傳入,而不是把文件描述符作爲參數傳入。文件描述符信息較多,我們配置起來並不方便,爲了兼容LInux的文件訪問接口,DFS又提供了POSIX風格的接口函數,下面仍以文件打開函數爲例,看POSIX風格的文件訪問接口函數的實現過程:

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

/**
 * this function is a POSIX compliant version, which will open a file and
 * return a file descriptor according specified flags.
 *
 * @param file the path name of file.
 * @param flags the file open flags.
 *
 * @return the non-negative integer on successful open, others for failed.
 */
int open(const char *file, int flags, ...)
{
    int fd, result;
    struct dfs_fd *d;

    /* allocate a fd */
    fd = fd_new();
    if (fd < 0)
    {
        rt_set_errno(-ENOMEM);

        return -1;
    }
    d = fd_get(fd);

    result = dfs_file_open(d, file, flags);
    if (result < 0)
    {
        /* release the ref-count of fd */
        fd_put(d);
        fd_put(d);

        rt_set_errno(result);

        return -1;
    }

    /* release the ref-count of fd */
    fd_put(d);

    return fd;
}
RTM_EXPORT(open);

DFS POSIX風格的接口函數跟Linux一致,我們使用也更加方便,open函數實際上是對dfs_file_open函數的再封裝,並且隱藏了文件描述符的配置管理過程,使用起來比較方便。

上面open函數中調用的fd_new / fd_get / fd_put是文件描述符管理函數,提供對文件描述符結構體的配置管理功能,這幾個函數在.\components\dfs\src\dfs.c中實現,這裏就不再展示其代碼實現了。

DFS POSIX提供的文件訪問接口還有很多,下面只展示其接口函數聲明:

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

/**
 * this function is a POSIX compliant version, which will open a file and
 * return a file descriptor according specified flags.
 *
 * @param file the path name of file.
 * @param flags the file open flags.
 *
 * @return the non-negative integer on successful open, others for failed.
 */
int open(const char *file, int flags, ...);

/**
 * this function is a POSIX compliant version, which will close the open
 * file descriptor.
 *
 * @param fd the file descriptor.
 *
 * @return 0 on successful, -1 on failed.
 */
int close(int fd);

/**
 * this function is a POSIX compliant version, which will read specified data
 * buffer length for an open file descriptor.
 *
 * @param fd the file descriptor.
 * @param buf the buffer to save the read data.
 * @param len the maximal length of data buffer
 *
 * @return the actual read data buffer length. If the returned value is 0, it
 * may be reach the end of file, please check errno.
 */
int read(int fd, void *buf, size_t len);

/**
 * this function is a POSIX compliant version, which will write specified data
 * buffer length for an open file descriptor.
 *
 * @param fd the file descriptor
 * @param buf the data buffer to be written.
 * @param len the data buffer length.
 *
 * @return the actual written data buffer length.
 */
int write(int fd, const void *buf, size_t len);

/**
 * this function is a POSIX compliant version, which will seek the offset for
 * an open file descriptor.
 *
 * @param fd the file descriptor.
 * @param offset the offset to be seeked.
 * @param whence the directory of seek.
 *
 * @return the current read/write position in the file, or -1 on failed.
 */
off_t lseek(int fd, off_t offset, int whence);

/**
 * this function is a POSIX compliant version, which will rename old file name
 * to new file name.
 *
 * @param old the old file name.
 * @param new the new file name.
 *
 * @return 0 on successful, -1 on failed.
 *
 * note: the old and new file name must be belong to a same file system.
 */
int rename(const char *old, const char *new);

/**
 * this function is a POSIX compliant version, which will unlink (remove) a
 * specified path file from file system.
 *
 * @param pathname the specified path name to be unlinked.
 *
 * @return 0 on successful, -1 on failed.
 */
int unlink(const char *pathname);

/**
 * this function is a POSIX compliant version, which will get file information.
 *
 * @param file the file name
 * @param buf the data buffer to save stat description.
 *
 * @return 0 on successful, -1 on failed.
 */
int stat(const char *file, struct stat *buf);

/**
 * this function is a POSIX compliant version, which will get file status.
 *
 * @param fildes the file description
 * @param buf the data buffer to save stat description.
 *
 * @return 0 on successful, -1 on failed.
 */
int fstat(int fildes, struct stat *buf);

/**
 * this function is a POSIX compliant version, which shall request that all data
 * for the open file descriptor named by fildes is to be transferred to the storage
 * device associated with the file described by fildes.
 *
 * @param fildes the file description
 *
 * @return 0 on successful completion. Otherwise, -1 shall be returned and errno
 * set to indicate the error.
 */
int fsync(int fildes);

/**
 * this function is a POSIX compliant version, which shall perform a variety of
 * control functions on devices.
 *
 * @param fildes the file description
 * @param cmd the specified command
 * @param data represents the additional information that is needed by this
 * specific device to perform the requested function.
 *
 * @return 0 on successful completion. Otherwise, -1 shall be returned and errno
 * set to indicate the error.
 */
int fcntl(int fildes, int cmd, ...);

/**
 * this function is a POSIX compliant version, which shall perform a variety of
 * control functions on devices.
 *
 * @param fildes the file description
 * @param cmd the specified command
 * @param data represents the additional information that is needed by this
 * specific device to perform the requested function.
 *
 * @return 0 on successful completion. Otherwise, -1 shall be returned and errno
 * set to indicate the error.
 */
int ioctl(int fildes, int cmd, ...);


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

/**
 * This function can monitor the I/O device for events.
 *
 * @param nfds the maximum value of all file descriptors plus 1
 * @param readfds Set of read event file descriptors that need to be monitored
 * @param writefds Set of write event file descriptors that need to be monitored
 * @param exceptfds Set of exception event file descriptors that need to be monitored
 *
 * @return >0 A read/write event or error occurred in the monitored file collection. 
 * 		   =0 Waiting for timeout, no readable or writable or erroneous files
 * 		   <0 Error
 */
 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select函數比較特殊複雜些,可以阻塞的同時探測一組支持非阻塞的I / O設備是否有事件發生(比如可讀、可寫、出現異常或錯誤等),直至某一個設備觸發了事件或者超過了指定的等待時間。該函數的實現原理在《Socket API編程:基於Select的併發服務器》中有詳細介紹,在lwip中把套接字作文文件描述符對象進行管理的。

目錄也是一種特殊類型的文件,目錄跟文件還是有些不同,所以DFS POSIC針對目錄操作也提供了一組POSIX接口函數集合(實際調用的依然是dfs_file_ops函數集合),目錄的數據描述及接口函數聲明如下:

// rt-thread-4.0.1\components\dfs\include\dfs_posix.h
typedef struct
{
    int fd;     /* directory file */
    char buf[512];
    int num;
    int cur;
} DIR;


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

/**
 * this function is a POSIX compliant version, which will make a directory
 *
 * @param path the directory path to be made.
 * @param mode
 *
 * @return 0 on successful, others on failed.
 */
int mkdir(const char *path, mode_t mode);

/**
 * this function is a POSIX compliant version, which will remove a directory.
 *
 * @param pathname the path name to be removed.
 *
 * @return 0 on successful, others on failed.
 */
int rmdir(const char *pathname);

/**
 * this function is a POSIX compliant version, which will open a directory.
 *
 * @param name the path name to be open.
 *
 * @return the DIR pointer of directory, NULL on open directory failed.
 */
DIR *opendir(const char *name);

/**
 * this function is a POSIX compliant version, which will return a pointer
 * to a dirent structure representing the next directory entry in the
 * directory stream.
 *
 * @param d the directory stream pointer.
 *
 * @return the next directory entry, NULL on the end of directory or failed.
 */
struct dirent *readdir(DIR *d);

/**
 * this function is a POSIX compliant version, which will return current
 * location in directory stream.
 *
 * @param d the directory stream pointer.
 *
 * @return the current location in directory stream.
 */
long telldir(DIR *d);

/**
 * this function is a POSIX compliant version, which will set position of
 * next directory structure in the directory stream.
 *
 * @param d the directory stream.
 * @param offset the offset in directory stream.
 */
void seekdir(DIR *d, off_t offset);

/**
 * this function is a POSIX compliant version, which will reset directory
 * stream.
 *
 * @param d the directory stream.
 */
void rewinddir(DIR *d);

/**
 * this function is a POSIX compliant version, which will close a directory
 * stream.
 *
 * @param d the directory stream.
 *
 * @return 0 on successful, -1 on failed.
 */
int closedir(DIR *d);

/**
 * this function is a POSIX compliant version, which will change working
 * directory.
 *
 * @param path the path name to be changed to.
 *
 * @return 0 on successful, -1 on failed.
 */
int chdir(const char *path);

/**
 * this function is a POSIX compliant version, which will return current
 * working directory.
 *
 * @param buf the returned current directory.
 * @param size the buffer size.
 *
 * @return the returned current directory.
 */
char *getcwd(char *buf, size_t size);

上面這些DFS POSIX接口函數是在掛載文件系統後,用戶使用文件系統訪問文件或目錄實際調用的接口函數,使用比較頻繁。

2.2 DFS虛擬文件系統層

DFS虛擬文件系統層支持多種類型的文件系統,這裏直介紹跟I / O設備管理模型配合的DevFS文件系統和最常用的elmfat文件系統。

2.2.1 devfs設備文件系統

首先看devfs文件系統的初始化過程:

// rt-thread-4.0.1\components\dfs\filesystems\devfs\devfs.c

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

    return 0;
}

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,
};

static const struct dfs_file_ops _device_fops =
{
    dfs_device_fs_open,
    dfs_device_fs_close,
    dfs_device_fs_ioctl,
    dfs_device_fs_read,
    dfs_device_fs_write,
    RT_NULL,                    /* flush */
    RT_NULL,                    /* lseek */
    dfs_device_fs_getdents,
    dfs_device_fs_poll,
};

前面介紹DFS POSIX時,devfs_init函數被dfs_init調用,不過需要定義條件宏RT_USING_DFS_DEVFS,也即在menuconfig中配置devfs啓用後,devfs_init便會被自動調用,同時也會自動掛載devfs設備文件系統。

devfs主要用來管理I / O設備,向DFS POSIX註冊的文件操作集合_device_fops最終還是通過調用 I / O設備管理層接口函數實現的,相當於在I / O設備管理框架基礎上再封裝一層DFS POSIX設備文件系統層,相當於可以直接用DFS POSIX訪問文件的接口函數來訪問註冊到I / O設備管理框架的設備。下面以一個函數示例說明這種調用關係:

// rt-thread-4.0.1\components\dfs\filesystems\devfs\devfs.c

struct device_dirent
{
    rt_device_t *devices;
    rt_uint16_t read_index;
    rt_uint16_t device_count;
};

int dfs_device_fs_open(struct dfs_fd *file)
{
    rt_err_t result;
    rt_device_t device;

    /* open root directory */
    if ((file->path[0] == '/') && (file->path[1] == '\0') &&
        (file->flags & O_DIRECTORY))
    {
        struct rt_object *object;
        struct rt_list_node *node;
        struct rt_object_information *information;
        struct device_dirent *root_dirent;
        rt_uint32_t count = 0;

        /* lock scheduler */
        rt_enter_critical();

        /* traverse device object */
        information = rt_object_get_information(RT_Object_Class_Device);
        RT_ASSERT(information != RT_NULL);
        for (node = information->object_list.next; node != &(information->object_list); node = node->next)
        {
            count ++;
        }

        root_dirent = (struct device_dirent *)rt_malloc(sizeof(struct device_dirent) +
                      count * sizeof(rt_device_t));
        if (root_dirent != RT_NULL)
        {
            root_dirent->devices = (rt_device_t *)(root_dirent + 1);
            root_dirent->read_index = 0;
            root_dirent->device_count = count;
            count = 0;
            /* get all device node */
            for (node = information->object_list.next; node != &(information->object_list); node = node->next)
            {
                object = rt_list_entry(node, struct rt_object, list);
                root_dirent->devices[count] = (rt_device_t)object;
                count ++;
            }
        }
        rt_exit_critical();

        /* set data */
        file->data = root_dirent;

        return RT_EOK;
    }

    device = rt_device_find(&file->path[1]);
    if (device == RT_NULL)
        return -ENODEV;

#ifdef RT_USING_POSIX
    if (device->fops)
    {
        /* use device fops */
        file->fops = device->fops;
        file->data = (void *)device;

        /* use fops */
        if (file->fops->open)
        {
            result = file->fops->open(file);
            if (result == RT_EOK || result == -RT_ENOSYS)
            {
                return 0;
            }
        }
    }
    else
#endif
    {
        result = rt_device_open(device, RT_DEVICE_OFLAG_RDWR);
        if (result == RT_EOK || result == -RT_ENOSYS)
        {
            file->data = device;
            return RT_EOK;
        }
    }

    file->data = RT_NULL;
    /* open device failed. */
    return -EIO;
}

int dfs_device_fs_read(struct dfs_fd *file, void *buf, size_t count)
{
    int result;
    rt_device_t dev_id;

    RT_ASSERT(file != RT_NULL);

    /* get device handler */
    dev_id = (rt_device_t)file->data;
    RT_ASSERT(dev_id != RT_NULL);

    /* read device data */
    result = rt_device_read(dev_id, file->pos, buf, count);
    file->pos += result;

    return result;
}

爲了便於通過路徑文件名的方式訪問設備,增加了設備目錄描述結構體device_dirent,結構體device_dirent首成員指向設備對象rt_device的句柄,可以通過結構體device_dirent方便的獲取已註冊設備句柄,並通過設備句柄訪問該設備。

結構體device_dirent首成員devices實際上是一個指針數組的首地址,數組元素是設備句柄(指針),數組索引和總元素個數由device_dirent的另外兩個成員定義。

結構體device_dirent對象的首地址又被賦值給文件描述符的私有數據成員dfs_fd.data,這樣就可以通過文件描述符dfs_fd方便的訪問到相應的設備rt_device,實現DFS POSIX接口函數參數到 I / O 設備管理接口函數參數的轉換。如果查找設備,文件路徑名就包含了設備名,通過file->path[1]便可獲得設備名,作爲rt_device_find的傳入參數。

上面的函數dfs_device_fs_open實際調用的是device->fops->open或rt_device_open,其中後者rt_device_open是我們介紹 I / O設備模型框架時常用的接口形式,前者在介紹設備對象rt_device時介紹過一個成員dfs_file_ops只有在定義條件宏RT_USING_POSIX時有效,成員dfs_file_ops恰是爲了支持DFS設備文件系統而定義的。

2.2.2 elmfat虛擬文件系統

elmfat文件系統比devfs更復雜,我們先看下elmfat文件結構及文件依賴關係圖:
elmfat文件依賴關係圖
RT-Thread爲了適配DFS框架,在此基礎上新增了dfs_elm.c與dfs_elm.h兩個文件,主要對ff.h內elmfat文件系統提供的接口按照DFS中dfs_filesystem_ops與dfs_file_ops的格式進行再封裝,同時把封裝好的符合DFS要求的文件系統接口函數集合dfs_elm註冊到DFS框架中去。

elmfat文件系統初始化和註冊過程如下:

// rt-thread-4.0.1\components\dfs\filesystems\elmfat\dfs_elm.c

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

    return 0;
}
INIT_COMPONENT_EXPORT(elm_init);

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,
};

static const struct dfs_file_ops dfs_elm_fops =
{
    dfs_elm_open,
    dfs_elm_close,
    dfs_elm_ioctl,
    dfs_elm_read,
    dfs_elm_write,
    dfs_elm_flush,
    dfs_elm_lseek,
    dfs_elm_getdents,
    RT_NULL, /* poll interface */
};

elmfat初始化函數被自動初始化組件調用,不需要用戶再手動調用了。elmfat文件系統向上層DFS註冊的接口函數集合就不展開講了(如果展開介紹還需要再介紹ff.h與ff.c中elmfat文件系統的具體實現過程)。

要移植elmfat文件系統,還需要了解其對底層 I / O設備的操作,從elmfat文件結構可知,該部分接口在diskio.h中聲明,要移植elmfat文件系統,需要我們把diskio.h中聲明的函數實現出來。RT-Thread同樣幫我們在dfs_elm.c中實現了,移植函數的實現如下:

// rt-thread-4.0.1\components\dfs\filesystems\elmfat\dfs_elm.c

/* Read Sector(s) */
DRESULT disk_read(BYTE drv, BYTE *buff, DWORD sector, UINT count)
{
    rt_size_t result;
    rt_device_t device = disk[drv];

    result = rt_device_read(device, sector, buff, count);
    if (result == count)
    {
        return RES_OK;
    }

    return RES_ERROR;
}

/* Write Sector(s) */
DRESULT disk_write(BYTE drv, const BYTE *buff, DWORD sector, UINT count)
{
    rt_size_t result;
    rt_device_t device = disk[drv];

    result = rt_device_write(device, sector, buff, count);
    if (result == count)
    {
        return RES_OK;
    }

    return RES_ERROR;
}

/* Miscellaneous Functions */
DRESULT disk_ioctl(BYTE drv, BYTE ctrl, void *buff)
{
    rt_device_t device = disk[drv];

    if (device == RT_NULL)
        return RES_ERROR;

    if (ctrl == GET_SECTOR_COUNT)
    {
        struct rt_device_blk_geometry geometry;

        rt_memset(&geometry, 0, sizeof(geometry));
        rt_device_control(device, RT_DEVICE_CTRL_BLK_GETGEOME, &geometry);

        *(DWORD *)buff = geometry.sector_count;
        if (geometry.sector_count == 0)
            return RES_ERROR;
    }
    else if (ctrl == GET_SECTOR_SIZE)
    {
        struct rt_device_blk_geometry geometry;

        rt_memset(&geometry, 0, sizeof(geometry));
        rt_device_control(device, RT_DEVICE_CTRL_BLK_GETGEOME, &geometry);

        *(WORD *)buff = (WORD)(geometry.bytes_per_sector);
    }
    else if (ctrl == GET_BLOCK_SIZE) /* Get erase block size in unit of sectors (DWORD) */
    {
        struct rt_device_blk_geometry geometry;

        rt_memset(&geometry, 0, sizeof(geometry));
        rt_device_control(device, RT_DEVICE_CTRL_BLK_GETGEOME, &geometry);

        *(DWORD *)buff = geometry.block_size / geometry.bytes_per_sector;
    }
    else if (ctrl == CTRL_SYNC)
    {
        rt_device_control(device, RT_DEVICE_CTRL_BLK_SYNC, RT_NULL);
    }
    else if (ctrl == CTRL_TRIM)
    {
        rt_device_control(device, RT_DEVICE_CTRL_BLK_ERASE, buff);
    }

    return RES_OK;
}

從上面的代碼可以看出,elmfat文件系統對磁盤設備的操作函數最終是通過調用 I / O設備管理接口函數實現的。

elmfat只能掛載到塊設備上,比如SPI / QSPI Flash或SD Card等,前篇文章才介紹過SPI設備對象管理與SFUD管理,這裏比較方便的辦法是通過SFUD框架管理SPI / QSPI Flash,SFUD框架也會向 I / O設備管理層註冊統一的接口函數集合,下面我們在前一篇通過SFUD訪問W25Q128示例的基礎上使用DFS文件系統。

2.3 DFS設備抽象層

DFS設備抽象層主要用於驅動具體的 I / O塊設備,前一篇介紹的SFUD管理SPI / QSPI Flash的框架就屬於DFS設備抽象層,這裏就不再贅述了。

SFUD框架一般按物理設備的個數爲單位驅動的,也即一個SPI Flash(比如W25Q128)爲一個塊設備,如果我們想對一個Flash分爲多個邏輯設備或者多個分區,每個分區的作用不同或者掛載不同的文件系統,也即只想在一個Flash的一個分區上掛載某文件系統(比如elmfat),可以藉助FAL(Flash Abstraction Layer))Flash抽象層來實現該需求。

FAL可對多個物理Flash統一管理,也可能將某一物理Flash劃分爲多個邏輯分區,可以在每個分區上創建塊設備,用於上層文件系統的掛載,FAL框架圖如下:
FAL框架圖
FAL Flash抽象層並非必要,這裏限於篇幅留待後面再介紹,下面先基於SFUD框架展示DFS devfs與elmfat文件系統的使用示例。

三、DFS文件系統示例

這裏在示例工程通過SFUD訪問W25Q128示例的基礎上新增,爲了突出DFS文件系統組件,將stm32l475_device_sample複製一份並改名stm32l475_dfs_sample,DFS示例工程在stm32l475_dfs_sample的基礎上新增。

3.1 devfs文件系統示例

切換到工程目錄stm32l475_dfs_sample,打開env輸入menuconfig,啓用Device Virtual file system與devfs的配置界面如下:
啓用devfs配置界面
保存配置並退出,在projects\stm32l475_dfs_sample\applications目錄下新建文件dfs_sample.c(將原先的pin_sample.c與spi_sample.c移到新建文件夾\applications\old_sample中不參與編譯),打開文件dfs_sample.c編寫devfs示例代碼(我使用的VS Code代碼編譯器)。

本示例想同時包含目錄訪問與文件訪問,devfs主要用於訪問掛載到“/dev”目錄下的設備,所以本示例先讀取/dev目錄下所有的設備,再通過DFS POSIX接口訪問PIN設備中的蜂鳴器,實現配置蜂鳴器模式、讀寫蜂鳴器狀態的功能。按照實現目標編寫的代碼如下:

// projects\stm32l475_dfs_sample\applications\dfs_sample.c

#include "rtdevice.h"
#include "rtthread.h"
#include "board.h"
#include "dfs_posix.h"


#define BEEP_PIN    GET_PIN(B, 2)

static void devfs_sample(void)
{
    int fd;
    DIR *dirp;
    struct dirent *d;
    struct rt_device_pin_mode beepmode;
    struct rt_device_pin_status beepstatus, tempstatus;

    dirp = opendir("/dev");
    if(dirp == RT_NULL){
        rt_kprintf("open directory error!\n");
    }else{
        while ((d = readdir(dirp)) != RT_NULL){
            rt_kprintf("found device: %s\n", d->d_name);
        }
        closedir(dirp);
    }

    beepmode.pin = beepstatus.pin = tempstatus.pin = BEEP_PIN;
    beepmode.mode = PIN_MODE_OUTPUT;
    beepstatus.status = PIN_HIGH;

    rt_kprintf("Control beep via devfs.\n");
    
	/* 打開設備文件的標識RT_DEVICE_OFLAG_OPEN */
    fd = open("/dev/pin", RT_DEVICE_OFLAG_OPEN);
    if(fd >= 0){
        if(ioctl(fd, 0, &beepmode) == 0)
            rt_kprintf("Beep pin mode config finish.\n");

        if(write(fd, &beepstatus, sizeof(beepstatus)) == sizeof(beepstatus))
            rt_kprintf("Beep turn on.\n");

        if(read(fd, &tempstatus, sizeof(tempstatus)) == sizeof(tempstatus))
            rt_kprintf("Beep pin status: %d.\n", tempstatus.status);

        close(fd);
    }

    rt_thread_mdelay(3000);

    beepstatus.status = PIN_LOW;

    fd = open("/dev/pin", RT_DEVICE_OFLAG_OPEN);
    if(fd >= 0){
        if(write(fd, &beepstatus, sizeof(beepstatus)) == sizeof(beepstatus))
            rt_kprintf("Beep turn off.\n");

        if(read(fd, &tempstatus, sizeof(tempstatus)) == sizeof(tempstatus))
            rt_kprintf("Beep pin status: %d.\n", tempstatus.status);

        close(fd);
    }
}
MSH_CMD_EXPORT(devfs_sample, devfs sample);

在env環境中執行scons --target=mdk5生成MDK5工程代碼,打開project.uvprojx編譯報錯,將編譯器配置爲ARM Compiler V5,重新編譯無報錯。將程序燒錄到STM32L475潘多拉開發板上,運行結果如下:
devfs示例運行結果
從串口工具putty通過finsh組件交互結果看,運行devfs_sample示例程序運行正常,通過讀取目錄/dev下的設備文件和list_device命令獲取的設備列表一致,通過DFS POSIX接口也可以正常控制蜂鳴器PIN設備。

DFS除了提供POSIX接口函數外,還提供了FINSH部分命令,便於用戶通過finsh訪問設備文件和目錄。

本示例工程源碼下載地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_dfs_sample

3.2 elmfat文件系統示例

該工作目錄env環境輸入menuconfig命令,新增elmfat文件系統配置如下:
elmfat啓用配置
需要注意的是,elmfat文件系統默認的最大扇區大小maximum sector size是512,在前篇博客介紹W25Q128時談到W25Q128 Flash的扇區大小爲4096,本示例我們要將elmfat文件系統掛載到W25Q128 Flash上,所以需要將maximum sector size修改爲4096,然後保存配置退出。

本示例目的是在W25Q128 Flash上掛載elmfat文件系統,在掛載前需要先在W25Q128 Flash上創建elmfat格式的文件系統,掛在成功後通過DFS POSIX接口獲取該文件系統的統計信息。在掛載elmfat的路徑“/”下新建目錄“/user”,使用DFS POSIX接口打開、關閉文件,並往該文件中寫入、讀取數據。

前篇博客已經完成了使用SFUD框架管理W25Q128 Flash,並向 I / O設備管理層註冊了訪問W25Q128 Flash的接口函數集合,所以elmfat文件系統可以直接通過 I / O設備管理接口訪問到W25Q128。在文件dfs_sample.c中新增elmfat示例代碼如下:

// projects\stm32l475_dfs_sample\applications\dfs_sample.c

#include "drv_qspi.h"
#include "spi_flash_sfud.h"

#define QSPI_BUD_NAME       "qspi1"
#define QSPI_DEVICE_NAME    "qspi10"
#define W25Q_FLASH_NAME     "W25Q128"

#define QSPI_CS_PIN         GET_PIN(E, 11)

static int rt_hw_spi_flash_init(void)
{
    if(stm32_qspi_bus_attach_device(QSPI_BUD_NAME, QSPI_DEVICE_NAME, (rt_uint32_t)QSPI_CS_PIN, 1, RT_NULL, RT_NULL) != RT_EOK)
        return -RT_ERROR;

    if(rt_sfud_flash_probe(W25Q_FLASH_NAME, QSPI_DEVICE_NAME) == RT_NULL)
        return -RT_ERROR;

    return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_spi_flash_init);

static void elmfat_sample(void)
{
    int fd, size;
    struct statfs elm_stat;
    char str[] = "elmfat mount to W25Q flash.", buf[80];

    if(dfs_mkfs("elm", W25Q_FLASH_NAME) == 0)
        rt_kprintf("make elmfat filesystem success.\n");

    if(dfs_mount(W25Q_FLASH_NAME, "/", "elm", 0, 0) == 0)
        rt_kprintf("elmfat filesystem mount success.\n");

    if(statfs("/", &elm_stat) == 0)
        rt_kprintf("elmfat filesystem block size: %d, total blocks: %d, free blocks: %d.\n", 
                    elm_stat.f_bsize, elm_stat.f_blocks, elm_stat.f_bfree);

    if(mkdir("/user", 0x777) == 0)
        rt_kprintf("make a directory: '/user'.\n");

    rt_kprintf("Write string '%s' to /user/test.txt.\n", str);

    /* 以創建和讀寫模式打開文件,如果該文件不存在則創建該文件*/
    fd = open("/user/test.txt", O_WRONLY | O_CREAT);
    if (fd >= 0)
    {
        if(write(fd, str, sizeof(str)) == sizeof(str))
            rt_kprintf("Write data done.\n");

        close(fd);   
    }

    /* 以只讀模式打開文件 */
    fd = open("/user/test.txt", O_RDONLY);
    if (fd >= 0)
    {
        size = read(fd, buf, sizeof(buf));

        close(fd);

        if(size == sizeof(str))
            rt_kprintf("Read data from file test.txt(size: %d): %s \n", size, buf);
    }
}
MSH_CMD_EXPORT(elmfat_sample, elmfat sample);

在env環境中執行scons --target=mdk5生成MDK5工程代碼,打開project.uvprojx,將編譯器配置爲ARM Compiler V5,編譯無報錯。將程序燒錄到STM32L475潘多拉開發板上,運行結果如下:
elmfat示例運行結果
通過自動初始化組件配置了QSPI和SFUD,將elmfat文件系統掛載到W25Q128 Flash上的路徑"/"成功,並通過DFS POSIX接口訪問目錄和文件正常。

DFS提供的finsh命令跟Linux訪問目錄和文件的命令類似,部分命令的使用也在上圖給出了示範。

本示例工程源碼下載地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_dfs_sample

更多文章:

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