最簡單文件系統(tǒng)的升級,增加文件創(chuàng)建和讀寫功能

基于前文,我們繼續(xù)我們的文件系統(tǒng)的開發(fā)。在《基于Fuse的最簡單的文件系統(tǒng)》一文中我們實現(xiàn)了一個假文件系統(tǒng)。雖然呈現(xiàn)了目錄結(jié)構(gòu),但是不能創(chuàng)建新文件,也不能刪除文件,更加不能對文件進行讀寫。

接下來我們就增加更多的功能,使該文件系統(tǒng)具備創(chuàng)建文件和讀寫的功能。但是,我們本著循序漸進的原則,這里需要注意的是本文件系統(tǒng)還有諸多限制,具體如下:

  • 目前的文件系統(tǒng)還是基于內(nèi)存的實現(xiàn),數(shù)據(jù)并不會持久化到存儲介質(zhì)上
  • 只支持在根目錄創(chuàng)建和刪除文件
  • 文件的大小限制在1024字節(jié)以內(nèi)
  • 不支持在根目錄創(chuàng)建子目錄
  • 只支持一次性讀寫,不支持指定偏移讀寫
  • 最大可以創(chuàng)建256個文件

好吧,限制好多! 增加這么多限制的原因很簡單,就是為了讓降低大家的學(xué)習(xí)門檻。如果一下子實現(xiàn)一個支持所有功能的文件系統(tǒng),估計大家就沒有學(xué)習(xí)的興趣和動力了。所以我們每一個例子會增加一點點功能,逐漸實現(xiàn)一個功能完善的文件系統(tǒng)。至于本系列文章中涉及的技術(shù)細節(jié),大家可以參考本人拙著《文件系統(tǒng)技術(shù)內(nèi)幕》一書。

接下來我們回歸正題。雖然本地文件系統(tǒng)的基本原理是將線性的硬盤空間抽象為樹型層級結(jié)構(gòu),但是底層存儲并不一定要是線性的地址空間。為了實現(xiàn)簡單,本文我們會借助STL的容器實現(xiàn)文件系統(tǒng)的一些基本功能。當(dāng)然我們最終實現(xiàn)的文件系統(tǒng)肯定是基于硬盤的,目前的設(shè)計思路是為了降低學(xué)習(xí)的門檻和坡度,達到循序漸進的目的。

為了支持256個最大為1KB的內(nèi)存文件,我們首先需要分配256KB的內(nèi)存空間(用如下代碼中的data_space表示)。同時,為了管理這些空間,也就是記錄哪些空間已經(jīng)被使用,哪些空間還是可用狀態(tài),我們通過創(chuàng)建了一個位圖類型的變量(data_bitmap)。


image.png

另外,為了記錄根目錄中文件名稱與文件數(shù)據(jù)的對應(yīng)關(guān)系,我們創(chuàng)建了一個map類型的變量files。通過這個變量,我們可以根據(jù)文件名稱獲取文件的描述信息,比如文件數(shù)據(jù)的位置和大小等。這里文件的描述信息是通過一個名稱為inode的結(jié)構(gòu)體表示的,這也是模仿的Linux文件系統(tǒng)中的概念。

#define DATA_SPACE_LEN (256)
static map<string, inode*> files;
static vector<char*> data_space;
static bitset<DATA_SPACE_LEN> data_bitmap;

如下是結(jié)構(gòu)體inode的具體定義,這里一共包含4項內(nèi)容,都是最為基本的屬性描述。本文我們主要使用了data_index和size兩個成員變量,分別表示文件數(shù)據(jù)的位置和大小。

struct inode
{
    unsigned int inode_id;
    unsigned int mode;
    unsigned short data_index;
    unsigned short size;
};

如下代碼是初始化的代碼,本例中我們主要將內(nèi)存區(qū)域清零,并將位圖清零。這表示目前我們有一個干凈的,沒有任何文件的文件系統(tǒng)。后續(xù)我們在實現(xiàn)基于硬盤的文件系統(tǒng)時可以在這里實現(xiàn)硬盤格式化的工作。需要注意的是,在init函數(shù)中我們調(diào)用了syslog函數(shù),該函數(shù)用于向系統(tǒng)日志中記錄一條日志,方便我們了解程序的運行情況。

