1# 虚拟文件系统 2 3 4## 基本概念 5 6VFS(Virtual File System)是文件系统的虚拟层,它不是一个实际的文件系统,而是一个异构文件系统之上的软件粘合层,为用户提供统一的类Unix文件操作接口。由于不同类型的文件系统接口不统一,若系统中有多个文件系统类型,访问不同的文件系统就需要使用不同的非标准接口。而通过在系统中添加VFS层,提供统一的抽象接口,屏蔽了底层异构类型的文件系统的差异,使得访问文件系统的系统调用不用关心底层的存储介质和文件系统类型,提高开发效率。 7 8OpenHarmony内核中,VFS框架是通过在内存中的树结构来实现的,树的每个结点都是一个Vnode结构体,父子结点的关系以PathCache结构体保存。VFS最主要的两个功能是: 9 10- 查找节点。 11 12- 统一调用(标准)。 13 14 15## 运行机制 16 17当前,VFS层主要通过函数指针,实现对不同文件系统类型调用不同接口实现标准接口功能;通过Vnode与PathCache机制,提升路径搜索以及文件访问的性能;通过挂载点管理进行分区管理;通过FD管理进行进程间FD隔离等。下面将对这些机制进行简要说明。 18 191. 文件系统操作函数指针:VFS层通过函数指针的形式,将统一调用按照不同的文件系统类型,分发到不同文件系统中进行底层操作。各文件系统的各自实现一套Vnode操作、挂载点操作以及文件操作接口,并以函数指针结构体的形式存储于对应Vnode、挂载点、File结构体中,实现VFS层对下访问。 20 212. Vnode:Vnode是具体文件或目录在VFS层的抽象封装,它屏蔽了不同文件系统的差异,实现资源的统一管理。Vnode节点主要有以下几种类型: 22 - 挂载点:挂载具体文件系统,如/、/storage 23 - 设备节点:/dev目录下的节点,对应于一个设备,如/dev/mmcblk0 24 - 文件/目录节点:对应于具体文件系统中的文件/目录,如/bin/init 25 26 Vnode通过哈希以及LRU机制进行管理。当系统启动后,对文件或目录的访问会优先从哈希链表中查找Vnode缓存,若缓存没有命中,则并从对应文件系统中搜索目标文件或目录,创建并缓存对应的Vnode。当Vnode缓存数量达到上限时,将淘汰长时间未访问的Vnode,其中挂载点Vnode与设备节点Vnode不参与淘汰。当前系统中Vnode的规格默认为512,该规格可以通过LOSCFG_MAX_VNODE_SIZE进行配置。Vnode数量过大,会造成较大的内存占用;Vnode数量过少,则会造成搜索性能下降。下图展示了Vnode的创建流程。 27 28 **图1** Vnode创建流程 29 30 ![zh-cn_image_0000001127393126](figures/zh-cn_image_0000001127393126.png) 31 321. PathCache:PathCache是路径缓存,它通过哈希表存储,利用父节点Vnode的地址和子节点的文件名,可以从PathCache中快速查找到子节点对应的Vnode。下图展示了文件/目录的查找流程。 33 34 **图2** 文件查找流程 35 36 ![zh-cn_image_0000001175795145](figures/zh-cn_image_0000001175795145.png) 37 381. PageCache:PageCache是内核中文件的缓存。当前PageCache仅支持缓存二进制文件,在初次访问文件时通过mmap映射到内存中,下次再访问时,直接从PageCache中读取,可以提升对同一个文件的读写速度。另外基于PageCache可实现以文件为基底的进程间通信。 39 402. fd管理:Fd(File Descriptor)是描述一个打开的文件/目录的描述符。当前OpenHarmony内核中,fd总规格为896,分为三种类型: 41 - 普通文件描述符,系统总规格为512。 42 - Socket描述符,系统总规格为128。 43 - 消息队列描述符,系统总规格为256。 44 45 当前OpenHarmony内核中,对不同进程中的fd进行隔离,即进程只能访问本进程的fd,所有进程的fd映射到全局fd表中进行统一分配管理。进程的文件描述符最多有256个。 46 473. 挂载点管理:当前OpenHarmony内核中,对系统中所有挂载点通过链表进行统一管理。挂载点结构体中,记录了该挂载分区内的所有Vnode。当分区卸载时,会释放分区内的所有Vnode。 48 49 50## 开发指导 51 52 53### 接口说明 54 55当前文件系统支持的接口如下表所示,表格中的“×”代表对应文件系统不支持该接口。 56 57 **表1** 文件操作 58 59| 接口**名称** | 功能 | FAT | JFFS2 | NFS | TMPFS | PROCFS | 60| -------- | -------- | -------- | -------- | -------- | -------- | -------- | 61| open | 打开文件 | √ | √ | √ | √ | √ | 62| read/pread/readv/preadv | 读取文件 | √ | √ | √ | √ | √ | 63| write/pwrite/writev/pwritev | 写入文件 | √ | √ | √ | √ | √ | 64| lseek | 设置文件偏移 | √ | √ | √ | √ | × | 65| close | 关闭文件 | √ | √ | √ | √ | √ | 66| unlink | 删除文件 | √ | √ | √ | √ | × | 67| fstat | 查询文件信息 | √ | √ | √ | √ | √ | 68| fallocate | 预分配大小 | √ | × | × | × | × | 69| truncate | 文件截断 | √ | √ | × | √ | × | 70| link | 创建硬链接 | × | √ | × | × | × | 71| symlink | 创建软链接 | √ | √ | × | × | × | 72| readlink | 读取软链接 | √ | √ | × | × | × | 73| dup | 复制文件句柄 | √ | √ | √ | √ | √ | 74| fsync | 文件内容刷入设备 | √ | × | × | × | × | 75| ioctl | 设备控制 | × | × | × | √ | × | 76| fcntl | 文件控制操作 | √ | √ | √ | √ | √ | 77| mkdir | 创建目录 | √ | √ | √ | √ | × | 78| opendir | 打开目录 | √ | √ | √ | √ | √ | 79| readdir | 读取目录 | √ | √ | √ | √ | √ | 80| closedir | 关闭目录 | √ | √ | √ | √ | √ | 81| telldir | 获取目录偏移 | √ | √ | √ | √ | √ | 82| seekdir | 设置目录偏移 | √ | √ | √ | √ | √ | 83| rewinddir | 重置目录偏移 | √ | √ | √ | √ | × | 84| scandir | 读取目录数据 | √ | √ | √ | √ | √ | 85| rmdir | 删除目录 | √ | √ | √ | √ | × | 86| chdir | 切换当前路径 | √ | √ | √ | √ | √ | 87| getcwd | 获取当前路径 | √ | √ | √ | √ | √ | 88| realpath | 相对/绝对路径转换 | √ | √ | √ | √ | √ | 89| rename | 文件/目录重命名 | √ | √ | √ | √ | × | 90| chmod | 修改文件/目录属性 | √ | √ | × | × | × | 91| chown | 修改文件/目录所有者 | √ | √ | × | × | × | 92| stat/lstat | 查询文件/目录信息 | √ | √ | √ | √ | √ | 93| access | 查询文件/目录访问权限 | √ | √ | √ | √ | √ | 94| mount | 挂载分区 | √ | √ | √ | √ | √ | 95| umount | 卸载分区 | √ | √ | √ | √ | × | 96| statfs | 查询挂载分区信息 | √ | √ | √ | √ | √ | 97| format | 格式化分区 | √ | × | × | × | × | 98| sync | 分区内容刷入设备 | √ | × | × | × | × | 99 100 **表2** 目录操作 101 102| 接口**名称** | 功能 | FAT | JFFS2 | NFS | TMPFS | PROCFS | 103| -------- | -------- | -------- | -------- | -------- | -------- | -------- | 104| mkdir | 创建目录 | √ | √ | √ | √ | × | 105| opendir | 打开目录 | √ | √ | √ | √ | √ | 106| readdir | 读取目录 | √ | √ | √ | √ | √ | 107| closedir | 关闭目录 | √ | √ | √ | √ | √ | 108| telldir | 获取目录偏移 | √ | √ | √ | √ | √ | 109| seekdir | 设置目录偏移 | √ | √ | √ | √ | √ | 110| rewinddir | 重置目录偏移 | √ | √ | √ | √ | × | 111| scandir | 读取目录数据 | √ | √ | √ | √ | √ | 112| rmdir | 删除目录 | √ | √ | √ | √ | × | 113| chdir | 切换当前路径 | √ | √ | √ | √ | √ | 114| getcwd | 获取当前路径 | √ | √ | √ | √ | √ | 115| realpath | 相对/绝对路径转换 | √ | √ | √ | √ | √ | 116| rename | 文件/目录重命名 | √ | √ | √ | √ | × | 117| chmod | 修改文件/目录属性 | √ | √ | × | × | × | 118| chown | 修改文件/目录所有者 | √ | √ | × | × | × | 119| stat/lstat | 查询文件/目录信息 | √ | √ | √ | √ | √ | 120| access | 查询文件/目录访问权限 | √ | √ | √ | √ | √ | 121| mount | 挂载分区 | √ | √ | √ | √ | √ | 122| umount | 卸载分区 | √ | √ | √ | √ | × | 123| statfs | 查询挂载分区信息 | √ | √ | √ | √ | √ | 124| format | 格式化分区 | √ | × | × | × | × | 125| sync | 分区内容刷入设备 | √ | × | × | × | × | 126 127 **表3** 分区操作 128 129| 接口**名称** | 功能 | FAT | JFFS2 | NFS | TMPFS | PROCFS | 130| -------- | -------- | -------- | -------- | -------- | -------- | -------- | 131| mount | 挂载分区 | √ | √ | √ | √ | √ | 132| umount | 卸载分区 | √ | √ | √ | √ | × | 133| statfs | 查询挂载分区信息 | √ | √ | √ | √ | √ | 134| format | 格式化分区 | √ | × | × | × | × | 135| sync | 分区内容刷入设备 | √ | × | × | × | × | 136 137 138### 开发流程 139 140文件系统的主要开发流程包括挂载/卸载分区,以及系列目录/文件操作。 141 142 143### 编程实例 144 145代码实现如下: 146 147 148``` 149#include <stdio.h> 150#include <string.h> 151#include "sys/stat.h" 152#include "fcntl.h" 153#include "unistd.h" 154 155#define LOS_OK 0 156#define LOS_NOK -1 157 158int main(void) 159{ 160 int ret; 161 int fd = -1; 162 ssize_t len; 163 off_t off; 164 char mntName[20] = "/storage"; 165 char devName[20] = "/dev/mmcblk0p0"; 166 char dirName[20] = "/storage/test"; 167 char fileName[20] = "/storage/test/file.txt"; 168 char writeBuf[20] = "Hello OpenHarmony!"; 169 char readBuf[20] = {0}; 170 171 /* 创建目录“/storage” */ 172 ret = mkdir(mntName, 0777); 173 if (ret != LOS_OK) { 174 printf("mkdir failed.\n"); 175 return LOS_NOK; 176 } 177 178 /* 挂载设备“/dev/mmcblk0p0”到“/storage” */ 179 ret = mount(devName, mntName, "vfat", 0, 0); 180 if (ret != LOS_OK) { 181 printf("mount failed.\n"); 182 return LOS_NOK; 183 } 184 185 /* 创建目录“/storage/test” */ 186 ret = mkdir(dirName, 0777); 187 if (ret != LOS_OK) { 188 printf("mkdir failed.\n"); 189 return LOS_NOK; 190 } 191 192 /* 创建可读写文件“/storage/test/file.txt” */ 193 fd = open(fileName, O_RDWR | O_CREAT, 0777); 194 if (fd < 0) { 195 printf("open file failed.\n"); 196 return LOS_NOK; 197 } 198 199 /* 将writeBuf中的内容写入文件 */ 200 len = write(fd, writeBuf, strlen(writeBuf)); 201 if (len != strlen(writeBuf)) { 202 printf("write file failed.\n"); 203 return LOS_NOK; 204 } 205 206 /* 将文件内容刷入存储设备中 */ 207 ret = fsync(fd); 208 if (ret != LOS_OK) { 209 printf("fsync failed.\n"); 210 return LOS_NOK; 211 } 212 213 /* 将读写指针偏移至文件头 */ 214 off = lseek(fd, 0, SEEK_SET); 215 if (off != 0) { 216 printf("lseek failed.\n"); 217 return LOS_NOK; 218 } 219 220 /* 将文件内容读出至readBuf中,读取长度为readBuf大小 */ 221 len = read(fd, readBuf, sizeof(readBuf)); 222 if (len != strlen(readBuf)) { 223 printf("read file failed.\n"); 224 return LOS_NOK; 225 } 226 printf("%s\n", readBuf); 227 228 /* 关闭文件 */ 229 ret = close(fd); 230 if (ret != LOS_OK) { 231 printf("close failed.\n"); 232 return LOS_NOK; 233 } 234 235 /* 删除文件“/storage/test/file.txt” */ 236 ret = unlink(fileName); 237 if (ret != LOS_OK) { 238 printf("unlink failed.\n"); 239 return LOS_NOK; 240 } 241 242 /* 删除目录“/storage/test” */ 243 ret = rmdir(dirName); 244 if (ret != LOS_OK) { 245 printf("rmdir failed.\n"); 246 return LOS_NOK; 247 } 248 249 /* 卸载分区“/storage” */ 250 ret = umount(mntName); 251 if (ret != LOS_OK) { 252 printf("umount failed.\n"); 253 return LOS_NOK; 254 } 255 256 /* 删除目录“/storage” */ 257 ret = rmdir(mntName); 258 if (ret != LOS_OK) { 259 printf("rmdir failed.\n"); 260 return LOS_NOK; 261 } 262 263 return LOS_OK; 264} 265``` 266 267 268**结果验证** 269 270 271编译运行得到的结果为: 272 273 274 275``` 276Hello OpenHarmony! 277``` 278