子曰:“君子博学于文,约之以礼,亦可以弗畔矣夫!” 《论语》:颜渊篇
百篇博客系列篇.本篇为:
v64.xx 鸿蒙内核源码分析(索引节点篇) | 谁是文件系统最重要的概念
文件系统相关篇为:
- v62.02 鸿蒙内核源码分析(文件概念) | 为什么说一切皆是文件
- v63.04 鸿蒙内核源码分析(文件系统) | 用图书管理说文件系统
- v64.06 鸿蒙内核源码分析(索引节点) | 谁是文件系统最重要的概念
- v65.05 鸿蒙内核源码分析(挂载目录) | 为何文件系统需要挂载
- v66.07 鸿蒙内核源码分析(根文件系统) | 先挂到
/
上的文件系统 - v67.03 鸿蒙内核源码分析(字符设备) | 字节为单位读写的设备
- v68.02 鸿蒙内核源码分析(VFS) | 文件系统和谐共处的基础
- v69.04 鸿蒙内核源码分析(文件句柄) | 你为什么叫句柄 ?
- v70.05 鸿蒙内核源码分析(管道文件) | 如何降低数据流动成本
读懂鸿蒙内核的关键线索是LOS_DL_LIST
(双向链表),它是系列篇开篇的内容.
而读懂文件系统的关键线索是vnode
(索引节点),vnode
在文件系统中起承上启下的关键点.vnode
是 BSD
的叫法,鸿蒙沿用了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 */ //其他挂载在这个节点上文件系统信息
};
解读
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)
注意
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/
发现包括
.
,..
在内有七个目录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)
./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
inode
结果都是一样的,Inode: 2
,inode
号对应什么文件可以使用 "find / -inum NUM" 来查看.同时请思考两个问题.- 为什么
/
的inode
的编号一定是2 ?inode
为0
和1
的节点又去哪了呢? 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);//重命名 .... }
看到没有里面的所有方法都是对索引节点(索引页)的增删改查操作,并不操作索引节点指向的数据块(图书区)内容.
各个文件系统都要去实现这些接口.//文件系统(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, };
那么对数据块(图书区)的修改用什么方法呢? 答案是:
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, };
file_operations_vfs
看参数就知道,很是给vnode
的上层的使用的,它是夹在应用层和vnode
中间的一层,是vnode
起承上启下作用的上层,具体为什么要有file
存在后续会详细说明,总之通过file
找到vnode
,从而对vnode
指向的内容区进行修改.我们在应用层比如修改一个ppt
,创建一个word
文档这些操作就是通过file_operations_vfs
.
一定要搞清楚VnodeOps
和file_operations_vfs
二者的区别,一个是对索引页的操作,一个是对索引页指向的内容的操作.data
使用了一个void
类型,这是私有格式数据,说明运行时才知道是什么类型,就像一个没有任何提示信息的私人密码箱一样,是打不开的,不知道顺序乱开只会毁掉数据,只有密码箱那边派人来了才能开,而这人就是各种不同的文件系统.每种文件系统如何读取数据的方式是不同的,差异化的就有接口内部来实现了.对外是相同的,无非都是读读写写.hashEntry
使用哈希算法来检索vnode
actFreeEntry
:这个就不用介绍了,双向链表是内核最重要的结构体,通过它挂到全局空闲链表和使用链表上.originMount
和newMount
是挂载相关的,任何文件系统都需要先挂载到根文件系统下才能使用.关于挂载后续有详细介绍.
百篇博客分析.深挖内核地基
- 给鸿蒙内核源码加注释过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。
- 与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。
按功能模块:
基础工具 | 加载运行 | 进程管理 | 编译构建 |
---|---|---|---|
双向链表 位图管理 用栈方式 定时器 原子操作 时间管理 |
ELF格式 ELF解析 静态链接 重定位 进程映像 |
进程管理 进程概念 Fork 特殊进程 进程回收 信号生产 信号消费 Shell编辑 Shell解析 |
编译环境 编译过程 环境脚本 构建工具 gn应用 忍者ninja |
进程通讯 | 内存管理 | 前因后果 | 任务管理 |
自旋锁 互斥锁 进程通讯 信号量 事件控制 消息队列 |
内存分配 内存管理 内存汇编 内存映射 内存规则 物理内存 |
总目录 调度故事 内存主奴 源码注释 源码结构 静态站点 |
时钟任务 任务调度 任务管理 调度队列 调度机制 线程概念 并发并行 CPU 系统调用 任务切换 |
文件系统 | 硬件架构 | ||
文件概念 文件系统 索引节点 挂载目录 根文件系统 字符设备 VFS 文件句柄 管道文件 |
汇编基础 汇编传参 工作模式 寄存器 异常接管 汇编汇总 中断切换 中断概念 中断管理 |
百万汉字注解.精读内核源码
四大码仓中文注解 . 定期同步官方代码
鸿蒙研究站( weharmonyos ) | 每天死磕一点点,原创不易,欢迎转载,请注明出处。若能支持点赞更好,感谢每一份支持。