鴻蒙輕核心原始碼分析:虛擬檔案系統 VFS

語言: CN / TW / HK

本文分享自華為雲社群《鴻蒙輕核心M核原始碼分析系列二一 01 虛擬檔案系統VFS》,作者:zhushy 。

VFS(Virtual File System)是檔案系統的虛擬層,它不是一個實際的檔案系統,而是一個異構檔案系統之上的軟體粘合層,為使用者提供統一的類 Unix 檔案操作介面。由於不同型別的檔案系統介面不統一,若系統中有多個檔案系統型別,訪問不同的檔案系統就需要使用不同的非標準介面。而通過在系統中新增 VFS 層,提供統一的抽象介面,遮蔽了底層異構型別的檔案系統的差異,使得訪問檔案系統的系統呼叫不用關心底層的儲存介質和檔案系統型別,提高開發效率。本文先介紹下 VFS 的結構體和全域性變數,然後詳細分析下 VFS 檔案操作介面。文中所涉及的原始碼,均可以在開源站點https://gitee.com/openharmony/kernel_liteos_m 獲取。

1、VFS 結構體定義

在檔案 components\fs\vfs\fs_operations.h 中定義了 VFS 虛擬檔案系統操作涉及的結構體。⑴處的 struct MountOps 結構體封裝了掛載相關的操作,包含掛載、解除安裝和檔案系統統計操作。⑵處的 struct FsMap 結構體對映檔案系統型別及其對應的掛載操作和檔案系統操作,支援的檔案型別包含“fat”和“littlefs”兩種,通過這個結構體可以獲取對應檔案型別的掛載操作及檔案系統操作介面。⑶處的 struct FileOps 封裝檔案系統的操作介面,包含檔案操作、目錄操作,統計等相應的介面。

⑴  struct MountOps {        int (*Mount)(const char *source, const char *target, const char *filesystemtype, unsigned long mountflags,            const void *data);        int (*Umount)(const char* target);        int (*Umount2)(const char* target, int flag);        int (*Statfs)(const char *path, struct statfs *buf);    };
⑵  struct FsMap {        const char *fileSystemtype;        const struct MountOps *fsMops;        const struct FileOps *fsFops;    };
⑶  struct FileOps {        int (*Open)(const char *path, int openFlag, ...);        int (*Close)(int fd);        int (*Unlink)(const char *fileName);        int (*Rmdir)(const char *dirName);        int (*Mkdir)(const char *dirName, mode_t mode);        struct dirent *(*Readdir)(DIR *dir);        DIR *(*Opendir)(const char *dirName);        int (*Closedir)(DIR *dir);        int (*Read)(int fd, void *buf, size_t len);        int (*Write)(int fd, const void *buf, size_t len);        off_t (*Seek)(int fd, off_t offset, int whence);        int (*Getattr)(const char *path, struct stat *buf);        int (*Rename)(const char *oldName, const char *newName);        int (*Fsync)(int fd);        int (*Fstat)(int fd, struct stat *buf);        int (*Stat)(const char *path, struct stat *buf);        int (*Ftruncate)(int fd, off_t length);    };

2、VFS 重要的內部全域性變數

在檔案 components\fs\vfs\los_fs.c 中有 2 個全域性變數比較重要,⑴處定義的陣列 g_fsmap 維護檔案系統型別對映資訊,陣列大小為 2,支援"fat"和"littlefs"檔案型別。⑵處的變數 g_fs 根據掛載的檔案型別指向陣列 g_fsmap 中的 FsMap 型別元素。⑶處的函式 InitMountInfo()會給陣列 g_fsmap 進行初始化賦值。第 0 個元素維護的"fat"檔案型別的檔案系統對映資訊,第 1 個元素維護的"littlefs"檔案型別的檔案系統對映資訊。涉及到的掛載操作、檔案系統操作變數 g_fatfsMnt、g_fatfsFops、g_lfsMnt、g_lfsFops 在對應的檔案系統檔案中定義。⑷處的函式 MountFindfs()用於根據檔案型別從陣列中獲取檔案對映資訊。

