1# 文件系统 2 3## VFS 4 5 6### 基本概念 7 8**VFS(Virtual File System)** 是文件系统的虚拟层,它不是一个实际的文件系统,而是一个异构文件系统之上的软件粘合层,为用户提供统一的类Unix文件操作接口。由于不同类型的文件系统接口不统一,若系统中有多个文件系统类型,访问不同的文件系统就需要使用不同的非标准接口。而通过在系统中添加VFS层,提供统一的抽象接口,屏蔽了底层异构类型的文件系统的差异,使得访问文件系统的系统调用不用关心底层的存储介质和文件系统类型,提高开发效率。 9 10M核的文件系统子系统当前支持的文件系统有FATFS与LittleFS。通过VFS层提供了POSIX标准的操作,保持了接口的一致性,但是因为M核的资源非常紧张,VFS层非常轻薄,没有提供类似A核的高级功能(如pagecache等),主要是接口的标准化和适配工作,具体的事务由各个文件系统实际承载。M核文件系统支持的功能如下表所示: 11 12### 接口说明 13 14**表1** 文件操作 15 16| 接口名 | 描述 | FATFS | LITTLEFS | 17| -------- | -------- | -------- | -------- | 18| open | 打开文件 | 支持 | 支持 | 19| close | 关闭文件 | 支持 | 支持 | 20| read | 读取文件内容 | 支持 | 支持 | 21| write | 往文件写入内容 | 支持 | 支持 | 22| lseek | 设置文件偏移位置 | 支持 | 支持 | 23| stat | 通过文件路径名获取文件信息 | 支持 | 支持 | 24| unlink | 删除文件 | 支持 | 支持 | 25| rename | 重命名文件 | 支持 | 支持 | 26| fstat | 通过文件句柄获取文件信息 | 支持 | 支持 | 27| fsync | 文件内容刷入存储设备 | 支持 | 支持 | 28 29 30 **表2** 目录操作 31 32| 接口名 | 描述 | FATFS | LITTLEFS | 33| -------- | -------- | -------- | -------- | 34| mkdir | 创建目录 | 支持 | 支持 | 35| opendir | 打开目录 | 支持 | 支持 | 36| readdir | 读取目录项内容 | 支持 | 支持 | 37| closedir | 关闭目录 | 支持 | 支持 | 38| rmdir | 删除目录 | 支持 | 支持 | 39 40 41 **表3** 分区操作 42 43| 接口名 | 描述 | FATFS | LITTLEFS | 44| -------- | -------- | -------- | -------- | 45| mount | 分区挂载 | 支持 | 支持 | 46| umount | 分区卸载 | 支持 | 支持 | 47| umount2 | 分区卸载,可通过MNT_FORCE参数进行强制卸载 | 支持 | 不支持 | 48| statfs | 获取分区信息 | 支持 | 不支持 | 49 50ioctl,fcntl等接口由不同的lib库支持,与底层文件系统无关。 51 52## FAT 53 54 55### 基本概念 56 57FAT文件系统是File Allocation Table(文件配置表)的简称,主要包括DBR区、FAT区、DATA区三个区域。其中,FAT区各个表项记录存储设备中对应簇的信息,包括簇是否被使用、文件下一个簇的编号、是否文件结尾等。FAT文件系统有FAT12、FAT16、FAT32等多种格式,其中,12、16、32表示对应格式中FAT表项的比特数。FAT文件系统支持多种介质,特别在可移动存储介质(U盘、SD卡、移动硬盘等)上广泛使用,使嵌入式设备和Windows、Linux等桌面系统保持很好的兼容性,方便用户管理操作文件。 58 59OpenHarmony内核支持FAT12、FAT16与FAT32三种格式的FAT文件系统,具有代码量小、资源占用小、可裁切、支持多种物理介质等特性,并且与Windows、Linux等系统保持兼容,支持多设备、多分区识别等功能。OpenHarmony内核支持硬盘多分区,可以在主分区以及逻辑分区上创建FAT文件系统。 60 61 62### 开发指导 63 64 65#### 驱动适配 66 67FAT文件系统的使用需要底层MMC相关驱动的支持。在一个带MMC存储设备的板子上运行FATFS,需要: 68 691、适配板端EMMC驱动,实现disk_status、disk_initialize、disk_read、disk_write、disk_ioctl接口; 70 712、新增fs_config.h文件,配置FS_MAX_SS(存储设备最大sector大小)、FF_VOLUME_STRS(分区名)等信息,例如: 72 73 74``` 75#define FF_VOLUME_STRS "system", "inner", "update", "user" 76#define FS_MAX_SS 512 77#define FAT_MAX_OPEN_FILES 50 78``` 79 80#### 分区挂载 81 82移植FATFS到新硬件设备上,需要在初始化flash驱动后,完成设备分区。 83 84设备分区接口:int LOS_DiskPartition(const char *dev, const char *fsType, int *lengthArray, int *addrArray, int partNum); 85 86- dev:设备名称, 如“spinorblk0” 87- fsType:文件系统类型,”vfat“ 88- lengthArray:该设备上各分区的长度列表,fatfs填入百分比即可 89- addrArray:该设备上各分区的起始地址列表 90- partNum:分区的个数 91 92格式化接口:int LOS_PartitionFormat(const char *partName, char *fsType, void *data); 93 94- partName:分区名称,设备名称+ ‘p’ + 分区号,如“spinorblk0p0” 95- fsType:文件系统类型,”vfat“ 96- data:私有数据 传入(VOID *)formatType,(如FMT_FAT, FMT_FAT32) 97 98mount接口:int mount(const char *source, const char *target, const char *filesystemtype, unsigned long mountflags, const void *data); 99 100- source:分区名称,设备名称+ ‘p’ + 分区号,如“spinorblk0p0” 101- target:挂载路径 102- filesystemtype:文件系统类型,”vfat“ 103- mountflags:mount配置参数 104- data:私有数据,传入(VOID *)formatType,(如FMT_FAT, FMT_FAT32) 105 106本参考代码已在 ./device/qemu/arm_mps2_an386/liteos_m/board/fs/fs_init.c 中实现,M核qemu上可直接使用,请参考并根据实际硬件修改。 107 108 #include "fatfs_conf.h" 109 #include "fs_config.h" 110 #include "los_config.h" 111 #include "ram_virt_flash.h" 112 #include "los_fs.h" 113 114 struct fs_cfg { 115 CHAR *mount_point; 116 struct PartitionCfg partCfg; 117 }; 118 119 INT32 FatfsLowLevelInit() 120 { 121 INT32 ret; 122 INT32 i; 123 UINT32 addr; 124 int data = FMT_FAT32; 125 126 const char * const pathName[FF_VOLUMES] = {FF_VOLUME_STRS}; 127 HalLogicPartition *halPartitionsInfo = getPartitionInfo(); /* 获取长度和起始地址的函数,请根据实际单板适配 */ 128 INT32 lengthArray[FF_VOLUMES] = {25, 25, 25, 25}; 129 INT32 addrArray[FF_VOLUMES]; 130 131 /* 配置各分区的地址和长度,请根据实际单板适配 */ 132 for (i = 0; i < FF_VOLUMES; i++) { 133 addr = halPartitionsInfo[FLASH_PARTITION_DATA1].partitionStartAddr + i * 0x10000; 134 addrArray[i] = addr; 135 FlashInfoInit(i, addr); 136 } 137 138 /* 配置分区信息,请根据实际单板适配 */ 139 SetupDefaultVolToPartTable(); 140 141 ret = LOS_DiskPartition("spinorblk0", "vfat", lengthArray, addrArray, FF_VOLUMES); 142 printf("%s: DiskPartition %s\n", __func__, (ret == 0) ? "succeed" : "failed"); 143 if (ret != 0) { 144 return -1; 145 } 146 147 ret = LOS_PartitionFormat("spinorblk0p0", "vfat", &data); 148 printf("%s: PartitionFormat %s\n", __func__, (ret == 0) ? "succeed" : "failed"); 149 if (ret != 0) { 150 return -1; 151 } 152 153 ret = mount("spinorblk0p0", "/system", "vfat", 0, &data); 154 printf("%s: mount fs on '%s' %s\n", __func__, pathName[0], (ret == 0) ? "succeed" : "failed"); 155 if (ret != 0) { 156 return -1; 157 } 158 return 0; 159 } 160#### 开发流程 161 162> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** 163> - FATFS文件与目录操作: 164> - 单个文件大小不超过4G。 165> - 支持同时打开的文件数最大为FAT_MAX_OPEN_FILES,文件夹数最大为FAT_MAX_OPEN_DIRS。 166> - 暂不支持根目录管理,文件/目录名均以分区名开头,例如“user/testfile”就是在“user”分区下名为“testfile”的文件或目录。 167> - 若需要同时多次打开同一文件,必须全部使用只读方式(O_RDONLY)。以可写方式(O_RDWR、O_WRONLY等)只能打开一次。 168> - 读写指针未分离,例如以O_APPEND(追加写)方式打开文件后,读指针也在文件尾,从头读文件前需要用户手动置位。 169> - 暂不支持文件与目录的权限管理。 170> - stat及fstat接口暂不支持查询修改时间、创建时间和最后访问时间。微软FAT协议不支持1980年以前的时间。 171> - FATFS分区挂载与卸载: 172> - 支持以只读属性挂载分区。当mount函数的入参为MS_RDONLY时,所有的带有写入的接口,如write、mkdir、unlink,以及非O_RDONLY属性的open,将均被拒绝。 173> - mount支持通过MS_REMOUNT标记修改已挂载分区的权限。 174> - 在umount操作前,需确保所有目录及文件全部关闭。 175> - umount2支持通过MNT_FORCE参数强制关闭所有文件与文件夹并umount,但可能造成数据丢失,请谨慎使用。 176> - FATFS支持重新划分存储设备分区、格式化分区,对应接口为fatfs_fdisk与fatfs_format: 177> - 在fatfs_format操作之前,若需要格式化的分区已挂载,需确保分区中的所有目录及文件全部关闭,并且分区umount。 178> - 在fatfs_fdisk操作前,需要该设备中的所有分区均已umount。 179> - fatfs_fdisk与fatfs_format会造成设备数据丢失,请谨慎使用。 180 181 182### 编程实例 183 184 185#### 实例描述 186 187本实例实现以下功能: 188 1891. 创建目录“system/test” 190 1912. 在“system/test”目录下创建文件“file.txt” 192 1933. 在文件起始位置写入“Hello OpenHarmony!” 194 1954. 将文件内容刷入设备中 196 1975. 设置偏移到文件起始位置 198 1996. 读取文件内容 200 2017. 关闭文件 202 2038. 删除文件 204 2059. 删除目录 206 207 208#### 示例代码 209 210 **前提条件:** 211 212 系统已将设备分区挂载到目录,qemu默认已挂载system。 213 214 在kernel/liteos_m目录下执行 make menuconfig 命令配置"FileSystem->Enable FS VFS"开启FS功能; 215 216 开启FS后出现新选项“Enable FAT”开启FAT。 217 218 **代码实现如下:** 219 220本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleFatfs。 221 222 ``` 223#include <stdio.h> 224#include <string.h> 225#include "sys/stat.h" 226#include "fcntl.h" 227#include "unistd.h" 228 229#define BUF_SIZE 20 230#define TEST_ROOT "system" /* 测试的根目录请根据实际情况调整 */ 231VOID ExampleFatfs(VOID) 232{ 233 int ret; 234 int fd; 235 ssize_t len; 236 off_t off; 237 char dirName[BUF_SIZE] = TEST_ROOT"/test"; 238 char fileName[BUF_SIZE] = TEST_ROOT"/test/file.txt"; 239 char writeBuf[BUF_SIZE] = "Hello OpenHarmony!"; 240 char readBuf[BUF_SIZE] = {0}; 241 242 /* 创建测试目录 */ 243 ret = mkdir(dirName, 0777); 244 if (ret != LOS_OK) { 245 printf("mkdir failed.\n"); 246 return; 247 } 248 249 /* 创建可读写测试文件 */ 250 fd = open(fileName, O_RDWR | O_CREAT, 0777); 251 if (fd < 0) { 252 printf("open file failed.\n"); 253 return; 254 } 255 256 /* 将writeBuf中的内容写入文件 */ 257 len = write(fd, writeBuf, strlen(writeBuf)); 258 if (len != strlen(writeBuf)) { 259 printf("write file failed.\n"); 260 return; 261 } 262 263 /* 将文件内容刷入存储设备中 */ 264 ret = fsync(fd); 265 if (ret != LOS_OK) { 266 printf("fsync failed.\n"); 267 return; 268 } 269 270 /* 将读写指针偏移至文件头 */ 271 off = lseek(fd, 0, SEEK_SET); 272 if (off != 0) { 273 printf("lseek failed.\n"); 274 return; 275 } 276 277 /* 将文件内容读出至readBuf中,读取长度为readBuf大小 */ 278 len = read(fd, readBuf, sizeof(readBuf)); 279 if (len != strlen(writeBuf)) { 280 printf("read file failed.\n"); 281 return; 282 } 283 printf("%s\n", readBuf); 284 285 /* 关闭测试文件 */ 286 ret = close(fd); 287 if (ret != LOS_OK) { 288 printf("close failed.\n"); 289 return; 290 } 291 292 /* 删除测试文件 */ 293 ret = unlink(fileName); 294 if (ret != LOS_OK) { 295 printf("unlink failed.\n"); 296 return; 297 } 298 299 /* 删除测试目录 */ 300 ret = rmdir(dirName); 301 if (ret != LOS_OK) { 302 printf("rmdir failed.\n"); 303 return; 304 } 305 306 return; 307} 308 ``` 309 310 311#### 结果验证 312 313编译运行得到的结果为: 314 315 316``` 317Hello OpenHarmony! 318``` 319## LittleFS 320 321 322### 基本概念 323 324LittleFS是一个小型的Flash文件系统,它结合日志结构(log-structured)文件系统和COW(copy-on-write)文件系统的思想,以日志结构存储元数据,以COW结构存储数据。这种特殊的存储方式,使LittleFS具有强大的掉电恢复能力(power-loss resilience)。分配COW数据块时LittleFS采用了名为统计损耗均衡的动态损耗均衡算法,使Flash设备的寿命得到有效保障。同时LittleFS针对资源紧缺的小型设备进行设计,具有极其有限的ROM和RAM占用,并且所有RAM的使用都通过一个可配置的固定大小缓冲区进行分配,不会随文件系统的扩大占据更多的系统资源。 325 326当在一个资源非常紧缺的小型设备上,寻找一个具有掉电恢复能力并支持损耗均衡的Flash文件系统时,LittleFS是一个比较好的选择。 327 328 329### 开发指导 330 331移植LittleFS到新硬件设备上,需要在初始化flash驱动后,完成设备分区。 332 333设备分区接口:int LOS_DiskPartition(const char *dev, const char *fsType, int *lengthArray, int *addrArray, int partNum); 334 335- dev:设备名称 336- fsType:文件系统类型, "littlefs" 337- lengthArray:该设备上各分区的长度列表 338- addrArray:该设备上各分区的起始地址列表 339- partNum:分区的个数 340 341格式化接口:int LOS_PartitionFormat(const char *partName, char *fsType, void *data); 342 343- partName:分区名称 344- fsType:文件系统类型, "littlefs" 345- data:私有数据 传入(VOID *)struct fs_cfg 346 347mount接口:int mount(const char *source, const char *target, const char *filesystemtype, unsigned long mountflags, const void *data); 348 349- source:分区名称 350- target:挂载路径 351- filesystemtype:文件系统类型,"littlefs" 352- mountflags:mount配置参数 353- data:私有数据,传入(VOID *)struct fs_cfg 354 355本参考代码已在 ./device/qemu/arm_mps2_an386/liteos_m/board/fs/fs_init.c 中实现,M核qemu上可直接使用,请参考并根据实际硬件修改。 356 357 358``` 359#include "los_config.h" 360#include "ram_virt_flash.h" 361#include "los_fs.h" 362 363struct fs_cfg { 364 CHAR *mount_point; 365 struct PartitionCfg partCfg; 366}; 367 368INT32 LfsLowLevelInit() 369{ 370 INT32 ret; 371 struct fs_cfg fs[LOSCFG_LFS_MAX_MOUNT_SIZE] = {0}; 372 HalLogicPartition *halPartitionsInfo = getPartitionInfo(); /* 获取长度和起始地址的函数,请根据实际单板适配 */ 373 374 INT32 lengthArray[2]; 375 lengthArray[0]= halPartitionsInfo[FLASH_PARTITION_DATA0].partitionLength; 376 377 INT32 addrArray[2]; 378 addrArray[0] = halPartitionsInfo[FLASH_PARTITION_DATA0].partitionStartAddr; 379 380 ret = LOS_DiskPartition("flash0", "littlefs", lengthArray, addrArray, 2); 381 printf("%s: DiskPartition %s\n", __func__, (ret == 0) ? "succeed" : "failed"); 382 if (ret != 0) { 383 return -1; 384 } 385 fs[0].mount_point = "/littlefs"; 386 fs[0].partCfg.partNo = 0; 387 fs[0].partCfg.blockSize = 4096; /* 4096, lfs block size */ 388 fs[0].partCfg.blockCount = 1024; /* 2048, lfs block count */ 389 fs[0].partCfg.readFunc = virt_flash_read; /* flash读函数,请根据实际单板适配 */ 390 fs[0].partCfg.writeFunc = virt_flash_write; /* flash写函数,请根据实际单板适配 */ 391 fs[0].partCfg.eraseFunc = virt_flash_erase; /* flash擦函数,请根据实际单板适配 */ 392 393 fs[0].partCfg.readSize = 256; /* 256, lfs read size */ 394 fs[0].partCfg.writeSize = 256; /* 256, lfs prog size */ 395 fs[0].partCfg.cacheSize = 256; /* 256, lfs cache size */ 396 fs[0].partCfg.lookaheadSize = 16; /* 16, lfs lookahead size */ 397 fs[0].partCfg.blockCycles = 1000; /* 1000, lfs block cycles */ 398 399 ret = LOS_PartitionFormat("flash0", "littlefs", &fs[0].partCfg); 400 printf("%s: PartitionFormat %s\n", __func__, (ret == 0) ? "succeed" : "failed"); 401 if (ret != 0) { 402 return -1; 403 } 404 ret = mount(NULL, fs[0].mount_point, "littlefs", 0, &fs[0].partCfg); 405 printf("%s: mount fs on '%s' %s\n", __func__, fs[0].mount_point, (ret == 0) ? "succeed" : "failed"); 406 if (ret != 0) { 407 return -1; 408 } 409 return 0; 410} 411``` 412 413其中.readFunc,.writeFunc,.eraseFunc分别对应该硬件平台上的底层的读写\擦除等接口。 414 415readSize 每次读取的字节数,可以比物理读单元大以改善性能,这个数值决定了读缓存的大小,但值太大会带来更多的内存消耗。 416 417writeSize 每次写入的字节数,可以比物理写单元大以改善性能,这个数值决定了写缓存的大小,必须是readSize的整数倍,但值太大会带来更多的内存消耗。 418 419blockSize 每个擦除块的字节数,可以比物理擦除单元大,但此数值应尽可能小因为每个文件至少会占用一个块。必须是writeSize的整数倍。 420 421blockCount 可以被擦除的块数量,这取决于块设备的容量及擦除块的大小。 422 423 424### 示例代码 425 426 **前提条件:** 427 428系统已将设备分区挂载到目录,qemu默认已挂载/littlefs。 429 430在kernel/liteos_m目录下执行 make menuconfig 命令配置"FileSystem->Enable FS VFS"开启FS功能; 431 432开启FS后出现新选项“Enable Little FS”开启littlefs。 433 434代码实现如下: 435 436本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleLittlefs。 437 438``` 439#include <stdio.h> 440#include <string.h> 441#include "sys/stat.h" 442#include "fcntl.h" 443#include "unistd.h" 444 445#define BUF_SIZE 20 446#define TEST_ROOT "/littlefs" /* 测试的根目录请根据实际情况调整 */ 447VOID ExampleLittlefs(VOID) 448{ 449 int ret; 450 int fd; 451 ssize_t len; 452 off_t off; 453 char dirName[BUF_SIZE] = TEST_ROOT"/test"; 454 char fileName[BUF_SIZE] = TEST_ROOT"/test/file.txt"; 455 char writeBuf[BUF_SIZE] = "Hello OpenHarmony!"; 456 char readBuf[BUF_SIZE] = {0}; 457 458 /* 创建测试目录 */ 459 ret = mkdir(dirName, 0777); 460 if (ret != LOS_OK) { 461 printf("mkdir failed.\n"); 462 return; 463 } 464 465 /* 创建可读写测试文件 */ 466 fd = open(fileName, O_RDWR | O_CREAT, 0777); 467 if (fd < 0) { 468 printf("open file failed.\n"); 469 return; 470 } 471 472 /* 将writeBuf中的内容写入文件 */ 473 len = write(fd, writeBuf, strlen(writeBuf)); 474 if (len != strlen(writeBuf)) { 475 printf("write file failed.\n"); 476 return; 477 } 478 479 /* 将文件内容刷入存储设备中 */ 480 ret = fsync(fd); 481 if (ret != LOS_OK) { 482 printf("fsync failed.\n"); 483 return; 484 } 485 486 /* 将读写指针偏移至文件头 */ 487 off = lseek(fd, 0, SEEK_SET); 488 if (off != 0) { 489 printf("lseek failed.\n"); 490 return; 491 } 492 493 /* 将文件内容读出至readBuf中,读取长度为readBuf大小 */ 494 len = read(fd, readBuf, sizeof(readBuf)); 495 if (len != strlen(writeBuf)) { 496 printf("read file failed.\n"); 497 return; 498 } 499 printf("%s\n", readBuf); 500 501 /* 关闭测试文件 */ 502 ret = close(fd); 503 if (ret != LOS_OK) { 504 printf("close failed.\n"); 505 return; 506 } 507 508 /* 删除测试文件 */ 509 ret = unlink(fileName); 510 if (ret != LOS_OK) { 511 printf("unlink failed.\n"); 512 return; 513 } 514 515 /* 删除测试目录 */ 516 ret = rmdir(dirName); 517 if (ret != LOS_OK) { 518 printf("rmdir failed.\n"); 519 return; 520 } 521 522 return LOS_OK; 523} 524``` 525 526 527 **结果验证** 528 529首次编译运行得到的结果为: 530 531 532``` 533Hello OpenHarmony! 534```