天天看点

鸿蒙内核源码分析(挂载目录篇) | 为何文件系统需要挂载 | 百篇博客分析OpenHarmony源码 | v65.02

鸿蒙内核源码分析(挂载目录篇) | 为何文件系统需要挂载 | 百篇博客分析OpenHarmony源码 | v65.02

百篇博客系列篇.本篇为:

v65.xx 鸿蒙内核源码分析(挂载目录篇) | 为何文件系统需要挂载

文件系统相关篇为:

  • v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件
  • v63.xx 鸿蒙内核源码分析(文件系统篇) | 用图书管理说文件系统
  • v64.xx 鸿蒙内核源码分析(索引节点篇) | 谁是文件系统最重要的概念
  • v65.xx 鸿蒙内核源码分析(挂载目录篇) | 为何文件系统需要挂载
  • v66.xx 鸿蒙内核源码分析(根文件系统) | 先挂到

    /

    上的文件系统
  • v67.xx 鸿蒙内核源码分析(字符设备篇) | 字节为单位读写的设备
  • v68.xx 鸿蒙内核源码分析(VFS篇) | 文件系统和谐共处的基础
  • v69.xx 鸿蒙内核源码分析(文件句柄篇) | 你为什么叫句柄 ?
  • v70.xx 鸿蒙内核源码分析(管道文件篇) | 如何降低数据流动成本

关于文件系统的介绍已经写了三篇,但才刚刚开始,其中的 [文件系统篇] 一定要阅读,用生活中的场景去解释计算机各模块设计的原理和运行机制是整个系列篇最大的特点,计算机文件系统相关概念是非常的多的,若不还原其本质,不跳出这些概念去看问题是很难理解它为什么要弄这么些东东出来让你头大. 反之,如果搞明白了这些概念背后的真相你想忘记它们都很难,问题是经不起追问的, 多追问几个为什么就会离本源越来越近.

前几篇中追问了以下几个问题:

  • 对内核来说

    inode

    真的是唯一的吗? 答案是否定的,使用电脑的经验告诉我们,当把电脑硬盘拆下来挂到其他电脑上时,里面的数据一样能访问,并没有让你一切重来,而

    inode

    是存放硬盘上的,你没有办法让已编好序号的

    inode

    按你的逻辑重排,这不合理更不科学. 所以结论是

    inode

    的全局唯一性不是不想做,而是压根臣妾做不到啊.

    inode

    唯一性仅限于某个文件系统的内部.
  • 经验还告诉我们硬盘可以有多个分区,每个分区可以被格式化成不同的文件系统.(例如:C盘:

    NTF

    ,D盘

    FAT32

    ,E盘

    ext

    ),数据可以相互拷贝,毫无障碍.不同的文件系统是如何实现文件迁移到呢 ? 具体实现细节是怎样的 ?

如果想明白了这些问题, 就能逆向倒推为什么要有目录,为什么需要挂载使用, 为什么需要根文件系统.一切将是水到渠成 .

先说目录,从内核视角看目录可不能像普通老百姓从用户视角去看,目录是为了屏蔽文件系统之间的差异而设计出来的概念,也就说必须在

inode

的局部唯一性之上存在一个全局唯一性才能解决统一性问题.目录从更大尺度上去兼容并蓄各文件系统.

那它是如何解决的呢?

  • 首先各个文件系统记录了自己内部目录层级关系的,这个在 文件系统篇 | 目录项 中已经说过了. 这种关系是绝对的但也是相对的,绝对是对内,相对是对外. 例如:

    A文件系统内部如下:

    ├─古龙系列 inode id : 789
    │  ├─小李飞刀 inode id : 56
    │  ├─楚留香 inode id : 342
    │  └─陆小凤 inode id : 432
    └─金庸系列 inode id : 5567
        ├─倚天屠龙记 inode id : 89
        ├─射雕英雄传 inode id : 1212
        └─笑傲江湖 inode id : 567843
               
    B文件系统内部如下:
    ├─席绢系列  : inode id : 87 
    │  ├─上错花轿嫁对郎 : inode id : 89 
    │  ├─吻上你的心 : inode id : 789 
    │  └─红袖招 : inode id : 56 
    └─琼瑶系列 : inode id : 321 
        ├─在水一方 : inode id : 234 
        ├─梅花三弄 : inode id : 5678 
        ├─烟雨濛濛 : inode id : 987 
        └─还珠格格 : inode id : 23 
               
    其中

    789

    ,

    89

    两个文件系统中都用到了,但它们在内部是唯一的.在A文件系统中通过

    789

    就能找到

    56

    ,

    342

    ,

    432

    ,并且能得到相对路径: 古龙系列/小李飞刀,古龙系列/楚留香 .也就是说拿着

    inode

    只要进入了本文件系统地盘,那都不叫事,事都能给你办的妥妥的. 那如何才能进入而且不会搞错呢?

挂载目录

答案就是: 挂载目录,也叫挂载点,集体统一指挥的前提是需要先回归集体.如果已经有一颗目录树,将你们的目录树挂上来形成一颗更大的树不就统一了吗? 例如已有:

├─小说系列 inode id : 2
│  ├─武侠小说 inode id : 13
│  ├─言情小说 inode id : 14
           

其实它也是个文件系统,叫根文件系统, 它的

inode

也是独立的, 并且能得到相对路径

小说系列/武侠小说

,

小说系列/武侠小说