⑴  static struct FsMap g_fsmap[MAX_FILESYSTEM_LEN] = {0};⑵  static struct FsMap *g_fs = NULL;
⑶  static void InitMountInfo(void)    {    #if (LOSCFG_SUPPORT_FATFS == 1)        extern struct MountOps g_fatfsMnt;        extern struct FileOps g_fatfsFops;        g_fsmap[0].fileSystemtype = strdup("fat");        g_fsmap[0].fsMops = &g_fatfsMnt;        g_fsmap[0].fsFops = &g_fatfsFops;    #endif    #if (LOSCFG_SUPPORT_LITTLEFS == 1)        extern struct MountOps g_lfsMnt;        extern struct FileOps g_lfsFops;        g_fsmap[1].fileSystemtype = strdup("littlefs");        g_fsmap[1].fsMops = &g_lfsMnt;        g_fsmap[1].fsFops = &g_lfsFops;    #endif    }
⑷  static struct FsMap *MountFindfs(const char *fileSystemtype)    {        struct FsMap *m = NULL;
        for (int i = 0; i < MAX_FILESYSTEM_LEN; i++) {            m = &(g_fsmap[i]);            if (m->fileSystemtype && strcmp(fileSystemtype, m->fileSystemtype) == 0) {                return m;            }        }
        return NULL;    }

3、VFS 相關的操作介面

在之前的系列文章《鴻蒙輕核心 M 核原始碼分析系列十九 Musl LibC》中介紹了相關的介面,那些介面會呼叫 VFS 檔案系統中操作介面。對每個介面的用途用法不再描述,快速記錄下各個操作介面。

3.1 掛載解除安裝操作​

掛載解除安裝操作包含 LOS_FsMount、LOS_FsUmount、LOS_FsUmount2 等 3 個操作。⑴處在掛載檔案系統之前,需要初始化檔案系統對映資訊,只會操作一次。⑵處根據檔案系統型別獲取對應的檔案型別對映資訊。從這裡,可以獲知,LiteOS-M 核心只能同時支援一個檔案系統,不能只支援 fat 又支援 littlefs。⑶處對應對應的檔案系統掛載介面實現掛載操作。其他兩個函式同樣比較簡單,自行閱讀程式碼即可。

int LOS_FsMount(const char *source, const char *target,                    const char *filesystemtype, unsigned long mountflags,                    const void *data)    {        static int initFlag = 0;
⑴      if (initFlag == 0) {            InitMountInfo();            initFlag = 1;        }
⑵      g_fs = MountFindfs(filesystemtype);        if (g_fs == NULL) {            errno = ENODEV;            return FS_FAILURE;        }
        if (g_fs->fsMops == NULL || g_fs->fsMops->Mount == NULL) {            errno = ENOSYS;            return FS_FAILURE;        }
⑶      return g_fs->fsMops->Mount(source, target, filesystemtype, mountflags, data);    }
    int LOS_FsUmount(const char *target)    {        if (g_fs == NULL) {            errno = ENODEV;            return FS_FAILURE;        }        if (g_fs->fsMops == NULL || g_fs->fsMops->Umount == NULL) {            errno = ENOSYS;            return FS_FAILURE;        }        return g_fs->fsMops->Umount(target);    }
    int LOS_FsUmount2(const char *target, int flag)    {        if (g_fs == NULL) {            errno = ENODEV;            return FS_FAILURE;        }        if (g_fs->fsMops == NULL || g_fs->fsMops->Umount2 == NULL) {            errno = ENOSYS;            return FS_FAILURE;        }        return g_fs->fsMops->Umount2(target, flag);    }

3.2 檔案目錄操作​

VFS 封裝的檔案目錄操作介面包含 LOS_Open、LOS_Close、LOS_Read、LOS_Write、LOS_Opendir、LOS_Readdir、LOS_Closedir 等等。對具體的檔案型別的檔案目錄操作介面進行封裝,程式碼比較簡單,自行閱讀即可,部分程式碼片段如下。

......
int LOS_Unlink(const char *path){    if (g_fs == NULL) {        errno = ENODEV;        return FS_FAILURE;    }    if (g_fs->fsFops == NULL || g_fs->fsFops->Unlink == NULL) {        errno = ENOSYS;        return FS_FAILURE;    }    return g_fs->fsFops->Unlink(path);}
int LOS_Fstat(int fd, struct stat *buf){    if (g_fs == NULL) {        errno = ENODEV;        return FS_FAILURE;    }    if (g_fs->fsFops == NULL || g_fs->fsFops->Fstat == NULL) {        errno = ENOSYS;        return FS_FAILURE;    }    return g_fs->fsFops->Fstat(fd, buf);}
......
int LOS_Mkdir(const char *path, mode_t mode){    if (g_fs == NULL) {        errno = ENODEV;        return FS_FAILURE;    }    if (g_fs->fsFops == NULL || g_fs->fsFops->Mkdir == NULL) {        errno = ENOSYS;        return FS_FAILURE;    }    return g_fs->fsFops->Mkdir(path, mode);}
DIR *LOS_Opendir(const char *dirName){    if (g_fs == NULL) {        errno = ENODEV;        return NULL;    }    if (g_fs->fsFops == NULL || g_fs->fsFops->Opendir == NULL) {        errno = ENOSYS;        return NULL;    }    return g_fs->fsFops->Opendir(dirName);}......

3.3 隨機數檔案​

檔案/dev/random 可以用於產生隨機數。在開啟巨集 LOSCFG_RANDOM_DEV 時,LiteOS-M 支援隨機數檔案。從⑴處可知隨機數依賴檔案~/openharmony/base/security/huks/interfaces/innerkits/huks_lite/hks_client.h 和 hks_tmp_client.c,這些檔案用來產生隨機數。⑵處定義的 RANDOM_DEV_FD 和 RANDOM_DEV_PATH 分別是隨機數檔案的檔案描述符和隨機數檔案路徑。

#ifdef LOSCFG_RANDOM_DEV⑴  #include "hks_client.h"⑵  #define RANDOM_DEV_FD  CONFIG_NFILE_DESCRIPTORS + CONFIG_NSOCKET_DESCRIPTORS    #define RANDOM_DEV_PATH  "/dev/random"    #endif

3.3.1 隨機 LOS_Open 和 LOS_Close

該函式開啟一個檔案,獲取檔案描述符用於進一步操作。⑴處表示對於隨機數檔案,開啟的標籤選項只能支援指定的這些,否則會返回錯誤碼。⑵處獲取標準路徑,如果獲取失敗,返回錯誤碼。⑶處比較獲取的標準路徑是否為 RANDOM_DEV_PATH,在確認是隨機數路徑時,⑷處開始判斷。如果訪問模式為只讀,返回錯誤,如果開啟選項標籤是目錄,返回錯誤。如果不是上述錯誤情形,返回隨機數檔案描述符。⑸處如果獲取的標準路徑為“/”或“/dev”,則根據不同的選項,返回不同的錯誤碼。

int LOS_Open(const char *path, int oflag, ...){#ifdef LOSCFG_RANDOM_DEV    unsigned flags = O_RDONLY | O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_LARGEFILE | O_TRUNC | O_EXCL | O_DIRECTORY;⑴  if ((unsigned)oflag & ~flags) {        errno = EINVAL;        return FS_FAILURE;    }
    size_t pathLen = strlen(path) + 1;    char *canonicalPath = (char *)malloc(pathLen);    if (!canonicalPath) {        errno = ENOMEM;        return FS_FAILURE;    }⑵  if (GetCanonicalPath(NULL, path, canonicalPath, pathLen) == 0) {        FREE_AND_SET_NULL(canonicalPath);        errno = ENOMEM;        return FS_FAILURE;    }
⑶  if (strcmp(canonicalPath, RANDOM_DEV_PATH) == 0) {        FREE_AND_SET_NULL(canonicalPath);⑷      if ((O_ACCMODE & (unsigned)oflag) != O_RDONLY) {            errno = EPERM;            return FS_FAILURE;        }        if ((unsigned)oflag & O_DIRECTORY) {            errno = ENOTDIR;            return FS_FAILURE;        }        return RANDOM_DEV_FD;    }⑸  if (strcmp(canonicalPath, "/") == 0 || strcmp(canonicalPath, "/dev") == 0) {        FREE_AND_SET_NULL(canonicalPath);        if ((unsigned)oflag & O_DIRECTORY) {            errno = EPERM;            return FS_FAILURE;        }        errno = EISDIR;        return FS_FAILURE;    }    FREE_AND_SET_NULL(canonicalPath);#endif......}

對於隨機數檔案,關閉時,直接返回成功,不需要額外操作。程式碼片段如下:

int LOS_Close(int fd){#ifdef LOSCFG_RANDOM_DEV    if (fd == RANDOM_DEV_FD) {        return FS_SUCCESS;    }#endif......}

3.3.2 隨機 LOS_Read 和 LOS_Write

隨機數檔案讀寫使用 LOS_Read 和 LOS_Write 介面。讀取時,⑴處先對傳入引數進行校驗,如果讀取位元組數為 0,則返回 0;如果讀取的快取地址為空,返回-1;如果讀的位元組大於 1024,則使用 1024。⑵處呼叫 hks_generate_random()產生隨機數。由於隨機數檔案是隻讀的,如果嘗試寫入會返回-1 錯誤碼。

ssize_t LOS_Read(int fd, void *buf, size_t nbyte){#ifdef LOSCFG_RANDOM_DEV    if (fd == RANDOM_DEV_FD) {⑴      if (nbyte == 0) {            return FS_SUCCESS;        }        if (buf == NULL) {            errno = EINVAL;            return FS_FAILURE;        }        if (nbyte > 1024) { /* 1024, max random_size */            nbyte = 1024; /* hks_generate_random: random_size must <= 1024 */        }        struct hks_blob key = {HKS_BLOB_TYPE_RAW, (uint8_t *)buf, nbyte};⑵      if (hks_generate_random(&key) != 0) {            errno = EIO;            return FS_FAILURE;        }        return (ssize_t)nbyte;    }#endif......}
ssize_t LOS_Write(int fd, const void *buf, size_t nbyte){#ifdef LOSCFG_RANDOM_DEV    if (fd == RANDOM_DEV_FD) {        errno = EBADF; /* "/dev/random" is readonly */        return FS_FAILURE;    }#endif......}

小結

本文介紹了 VFS 的結構體和全域性變數,分析了下 VFS 檔案操作介面,對於隨機數檔案也進行了分析。時間倉促和能力關係,如有失誤,歡迎指正。感謝閱讀,如有任何問題、建議,都可以部落格下留言給我,謝謝。

參考資料

  • HarmonyOS Device>文件指南>基礎能力-虛擬檔案系統

點選關注,第一時間瞭解華為雲新鮮技術~