/* 初始化的時候被調(diào)用 */
void* MemoryFS::init(struct fuse_conn_info *conn, fuse_config*)
{
    syslog(LOG_NOTICE, "init\n");
    for (int i = 0; i < DATA_SPACE_LEN; i++) {
        char* block = new char[1024];
        memset(block, 0, 1024);
        data_space.push_back(block);
        data_bitmap.reset(i);
    }

    return 0;
}

然后回到本文的核心內(nèi)容,實現(xiàn)文件的創(chuàng)建和讀寫。文件的創(chuàng)建和讀寫一共涉及4個函數(shù),分別是create、open、write和read。其中create是創(chuàng)建文件時被調(diào)用,open是打開文件時被調(diào)用,write和read分別數(shù)據(jù)寫讀的時候被調(diào)用。接下來我們分別看看代碼實現(xiàn)。

首先要看的自然是創(chuàng)建文件的實現(xiàn),也就是create函數(shù)的實現(xiàn),具體代碼如下所示。本例實現(xiàn)原理是在創(chuàng)建一個文件的時候?qū)⒃撐募鎯υ谝粋€map數(shù)據(jù)結(jié)構(gòu)中,這樣后續(xù)讀寫文件的時候就可以找到該文件。在本例中,我們只是創(chuàng)建一個inode實例,并構(gòu)建文件系統(tǒng)與inode的映射關(guān)系。我們這里實現(xiàn)的非常簡單,相當(dāng)于在根目錄中增加了一個文件。

int MemoryFS::create(const char *name, mode_t mode, struct fuse_file_info *)
{
    syslog(LOG_NOTICE, "Create: %s\n", name);
    inode *i = new inode();
    memset(i, 0, sizeof(inode));
    files[name] = i;  // 存儲到map中
    return 0;
}

打開文件的函數(shù)實現(xiàn)也是非常簡單的,我們只需要查詢一下map,看看要打開的文件是否存在。如果不存在需要返回ENOENT。

int MemoryFS::open(const char *name, struct fuse_file_info *)
{
    int status = 0;
    auto inode = files.find(name);
    if (inode == files.end()) {
        status = -ENOENT;
    }

    syslog(LOG_NOTICE, "Open: %s %d\n", name, status);
    return 0;
}

接下來到正題了,也就是文件寫數(shù)據(jù)的功能。首先是寫數(shù)據(jù)的函數(shù)原型,可以看出與Linux的write函數(shù)類似。不同的地方是Linux API第一個參數(shù)是句柄,而本函數(shù)是文件名稱。當(dāng)我們在根目錄中向某個文件寫入數(shù)據(jù)的時候就會觸發(fā)給函數(shù)。該函數(shù)的核心功能是找到前面創(chuàng)建的文件,并且找到一個內(nèi)存空間用于存儲用戶要寫入的數(shù)據(jù)。找到具體的信息后,會將數(shù)據(jù)拷貝到內(nèi)存空間,并更新inode的記錄。

int MemoryFS::write(const char *name, const char *buf, size_t buf_size, off_t offset, struct fuse_file_info *)
{
    auto inode = files.find(name); // 查找要寫入數(shù)據(jù)的文件
    char* content = nullptr;
    short index = 0;
    int status = 0;

    if ( inode == files.end() ) {  // 如果沒有找到文件,返回相應(yīng)的錯誤碼
        status = -ENOENT;
        goto OUT;
    }

    if ( inode->second->size ) {  // 判斷文件是否已經(jīng)有數(shù)據(jù)
        index = inode->second->data_index;
    } else {
            for (int i = 0; i < DATA_SPACE_LEN; i++) {  // 如果沒有數(shù)據(jù),根據(jù)位圖查找一個可用的空間
                if (!data_bitmap[i]) {
                    index = i;
            data_bitmap.set(i);
                    break;
                }
           }
    }

    content = data_space[index]; // 獲取空間的地址
    memcpy(content, buf, buf_size); // 將數(shù)據(jù)寫入內(nèi)存空間
    inode->second->data_index = index; // 更新數(shù)據(jù)的位置
    inode->second->size = buf_size;       // 更新文件大小

OUT:
    syslog(LOG_NOTICE, "Write: %s %s %d\n", name, buf, buf_size);
    return buf_size;
}

