天天看點

鴻蒙核心源碼分析(VFS篇) | 檔案系統和諧共處的基礎 | 百篇部落格分析OpenHarmony源碼 | v68.02

子曰:“質勝文則野,文勝質則史。文質彬彬,然後君子。” 《論語》:雍也篇

鴻蒙核心源碼分析(VFS篇) | 檔案系統和諧共處的基礎 | 百篇部落格分析OpenHarmony源碼 | v68.02

百篇部落格系列篇.本篇為:

v68.xx 鴻蒙核心源碼分析(VFS篇) | 檔案系統和諧共處的基礎

檔案系統相關篇為:

  • v62.xx 鴻蒙核心源碼分析(檔案概念篇) | 為什麼說一切皆是檔案
  • v63.xx 鴻蒙核心源碼分析(檔案系統篇) | 用圖書管理說檔案系統
  • v64.xx 鴻蒙核心源碼分析(索引節點篇) | 誰是檔案系統最重要的概念
  • v65.xx 鴻蒙核心源碼分析(挂載目錄篇) | 為何檔案系統需要挂載
  • v66.xx 鴻蒙核心源碼分析(根檔案系統) | 先挂到

    /

    上的檔案系統
  • v67.xx 鴻蒙核心源碼分析(字元裝置篇) | 位元組為機關讀寫的裝置
  • v68.xx 鴻蒙核心源碼分析(VFS篇) | 檔案系統和諧共處的基礎
  • v69.xx 鴻蒙核心源碼分析(檔案句柄篇) | 你為什麼叫句柄 ?
  • v70.xx 鴻蒙核心源碼分析(管道檔案篇) | 如何降低資料流動成本

基本概念 | 官方定義

VFS

(Virtual File System)是檔案系統的虛拟層,它不是一個實際的檔案系統,而是一個異構檔案系統之上的軟體粘合層,為使用者提供統一的類Unix檔案操作接口。由于不同類型的檔案系統接口不統一,若系統中有多個檔案系統類型,通路不同的檔案系統就需要使用不同的非标準接口。而通過在系統中添加VFS層,提供統一的抽象接口,屏蔽了底層異構類型的檔案系統的差異,使得通路檔案系統的系統調用不用關心底層的存儲媒體和檔案系統類型,提高開發效率。

OpenHarmony

核心中,

VFS

架構是通過在記憶體中的樹結構來實作的,樹的每個結點都是一個

Vnode

結構體,父子結點的關系以

PathCache

結構體儲存。

VFS

最主要的兩個功能是:

  • 查找節點。
  • 統一調用(标準)。

VFS

層具體實作包括四個方面:

  • 通過三大函數指針操作接口,實作對不同檔案系統類型調用不同接口實作标準接口功能;
  • 通過

    Vnode

    PathCache

    機制,提升路徑搜尋以及檔案通路的性能;
  • 通過挂載點管理進行分區管理;
  • 通過FD管理進行程序間FD隔離等。

三大操作接口

VFS

層通過函數指針的形式,将統一調用按照不同的檔案系統類型,分發到不同檔案系統中進行底層操作。各檔案系統的各自實作一套Vnode操作(

VnodeOps

)、挂載點操作(

MountOps

)以及檔案操作接口(

file_operations_vfs

),并以函數指針結構體的形式存儲于對應

Vnode

、挂載點、

File

結構體中,實作

VFS

層對下通路。這三個接口分别為:

VnodeOps | 操作 Vnode 節點

