About 17 min

子曰:“君子博学于文,约之以礼,亦可以弗畔矣夫!” 《论语》:颜渊篇

在这里插入图片描述

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

v64.xx 鸿蒙内核源码分析(索引节点篇) | 谁是文件系统最重要的概念

下载 >> 离线文档.鸿蒙内核源码分析(百篇博客分析.挖透鸿蒙内核).pdf

文件系统相关篇为:

读懂鸿蒙内核的关键线索是LOS_DL_LIST(双向链表),它是系列篇开篇的内容. 而读懂文件系统的关键线索是vnode(索引节点),vnode在文件系统中起承上启下的关键点.vnodeBSD的叫法,鸿蒙沿用了BSD的称呼,linux的叫法是inode,关于vnode有翻译成虚拟节点,但系列篇还是统一翻译成索引节点.

什么是 vnode

先看大佬们对其的定义

OpenBSD 定义

A vnode is an object in kernel memory that speaks the UNIX file interface (open, read, write, close, readdir, etc.). Vnodes can represent files, directories, FIFOs, domain sockets, block devices, character devices.

vnode 是内核内存中的一个对象,它使用 UNIX 文件接口(打开、读取、写入、关闭、readdir 等)。 Vnodes 可以代表文件、目录、管道、套接字、块设备、字符设备。

freeBSD 定义

vnode -- internal representation of a file or directory . The vnode is the focus of all file activity in UNIX. A vnode is described by struct vnode. There is a unique vnode allocated for each active file, each current directory, each mounted-on file, text file, and the root.

vnode -- 文件或目录的内部表示. vnode 是 UNIX 中所有文件活动的焦点。 vnode 由 struct vnode 描述。 为每个活动文件、每个当前目录、每个挂载文件、文本文件和根分配了一个唯一的 vnode。

linux 定义

The inode (index node) is a data structure in a Unix-style file system that describes a file-system object such as a file or a directory. Each inode stores the attributes and disk block locations of the object's data.[1] File-system object attributes may include metadata (times of last change, access, modification), as well as owner and permission data.

inode(索引节点)是 Unix 风格的文件系统中的一种数据结构,用于描述文件系统对象,例如文件或目录。 每个 inode 存储对象数据的属性和磁盘块位置。 文件系统对象属性可能包括元数据(上次更改、访问、修改的时间),以及所有者和权限数据。

综上所述,发现木有,这说的可不就是 [v63.xx 鸿蒙内核源码分析(文件系统篇) | 用图书管理说文件系统 ] 中的索引页吗? 没读过的建议先阅读后再继续.对于在硬盘中的vnode,在系统启动后vnode会被加载到内存管理,但因内存问题并不会全部加载.

vnode 长啥样

Vnode 是具体文件或目录在VFS层的抽象封装,它屏蔽了不同文件系统的差异,实现资源的统一管理。Vnode通过哈希以及LRU机制进行管理。当系统启动后,对文件或目录的访问会优先从哈希链表中查找Vnode缓存,若缓存没有命中,则并从对应文件系统中搜索目标文件或目录,创建并缓存对应的Vnode。当Vnode缓存数量达到上限时,将淘汰长时间未访问的Vnode,其中挂载点Vnode与设备节点Vnode不参与淘汰。Vnode节点主要有以下几种类型:

  • 挂载点:挂载具体文件系统,如//storage
  • 设备节点:/dev目录下的节点,对应于一个设备,如/dev/mmcblk0
  • 文件/目录节点:对应于具体文件系统中的文件/目录,如/bin/init

节点创建流程如图

本篇主要围绕 vnode结构体来说,说透说烂这个文件系统最关键的节点.

