鸿蒙轻内核M核源码分析系列二一 01 虚拟文件系统VFS

2022年01月15日 阅读数:0
这篇文章主要向大家介绍鸿蒙轻内核M核源码分析系列二一 01 虚拟文件系统VFS,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

鸿蒙轻内核M核源码分析系列二一 虚拟文件系统VFS

【本文正在参与优质创做者激励】git

VFS(Virtual File System)是文件系统的虚拟层,它不是一个实际的文件系统,而是一个异构文件系统之上的软件粘合层,为用户提供统一的类Unix文件操做接口。因为不一样类型的文件系统接口不统一,若系统中有多个文件系统类型,访问不一样的文件系统就须要使用不一样的非标准接口。而经过在系统中添加VFS层,提供统一的抽象接口,屏蔽了底层异构类型的文件系统的差别,使得访问文件系统的系统调用不用关心底层的存储介质和文件系统类型,提升开发效率。本文先介绍下VFS的结构体和全局变量,而后详细分析下VFS文件操做接口。文中所涉及的源码,都可以在开源站点https://gitee.com/openharmony/kernel_liteos_m 获取。数组


一、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);
    };

二、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()用于根据文件类型从数组中获取文件映射信息。markdown

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

三、VFS相关的操做接口

在以前的系列文章《鸿蒙轻内核M核源码分析系列十九 Musl LibC》中介绍了相关的接口,那些接口会调用VFS文件系统中操做接口。对每一个接口的用途用法再也不描述,快速记录下各个操做接口。dom

3.1 挂载卸载操做

挂载卸载操做包含LOS_FsMount、LOS_FsUmount、LOS_FsUmount2等3个操做。⑴处在挂载文件系统以前,须要初始化文件系统映射信息,只会操做一次。⑵处根据文件系统类型获取对应的文件类型映射信息。从这里,能够获知,LiteOS-M内核只能同时支持一个文件系统,不能只支持fat又支持littlefs。⑶处对应对应的文件系统挂载接口实现挂载操做。其余两个函数一样比较简单,自行阅读代码便可。ide

    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”,则根据不一样的选项,返回不一样的错误码。ui

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
......
}

对于随机数文件,关闭时,直接返回成功,不须要额外操做。代码片断以下:code

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文件操做接口,对于随机数文件也进行了分析。时间仓促和能力关系,若有失误,欢迎指正。感谢阅读,若有任何问题、建议,均可以博客下留言给我,谢谢。

参考资料

【本文正在参与优质创做者激励】

想了解更多关于鸿蒙的内容,请访问:

51CTO和华为官方合做共建的鸿蒙技术社区

https://harmonyos.51cto.com/#bkwz

::: hljs-center

21_9.jpg

:::