讀數(shù)據(jù)的實現(xiàn)邏輯與寫數(shù)據(jù)類似,首先我們需要從map中查找到對應(yīng)的文件。如果該文件存在,則會返回文件對應(yīng)的inode實例。前文已述,inode中包含著文件數(shù)據(jù)的位置和大小等信息。根據(jù)inode中保存的信息,我們可以將數(shù)據(jù)拷貝到讀數(shù)據(jù)的緩沖區(qū),這樣在調(diào)用者就可以看到數(shù)據(jù)了。

int MemoryFS::read(const char *name, char *buf, size_t buf_size, off_t offset, struct fuse_file_info *)
{
    auto inode = files.find(name); // 查找文件的inode信息
    char* content = nullptr;
    short index = 0;
    short file_size = 0;
    int status = 0;

    if ( inode == files.end() ) {
        status = -ENOENT;
        goto OUT;
    }

    index = inode->second->data_index; // 根據(jù)inode信息確定文件數(shù)據(jù)的位置和大小
    file_size = inode->second->size; 
    content = data_space[index];
    memcpy(buf, content, file_size);  // 將數(shù)據(jù)拷貝到讀緩沖區(qū)

OUT:
    syslog(LOG_NOTICE, "Read: %s C %s B %s %d %d %d\n", name, content, buf, index, file_size, buf_size);
    return file_size;
}

雖然上述函數(shù)實現(xiàn)后就可以實現(xiàn)我們的目標(biāo)功能了。但是如果我們用ls命令查看文件,則需要實現(xiàn)目錄遍歷的功能,這部分功能我們在前面文章介紹過。首先是需要實現(xiàn)readdir功能,用于遍歷目錄項。本例中是遍歷map類型的變量。

int MemoryFS::readdir(const char *path, void *buf, fuse_fill_dir_t filler,
                           off_t, struct fuse_file_info *, enum fuse_readdir_flags)
{
    syslog(LOG_NOTICE, "readdir: %s\n", path);

    filler(buf, ".", NULL, 0, FUSE_FILL_DIR_PLUS);
    filler(buf, "..", NULL, 0, FUSE_FILL_DIR_PLUS);
    for (const auto& [key, value] : files) { // 實現(xiàn)目錄項的遍歷
        filler(buf, key.c_str() + 1, NULL, 0, FUSE_FILL_DIR_PLUS);
    }

    return 0;
}

然后是實現(xiàn)getattr,該函數(shù)用于獲取每個文件/目錄的詳細屬性。這里我們也是偷懶了,很多屬性是寫死的。這個函數(shù)的實現(xiàn)并不復(fù)雜,大家可以自行閱讀一下相關(guān)代碼。

int MemoryFS::getattr(const char *path, struct stat *stbuf, struct fuse_file_info *)
{
    int res = 0;

    memset(stbuf, 0, sizeof(struct stat));
    if (path == root_path) {
        stbuf->st_mode = S_IFDIR | 0755;
        stbuf->st_nlink = 2;
    } else if (files.find(path) != files.end()) {
        auto inode = files.find(path);
        stbuf->st_mode = S_IFREG | 0755; // 我們這里模式是寫死的,
        stbuf->st_nlink = 1;
        stbuf->st_size = inode->second->size;
        syslog(LOG_NOTICE, "getattr file: %s %d\n", path);
    } else {
        res = -ENOENT;
        syslog(LOG_NOTICE, "getattr error: %s\n", path);
    }

    syslog(LOG_NOTICE, "getattr: %s %d\n", path, res);
    return res;
}

至此,我們完成了所有函數(shù)的介紹。相關(guān)代碼已經(jīng)更新到作者的github空間,大家可以自行下載編譯實驗一下。接下來我們將進一步豐富功能,實現(xiàn)一個可以持久化的文件系統(tǒng)。

注: 本文配套的源代碼可以在github的SunnyZhang-IT/fs-from-zero庫中找到。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容