struct IATTR { //此结构用于记录 vnode 的属性
  /* This structure is used for record vnode attr. */
  unsigned int attr_chg_valid;//节点改变有效性 (CHG_MODE | CHG_UID | ... )
  unsigned int attr_chg_flags;//额外的系统与用户标志(flag),用来保护该文件
  unsigned attr_chg_mode;	//确定了文件的类型,以及它的所有者、它的group、其它用户访问此文件的权限 (S_IWUSR | ...)
  unsigned attr_chg_uid;	//用户ID
  unsigned attr_chg_gid;	//组ID
  unsigned attr_chg_size;	//节点大小
  unsigned attr_chg_atime;	//节点最近访问时间
  unsigned attr_chg_mtime;	//节点对应的文件内容被修改时间
  unsigned attr_chg_ctime;	//节点自身被修改时间
};
// 对IATTR的修改最终将落到 vnode->vop->Chattr(vnode, attr);
enum VnodeType {//节点类型
    VNODE_TYPE_UNKNOWN,       /* unknown type */	//未知类型
    VNODE_TYPE_REG,           /* regular file */	//vnode代表一个正则文件(普通文件)
    VNODE_TYPE_DIR,           /* directory */		  //vnode代表一个目录
    VNODE_TYPE_BLK,           /* block device */	//vnode代表一个块设备
    VNODE_TYPE_CHR,           /* char device */		//vnode代表一个字符设备
    VNODE_TYPE_BCHR,          /* block char mix device *///块和字符设备混合
    VNODE_TYPE_FIFO,          /* pipe */			//vnode代表一个管道
    VNODE_TYPE_LNK,           /* link */			//vnode代表一个符号链接
};
struct Vnode {//vnode并不包含文件名,因为 vnode和文件名是 1:N 的关系
    enum VnodeType type;                /* vnode type */	//节点类型 (文件|目录|链接...)
    int useCount;                       /* ref count of users *///节点引用(链接)数,即有多少文件名指向这个vnode,即上层理解的硬链接数   
    uint32_t hash;                      /* vnode hash */	//节点哈希值
    uint uid;                           /* uid for dac */	//文件拥有者的User ID
    uint gid;                           /* gid for dac */	//文件的Group ID
    mode_t mode;                        /* mode for dac */	//chmod 文件的读、写、执行权限
    LIST_HEAD parentPathCaches;         /* pathCaches point to parents */	//指向父级路径缓存,上面的都是当了爸爸节点
    LIST_HEAD childPathCaches;          /* pathCaches point to children */	//指向子级路径缓存,上面都是当了别人儿子的节点
    struct Vnode *parent;               /* parent vnode */	//父节点
    struct VnodeOps *vop;               /* vnode operations */	//相当于指定操作Vnode方式 (接口实现|驱动程序)
    struct file_operations_vfs *fop;    /* file operations */	//相当于指定文件系统
    void *data;                         /* private data */		//文件数据block的位置,指向每种具体设备私有的成员,例如 ( drv_data | nfsnode | ....)
    uint32_t flag;                      /* vnode flag */		//节点标签
    LIST_ENTRY hashEntry;               /* list entry for bucket in hash table */ //通过它挂入哈希表 g_vnodeHashEntrys[i], i:[0,g_vnodeHashMask]
    LIST_ENTRY actFreeEntry;            /* vnode active/free list entry */	//通过本节点挂到空闲链表和使用链表上
    struct Mount *originMount;          /* fs info about this vnode */ //自己所在的文件系统挂载信息
    struct Mount *newMount;             /* fs info about who mount on this vnode */	//其他挂载在这个节点上文件系统信息
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

解读

  • VnodeType即七种文件类型,鸿蒙增加了一种 VNODE_TYPE_BCHR,去掉了 socket类型,没搞懂为什么.
  • useCount代表硬链接数,任何目录下都会有 .,..两个文件, 前者指向当前目录,后者指向父目录.这样做的好处是由索引页指向的数据块中(目录项)存有父目录和当前目录的索引号,有了索引号就能很快的找到对应的索引页.例如当外部使用 cd ../../../这样的命令时,只需在当前目录(inode)所指向的目录项中查找..的索引号.这样是非常的快捷和方便的,用自己勤劳的双手就能解决的困扰何必去麻烦别人呢.因为被下级留有记录所以硬链接数会增加.会增加多少呢? 举例说明, stat命令用于查看索引节点信息
    turing@ubuntu:/home/openharmony/code-v1.1.1-LTS/kernel/liteos_a$ stat kernel
    File: kernel
    Size: 4096      	Blocks: 8          IO Block: 4096   directory
    Device: 805h/2053d	Inode: 1099218     Links: 7
    Access: (0755/drwxr-xr-x)  Uid: ( 1000/  turing)   Gid: ( 1000/  turing)
    