struct VnodeOps {
    int (*Create)(struct Vnode *parent, const char *name, int mode, struct Vnode **vnode);//建立節點
    int (*Lookup)(struct Vnode *parent, const char *name, int len, struct Vnode **vnode);//查詢節點
    //Lookup向底層檔案系統查找擷取inode資訊
    int (*Open)(struct Vnode *vnode, int fd, int mode, int flags);//打開節點
    int (*Close)(struct Vnode *vnode);//關閉節點
    int (*Reclaim)(struct Vnode *vnode);//回收節點
    int (*Unlink)(struct Vnode *parent, struct Vnode *vnode, const char *fileName);//取消硬連結
    int (*Rmdir)(struct Vnode *parent, struct Vnode *vnode, const char *dirName);//删除目錄節點
    int (*Mkdir)(struct Vnode *parent, const char *dirName, mode_t mode, struct Vnode **vnode);//建立目錄節點
    /*
    建立一個目錄時,實際做了3件事:在其“父目錄檔案”中增加一個條目;配置設定一個inode;再配置設定一個存儲塊,
    用來儲存目前被建立目錄包含的檔案與子目錄。被建立的“目錄檔案”中自動生成兩個子目錄的條目,名稱分别是:“.”和“..”。
    前者與該目錄具有相同的inode号碼,是以是該目錄的一個“硬連結”。後者的inode号碼就是該目錄的父目錄的inode号碼。
    是以,任何一個目錄的"硬連結"總數,總是等于它的子目錄總數(含隐藏目錄)加2。即每個“子目錄檔案”中的“..”條目,
    加上它自身的“目錄檔案”中的“.”條目,再加上“父目錄檔案”中的對應該目錄的條目。
    */
    int (*Readdir)(struct Vnode *vnode, struct fs_dirent_s *dir);//讀目錄節點
    int (*Opendir)(struct Vnode *vnode, struct fs_dirent_s *dir);//打開目錄節點
    int (*Rewinddir)(struct Vnode *vnode, struct fs_dirent_s *dir);//定位目錄節點
    int (*Closedir)(struct Vnode *vnode, struct fs_dirent_s *dir);//關閉目錄節點
    int (*Getattr)(struct Vnode *vnode, struct stat *st);//擷取節點屬性
    int (*Setattr)(struct Vnode *vnode, struct stat *st);//設定節點屬性
    int (*Chattr)(struct Vnode *vnode, struct IATTR *attr);//改變節點屬性(change attr)
    int (*Rename)(struct Vnode *src, struct Vnode *dstParent, const char *srcName, const char *dstName);//重命名
    int (*Truncate)(struct Vnode *vnode, off_t len);//縮減或擴充大小
    int (*Truncate64)(struct Vnode *vnode, off64_t len);//縮減或擴充大小
    int (*Fscheck)(struct Vnode *vnode, struct fs_dirent_s *dir);//檢查功能
    int (*Link)(struct Vnode *src, struct Vnode *dstParent, struct Vnode **dst, const char *dstName);
    int (*Symlink)(struct Vnode *parentVnode, struct Vnode **newVnode, const char *path, const char *target);
    ssize_t (*Readlink)(struct Vnode *vnode, char *buffer, size_t bufLen);
};
           

MountOps | 挂載點操作

//挂載操作
struct MountOps {
    int (*Mount)(struct Mount *mount, struct Vnode *vnode, const void *data);//挂載
    int (*Unmount)(struct Mount *mount, struct Vnode **blkdriver);//解除安裝
    int (*Statfs)(struct Mount *mount, struct statfs *sbp);//統計檔案系統的資訊,如該檔案系統類型、總大小、可用大小等資訊
};
           

file_operations_vfs | 檔案操作接口

struct file_operations_vfs 
{
  int     (*open)(struct file *filep);	//打開檔案
  int     (*close)(struct file *filep);	//關閉檔案
  ssize_t (*read)(struct file *filep, char *buffer, size_t buflen);	//讀檔案
  ssize_t (*write)(struct file *filep, const char *buffer, size_t buflen);//寫檔案
  off_t   (*seek)(struct file *filep, off_t offset, int whence);//尋找,檢索 檔案
  int     (*ioctl)(struct file *filep, int cmd, unsigned long arg);//對檔案的控制指令
  int     (*mmap)(struct file* filep, struct VmMapRegion *region);//記憶體映射實作<檔案/裝置 - 線性區的映射>
  /* The two structures need not be common after this point */

#ifndef CONFIG_DISABLE_POLL
  int     (*poll)(struct file *filep, poll_table *fds);	//輪詢接口
#endif
  int     (*stat)(struct file *filep, struct stat* st);	//統計接口
  int     (*fallocate)(struct file* filep, int mode, off_t offset, off_t len);
  int     (*fallocate64)(struct file *filep, int mode, off64_t offset, off64_t len);
  int     (*fsync)(struct file *filep);
  ssize_t (*readpage)(struct file *filep, char *buffer, size_t buflen);
  int     (*unlink)(struct Vnode *vnode);
};
           

PathCache | 路徑緩存

PathCache是路徑緩存,它通過哈希表存儲,利用父節點Vnode的位址和子節點的檔案名,可以從PathCache中快速查找到子節點對應的Vnode。目前PageCache僅支援緩存二進制檔案,在初次通路檔案時通過mmap映射到記憶體中,下次再通路時,直接從PageCache中讀取,可以提升對同一個檔案的讀寫速度。另外基于PageCache可實作以檔案為基底的程序間通信。下圖展示了檔案/目錄的查找流程。