通过两个

mount

动作, 将它变成如下所示

├─小说系列 (根文件系统)
    ├─武侠小说 (根文件系统)
    │  ├─古龙系列 (A文件系统)
    │  │  ├─小李飞刀
    │  │  ├─楚留香
    │  │  └─陆小凤
    │  └─金庸系列 (A文件系统)
    │      ├─倚天屠龙记
    │      ├─射雕英雄传
    │      └─笑傲江湖
    └─言情小说 (根文件系统)
        ├─席绢系列 (B文件系统)
        │  ├─上错花轿嫁对郎
        │  ├─吻上你的心
        │  └─红袖招
        └─琼瑶系列 (B文件系统)
            ├─在水一方
            ├─梅花三弄
            ├─烟雨濛濛
            └─还珠格格
           

哦,原来整颗目录树是由这三个文件系统像搭积木一样拼接起来.而两个文件系统的衔接点,必然会产生一个新的概念出来, 这个概念就是 挂载点,也叫 挂载目录

Mount

可以猜测到的是挂载点的描述结构体中必有两个文件系统接驳点

inode

的信息,挂钩和脱钩的操作也只属于它专有.具体如下:

//挂载操作
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);//统计文件系统的信息,如该文件系统类型、总大小、可用大小等信息
};
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节点
    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
};
           

解读

  • mountList

    : 挂载点由双向链表全局统一管理
  • vnodeBeCovered

    :,记录挂到根文件系统的哪个节点上.
  • vnodeCovered

    : 设备也是一种文件,也被统一管理,统一在

    /dev

    目录下,内核会给设备的每个分区分配一个

    vnode

    节点,一个分区对应一个文件系统,设备文件后续有专门的介绍,此处不展开.
  • vnodeList

    : 指的是A/B文件系统的节点链表,由挂载点结构体记录.
  • activeVnodeList

    :

    A

    文件系统节点的使用情况,统一由双向链表管理.
  • activeVnodeSize

    :

    A

    文件系统已被使用的节点数
  • data

    这是文件系统的私有数据,跟 索引节点篇 | Vnode -> data 一样理解.
  • pathName

    :这个很重要,记录了

    小说系列/武侠小说

    ,因为文件的绝对路径是拼接起来的,以

    小说系列/武侠小说/古龙系列/小李飞刀

    这个完整的路径来说,它是由

    小说系列/武侠小说

    (根文件系统提供) +

    古龙系列/小李飞刀

    (A文件系统提供) 这两部分拼成的.
  • devName

    :一般名称类似于

    mmcblk0p0

    =

    mmc

    +

    block0

    +

    Partition0

    • mmc

      :

      MultiMediaCard

      可理解为硬盘
    • block0

      : 0号块设备
    • Partition

      :0号分区,一个分区上安装一个文件系统.
  • MountOps ops

    : 每个文件系统挂载方式是不用的,都需要实现这几个接口(挂载,卸载,统计).
    // 文件系统 proc 对 MountOps 接口实现
    const struct MountOps procfs_operations = {
        .Mount = VfsProcfsMount,//装载
        .Unmount = NULL,
        .Statfs = VfsProcfsStatfs,//统计信息
    };
    //文件系统 fat 对MountOps 接口实现
    struct MountOps fatfs_mops = {
        .Mount = fatfs_mount,
        .Unmount = fatfs_umount,
        .Statfs = fatfs_statfs,
    };
    //文件系统 jffs 对MountOps 接口实现
    const struct MountOps jffs_operations = {
      .Mount = VfsJffs2Bind,
      .Unmount = VfsJffs2Unbind,
      .Statfs = VfsJffs2Statfs,
    };
               

问题

上面提到 挂载就需要一个已经存在的文件系统提供目录,也就是根文件系统,但根文件系统又是怎么来的呢?

百篇博客分析.深挖内核地基

给鸿蒙内核源码加注释过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 😛

与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,

.xx

代表修改的次数,精雕细琢,言简意赅,力求打造精品内容。

编译构建 基础工具 加载运行 进程管理

编译环境篇

编译过程篇

环境脚本篇

构建工具篇

gn应用篇

忍者ninja篇

双向链表篇

位图管理篇

用栈方式篇

定时器篇

原子操作篇

时间管理篇

ELF格式篇

ELF解析篇

静态链接篇

重定位篇

进程映像篇

进程管理篇

进程概念篇

Fork篇

特殊进程篇

进程回收篇

信号生产篇

信号消费篇

Shell编辑篇

Shell解析篇

进程通讯 内存管理 前因后果 任务管理

自旋锁篇

互斥锁篇

进程通讯篇

信号量篇

事件控制篇

消息队列篇

内存分配篇

内存管理篇

内存汇编篇

内存映射篇

内存规则篇

物理内存篇

总目录

调度故事篇

内存主奴篇

源码注释篇

源码结构篇

静态站点篇

时钟任务篇

任务调度篇

任务管理篇

调度队列篇

调度机制篇

线程概念篇

并发并行篇

系统调用篇

任务切换篇

文件系统 硬件架构

文件概念篇

文件系统篇

索引节点篇

挂载目录篇

根文件系统

字符设备篇

VFS篇

文件句柄篇

管道文件篇

汇编基础篇

汇编传参篇

工作模式篇

寄存器篇

异常接管篇

汇编汇总篇

中断切换篇

中断概念篇

中断管理篇

继续阅读