    1
    2
    3
    4
    5
    注意Inode: 1099218,而Links: 7代表kernel被七个地方所关联,除了自己应该还有六个,在哪呢? 用 ll -a命令展开kernel看看
    turing@ubuntu:/home/openharmony/code-v1.1.1-LTS/kernel/liteos_a/kernel$ ll -a
    total 36
    drwxr-xr-x  7 turing turing 4096 Jun 21 02:38 ./
    drwxr-xr-x 21 turing turing 4096 Jul 23 19:45 ../
    drwxr-xr-x 11 turing turing 4096 Jun 21 02:38 base/
    -rwxr-xr-x  1 turing turing 2214 Jun 21 02:38 BUILD.gn*
    drwxr-xr-x  3 turing turing 4096 Jun 21 02:38 common/
    drwxr-xr-x  9 turing turing 4096 Jun 21 02:38 extended/
    drwxr-xr-x  2 turing turing 4096 Jun 21 02:38 include/
    -rwxr-xr-x  1 turing turing 2864 Jun 21 02:38 Kconfig*
    drwxr-xr-x  4 turing turing 4096 Jun 21 02:38 user/
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    发现包括.,..在内有七个目录 d代表的是目录,但是注意其中的 ../并不指向kernel而是指向它的父级liteos_a,其余的 ./,base/..,common/..六个刚好指向kernel,可以验证下它们的inode信息就知道了.
    turing@ubuntu:/home/openharmony/code-v1.1.1-LTS/kernel/liteos_a$ stat ./kernel/.
    File: ./kernel/.
    Size: 4096      	Blocks: 8          IO Block: 4096   directory
    Device: 805h/2053d	Inode: 1099218     Links: 7
    Access: (0755/drwxr-xr-x)  Uid: ( 1000/  turing)   Gid: ( 1000/  turing)
    turing@ubuntu:/home/openharmony/code-v1.1.1-LTS/kernel/liteos_a$ stat ./kernel/base/..
      File: ./kernel/base/..
      Size: 4096      	Blocks: 8          IO Block: 4096   directory
    Device: 805h/2053d	Inode: 1099218     Links: 7
    Access: (0755/drwxr-xr-x)  Uid: ( 1000/  turing)   Gid: ( 1000/  turing)
    turing@ubuntu:/home/openharmony/code-v1.1.1-LTS/kernel/liteos_a$ stat ./kernel/..
    File: ./kernel/..
    Size: 4096      	Blocks: 8          IO Block: 4096   directory
    Device: 805h/2053d	Inode: 1099213     Links: 21
    Access: (0755/drwxr-xr-x)  Uid: ( 1000/  turing)   Gid: ( 1000/  turing)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    会发现./kernel/../kernel/base/..Inode都是1099218,而./kernel/..的为1099213是不一样.
  • 正常情况下一个目录的.,..是不一样的,但只有一个目录例外,就是 /
    turing@ubuntu:/$ stat /.
    File: /.
    Size: 4096      	Blocks: 8          IO Block: 4096   directory
    Device: 805h/2053d	Inode: 2           Links: 20
    turing@ubuntu:/$ stat /..
    File: /..
    Size: 4096      	Blocks: 8          IO Block: 4096   directory
    Device: 805h/2053d	Inode: 2           Links: 20
    