鴻蒙核心源碼分析(VFS篇) | 檔案系統和諧共處的基礎 | 百篇部落格分析OpenHarmony源碼 | v68.02
LIST_HEAD g_pathCacheHashEntrys[LOSCFG_MAX_PATH_CACHE_SIZE];	//路徑緩存哈希表項
struct PathCache {//路徑緩存
    struct Vnode *parentVnode;    /* vnode points to the cache */	
    struct Vnode *childVnode;     /* vnode the cache points to */
    LIST_ENTRY parentEntry;       /* list entry for cache list in the parent vnode */
    LIST_ENTRY childEntry;        /* list entry for cache list in the child vnode */
    LIST_ENTRY hashEntry;         /* list entry for buckets in the hash table */
    uint8_t nameLen;              /* length of path component */
#ifdef LOSCFG_DEBUG_VERSION
    int hit;                      /* cache hit count*/
#endif
    char name[0];                 /* path component name */
};
//路徑緩存初始化
int PathCacheInit(void)
{
    for (int i = 0; i < LOSCFG_MAX_PATH_CACHE_SIZE; i++) {
        LOS_ListInit(&g_pathCacheHashEntrys[i]);
    }
    return LOS_OK;
}
           

挂載點管理

目前OpenHarmony核心中,對系統中所有挂載點通過連結清單進行統一管理。挂載點結構體中,記錄了該挂載分區内的所有Vnode。當分區解除安裝時,會釋放分區内的所有Vnode。

static LIST_HEAD *g_mountList = NULL;//挂載連結清單,上面挂的是系統所有挂載點
struct Mount {
    LIST_ENTRY mountList;              /* mount list */			 //通過本節點将Mount挂到全局Mount連結清單上
    const struct MountOps *ops;        /* operations of mount */ //挂載操作函數	
    struct Vnode *vnodeBeCovered;      /* vnode we mounted on */ //要被挂載的節點 即 /bin1/vs/sd 對應的 vnode節點
    struct Vnode *vnodeCovered;        /* syncer vnode */		 //要挂載的節點	即/dev/mmcblk0p0 對應的 vnode節點
    struct Vnode *vnodeDev;            /* dev vnode */
    LIST_HEAD vnodeList;               /* list of vnodes */		//連結清單表頭
    int vnodeSize;                     /* size of vnode list */	//節點數量
    LIST_HEAD activeVnodeList;         /* list of active vnodes */	//激活的節點連結清單
    int activeVnodeSize;               /* szie of active vnodes list *///激活的節點數量
    void *data;                        /* private data */	//私有資料,可使用這個成員作為一個指向它們自己内部資料的指針
    uint32_t hashseed;                 /* Random seed for vfs hash */ //vfs 哈希随機種子
    unsigned long mountFlags;          /* Flags for mount */	//挂載标簽
    char pathName[PATH_MAX];           /* path name of mount point */	//挂載點路徑名稱  /bin1/vs/sd
    char devName[PATH_MAX];            /* path name of dev point */		//裝置名稱 /dev/mmcblk0p0
};
//配置設定一個挂載點
struct Mount* MountAlloc(struct Vnode* vnodeBeCovered, struct MountOps* fsop)
{
    struct Mount* mnt = (struct Mount*)zalloc(sizeof(struct Mount));//申請一個mount結構體記憶體,小記憶體配置設定用 zalloc
    if (mnt == NULL) {
        PRINT_ERR("MountAlloc failed no memory!\n");
        return NULL;
    }

    LOS_ListInit(&mnt->activeVnodeList);//初始化激活索引節點連結清單
    LOS_ListInit(&mnt->vnodeList);//初始化索引節點連結清單

    mnt->vnodeBeCovered = vnodeBeCovered;//裝置将裝載到vnodeBeCovered節點上
    vnodeBeCovered->newMount = mnt;//該節點不再是虛拟節點,而作為 裝置結點
#ifdef LOSCFG_DRIVERS_RANDOM	//随機值	驅動子產品
    HiRandomHwInit();//随機值初始化
    (VOID)HiRandomHwGetInteger(&mnt->hashseed);//用于生成哈希種子
    HiRandomHwDeinit();//随機值反初始化
#else
    mnt->hashseed = (uint32_t)random(); //随機生成哈子種子
#endif
    return mnt;
}
           

