开源鸿蒙内核源码分析系列 | 挂载机制 | 谁根逐流不掉队

开源鸿蒙内核源码分析系列 | 挂载机制 | 谁根逐流不掉队

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

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

  • 对内核来说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

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一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。百篇博客系列思维导图结构如下:

根据上图的思维导图,我们未来将要和大家一一分享以上大部分关键技术点的博客文章。

百万汉字注解.精读内核源码

如果大家觉得看文章不过瘾,想直接撸代码的话,可以去下面四大码仓围观同步注释内核源码:

gitee仓

https://gitee.com/weharmony/kernel_liteos_a_note

github仓 :

https://github.com/kuangyufei/kernel_liteos_a_note

codechina仓

https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note

coding仓

https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files

写在最后

我们最近正带着大家玩嗨OpenHarmony。如果你有用OpenHarmony开发的好玩的东东,或者有对OpenHarmony的深度技术剖析,想通过我们平台让更多的小伙伴知道和分享的,欢迎投稿,让我们一起嗨起来!有点子,有想法,有Demo,立刻联系我们:

合作邮箱:zzliang@atomsource.org