    1
    2
    3
    4
    5
    6
    7
    8
    inode结果都是一样的,Inode: 2,inode号对应什么文件可以使用 "find / -inum NUM" 来查看.同时请思考两个问题.
    • 为什么/inode的编号一定是2 ? inode01的节点又去哪了呢?
    • inode编号真的是唯一的吗? 不同的文件系统可以有相同编号的inode吗? 如果可以有,那上层又是如何确保全局唯一的呢?
  • uid,gid``mode代表文件所属用户/用户组和权限.discretionary access control (DAC) 自主访问控制.在计算机安全中,自主访问控制 (DAC) 是一种由可信计算机系统评估标准定义的访问控制“作为一种根据对象所属的主体和组的身份限制对对象的访问的手段. 控制方式是自由的,因为具有特定访问权限的主体能够将该权限(可能是间接地)传递给任何其他主体(除非受到强制访问控制的约束)。与其对应的是 mandatory access control (MAC) 强制访问控制.
  • parentPathCaches``childPathCaches路径缓存链表,用户快速查找父子信息.
  • parent指向父节点,父节点不管是什么内容,一样都是文件,都用Vnode描述.
  • VnodeOps *vop这是对vnode的操作,vnode本身也是数据,存储在索引表中,记录了用户,用户组,权限,时间等信息,这部分信息是可以修改的,就需要接口来维护,便是VnodeOps.
    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);//创建目录节点
      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);//重命名
      ....
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    看到没有里面的所有方法都是对索引节点(索引页)的增删改查操作,并不操作索引节点指向的数据块(图书区)内容. 各个文件系统都要去实现这些接口.
    //文件系统(fat)实现对索引节点的操作
    struct VnodeOps fatfs_vops = {
        /* file ops */
        .Getattr = fatfs_stat,
        .Chattr = fatfs_chattr,
        .Lookup = fatfs_lookup,
        .Rename = fatfs_rename,
        .Create = fatfs_create,
        .Unlink = fatfs_unlink,
        .Reclaim = fatfs_reclaim,
        .Truncate = fatfs_truncate,
        .Truncate64 = fatfs_truncate64,
        /* dir ops */
        .Opendir = fatfs_opendir,
        .Readdir = fatfs_readdir,
        .Rewinddir = fatfs_rewinddir,
        .Closedir = fatfs_closedir,
        .Mkdir = fatfs_mkdir,
        .Rmdir = fatfs_rmdir,
        .Fscheck = fatfs_fscheck,
        .Symlink = fatfs_symlink,
        .Readlink = fatfs_readlink,
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
  • 那么对数据块(图书区)的修改用什么方法呢? 答案是:file_operations_vfs *fop,
    /* This structure is provided by devices when they are registered with the
    * system.  It is used to call back to perform device specific operations.
    */
    //该结构由设备在向系统注册时提供,它用于回调以执行特定于设备的操作。
    struct file_operations_vfs
    {
      /* The device driver open method differs from the mountpoint open method */
      int     (*open)(struct file *filep);
    
      /* The following methods must be identical in signature and position because
      * the struct file_operations and struct mountp_operations are treated like
      * unions.
      */
    
      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 */
      ....
    };
    //同样各个文件系统都要去实现这些接口.
    //文件系统(fat)实现对索引节点指向的数据块操作 
    struct file_operations_vfs fatfs_fops = {
        .open = fatfs_open,
        .read = fatfs_read,
        .write = fatfs_write,
        .seek = fatfs_lseek,
        .close = fatfs_close,
        .mmap = OsVfsFileMmap,
        .fallocate = fatfs_fallocate,
        .fallocate64 = fatfs_fallocate64,
        .fsync = fatfs_fsync,
        .ioctl = fatfs_ioctl,
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    file_operations_vfs看参数就知道,很是给vnode的上层的使用的,它是夹在应用层和vnode中间的一层,是 vnode起承上启下作用的上层,具体为什么要有file存在后续会详细说明,总之通过file找到vnode,从而对vnode指向的内容区进行修改.我们在应用层比如修改一个ppt,创建一个word文档这些操作就是通过file_operations_vfs. 一定要搞清楚VnodeOpsfile_operations_vfs二者的区别,一个是对索引页的操作,一个是对索引页指向的内容的操作.
  • data使用了一个void类型,这是私有格式数据,说明运行时才知道是什么类型,就像一个没有任何提示信息的私人密码箱一样,是打不开的,不知道顺序乱开只会毁掉数据,只有密码箱那边派人来了才能开,而这人就是各种不同的文件系统.每种文件系统如何读取数据的方式是不同的,差异化的就有接口内部来实现了.对外是相同的,无非都是读读写写.
  • hashEntry使用哈希算法来检索vnode
  • actFreeEntry:这个就不用介绍了,双向链表是内核最重要的结构体,通过它挂到全局空闲链表和使用链表上.
  • originMountnewMount是挂载相关的,任何文件系统都需要先挂载到根文件系统下才能使用.关于挂载后续有详细介绍.

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

  • 给鸿蒙内核源码加注释过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 😛
  • 与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。

按功能模块:

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

四大码仓中文注解 . 定期同步官方代码

WeHarmony/kernel_liteos_a_note

关注不迷路.代码即人生

鸿蒙内核源码分析

QQ群 790015635 | 入群密码 666

鸿蒙研究站( weharmonyos ) | 每天死磕一点点,原创不易,欢迎转载,请注明出处。若能支持点赞则更佳,感谢每一份支持。