fd管理 | 兩種描述符/句柄的關系

Fd(File Descriptor)是描述一個打開的檔案/目錄的描述符。目前OpenHarmony核心中,fd總規格為896,分為三種類型:

  • 普通檔案描述符,系統總數量為512。
    #define CONFIG_NFILE_DESCRIPTORS    512	// 系統檔案描述符數量
               
  • Socket描述符,系統總規格為128。
    #define LWIP_CONFIG_NUM_SOCKETS         128	//socket連結數量
    #define CONFIG_NSOCKET_DESCRIPTORS  LWIP_CONFIG_NUM_SOCKETS 
               
  • 消息隊列描述符,系統總規格為256。
    #define CONFIG_NQUEUE_DESCRIPTORS    256
               

請記住,在OpenHarmony核心中,在不同的層面會有兩種檔案句柄::

  • 系統檔案描述符(

    sysfd

    ),由核心統一管理,和程序描述符形成映射關系,一個

    sysfd

    可以被多個

    profd

    映射,也就是說打開一個檔案隻會占用一個

    sysfd

    ,但可以占用多個

    profd

    ,即一個檔案被多個程序打開.
  • 程序檔案描述符(

    profd

    ),由程序管理的叫程序檔案描述符,核心對不同程序中的

    fd

    進行隔離,即程序隻能通路本程序的

    fd

    .舉例說明之間的關系:
    檔案            sysfd     profd
    吃個桃桃.mp4        10    13(A程序)
    吃個桃桃.mp4        10    3(B程序)
    容嬷嬷被冤枉.txt    12    3(A程序)
    容嬷嬷被冤枉.txt    12    3(C程序)
               
  • 不同程序的相同

    fd

    往往指向不同的檔案,但有三個

    fd

    例外
    • STDIN_FILENO(fd = 0)

      标準輸入 接收鍵盤的輸入
    • STDOUT_FILENO(fd = 1)

      标準輸出 向螢幕輸出
    • STDERR_FILENO(fd = 2)

      标準錯誤 向螢幕輸出

      sysfd

      和所有的

      profd

      的(0,1,2)号都是它們.熟知的

      printf

      就是向

      STDOUT_FILENO

      中寫入資料.
  • 具體涉及結構體
    struct file_table_s {//程序fd <--> 系統FD綁定
        intptr_t sysFd; /* system fd associate with the tg_filelist index */
    };//sysFd的預設值是-1
    struct fd_table_s {//程序fd表結構體
        unsigned int max_fds;//程序的檔案描述符最多有256個
        struct file_table_s *ft_fds; /* process fd array associate with system fd *///系統配置設定給程序的FD數組 ,fd 預設是 -1
        fd_set *proc_fds;	//程序fd管理位,用bitmap管理FD使用情況,預設打開了 0,1,2	       (stdin,stdout,stderr)
        fd_set *cloexec_fds;
        sem_t ft_sem; /* manage access to the file table */ //管理對檔案表的通路的信号量
    };
    struct files_struct {//程序檔案表結構體
        int count;				      //持有的檔案數量
        struct fd_table_s *fdt; //持有的檔案表
        unsigned int file_lock;	//檔案互斥鎖
        unsigned int next_fd;	  //下一個fd
    #ifdef VFS_USING_WORKDIR
        spinlock_t workdir_lock;	//工作區目錄自旋鎖
        char workdir[PATH_MAX];		//工作區路徑,最大 256個字元
    #endif
    };
    typedef struct ProcessCB {
    #ifdef LOSCFG_FS_VFS
        struct files_struct *files;        /**< Files held by the process */ //程序所持有的所有檔案,注者稱之為程序的檔案管理器
    #endif	//每個程序都有屬于自己的檔案管理器,記錄對檔案的操作. 注意:一個檔案可以被多個程序操作
    }
               
    解讀
    • 鴻蒙的每個程序

      ProcessCB

      都有屬于自己的程序的檔案描述符

      files_struct

      ,該程序和檔案系統有關的資訊都由它表達.
    • 搞清楚

      files_struct

      ,

      fd_table_s

      ,

      file_table_s

      三個結構體的關系就明白了進度描述符和系統描述符的關系.
    • fd_table_s

      是由

      alloc_fd_table

      配置設定的一個結構體數組,用于存放程序的檔案描述符
      //配置設定程序檔案表,初始化 fd_table_s 結構體中每個資料,包括系統FD(0,1,2)的綁定
      static struct fd_table_s * alloc_fd_table(unsigned int numbers)
      {
        struct fd_table_s *fdt;
        void *data;
        fdt = LOS_MemAlloc(m_aucSysMem0, sizeof(struct fd_table_s));//申請記憶體
        if (!fdt)
          {
            goto out;
          }
        fdt->max_fds = numbers;//最大數量
        if (!numbers)
          {
            fdt->ft_fds = NULL;
            fdt->proc_fds = NULL;
            return fdt;
          }
        data = LOS_MemAlloc(m_aucSysMem0, numbers * sizeof(struct file_table_s));//這是和系統描述符的綁定
        if (!data)
          {
            goto out_fdt;
          }
        fdt->ft_fds = data;//這其實是個 int[] 數組,
        for (int i = STDERR_FILENO + 1; i < numbers; i++)
          {
              fdt->ft_fds[i].sysFd = -1;//預設的系統描述符都為-1,即還沒有和任何系統檔案描述符綁定
          }
        data = LOS_MemAlloc(m_aucSysMem0, sizeof(fd_set));//管理FD的 bitmap 
        if (!data)
          {
            goto out_arr;
          }
        (VOID)memset_s(data, sizeof(fd_set), 0, sizeof(fd_set));
        fdt->proc_fds = data;
        alloc_std_fd(fdt);//配置設定标準的0,1,2系統檔案描述符,這樣做的結果是任務程序都可以寫系統檔案(0,1,2)
        (void)sem_init(&fdt->ft_sem, 0, 1);//互斥量初始化
        return fdt;
      out_arr:
        (VOID)LOS_MemFree(m_aucSysMem0, fdt->ft_fds);
      out_fdt:
        (VOID)LOS_MemFree(m_aucSysMem0, fdt);
      out:
        return NULL;
      }
                 
    • file_table_s

      記錄

      sysfd

      profd

      的綁定關系.

      fdt->ft_fds[i].sysFd

      中的

      i

      就是

      profd

