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模塊化,便於移植多種類型的文件系統。
————————————————
版權聲明:本文爲CSDN博主「flydream0」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/flydream0/article/details/8838192

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