百篇部落格分析.深挖核心地基

給鴻蒙核心源碼加注釋過程中,整理出以下文章。内容立足源碼,常以生活場景打比方盡可能多的将核心知識點置入某種場景,具有畫面感,容易了解記憶。說别人能聽得懂的話很重要! 百篇部落格絕不是百度教條式的在說一堆诘屈聱牙的概念,那沒什麼意思。更希望讓核心變得栩栩如生,倍感親切.确實有難度,自不量力,但已經出發,回頭已是不可能的了。 😛

與代碼有bug需不斷debug一樣,文章和注解内容會存在不少錯漏之處,請多包涵,但會反複修正,持續更新,

.xx

代表修改的次數,精雕細琢,言簡意赅,力求打造精品内容。

編譯建構 基礎工具 加載運作 程序管理

編譯環境篇

編譯過程篇

環境腳本篇

建構工具篇

gn應用篇

忍者ninja篇

雙向連結清單篇

位圖管理篇

用棧方式篇

定時器篇

原子操作篇

時間管理篇

ELF格式篇

ELF解析篇

靜态連結篇

重定位篇

程序映像篇

程序管理篇

程序概念篇

Fork篇

特殊程序篇

程序回收篇

信号生産篇

信号消費篇

Shell編輯篇

Shell解析篇

程序通訊 記憶體管理 前因後果 任務管理

自旋鎖篇

互斥鎖篇

程序通訊篇

信号量篇

事件控制篇

消息隊列篇

記憶體配置設定篇

記憶體管理篇

記憶體彙編篇

記憶體映射篇

記憶體規則篇

實體記憶體篇

總目錄

排程故事篇

記憶體主奴篇

源碼注釋篇

源碼結構篇

靜态站點篇

時鐘任務篇

任務排程篇

任務管理篇

排程隊列篇

排程機制篇

線程概念篇

并發并行篇

系統調用篇

任務切換篇

檔案系統 硬體架構

檔案概念篇

檔案系統篇

索引節點篇

挂載目錄篇

根檔案系統

字元裝置篇

VFS篇

檔案句柄篇

管道檔案篇

彙編基礎篇

彙編傳參篇

工作模式篇

寄存器篇

異常接管篇

彙編彙總篇

中斷切換篇

中斷概念篇

中斷管理篇

繼續閱讀