当前位置: 首页 > web >正文

QuecPython 文件系统操作

本文档旨在介绍QuecPython文件系统类型、使用方式和常见问题,指导客户使用QuecPython文件系统功能。

概述

文件系统是指文件和对文件进行操作和管理的软件的集合。文件系统实现了存储空间管理、构造文件结构、提供访问文件的操作接口。使用文件系统存储方式可以方便进行文件的增、删、查、改。

文件系统类型

目前各个平台使用的文件系统类型有EFS、SPIFFS、littleFS、FATFS。

文件系统名称特点适用场景适用型号开源网址
EFS支持各种NOR和NAND技术嵌入式系统NOR flash、NAND flash、SD卡、EMMC存储BG95系列模组底层高通特有,非开源
SPIFFS低资源消耗、擦写均衡、掉电保护嵌入式系统SPI NOR flash存储ECx00U&EGx00U&ECx00G系列模组底层https://github.com/pellepl/spiffs
littleFS低资源消耗、擦写均衡、掉电保护嵌入式系统SPI NOR flash存储各型号QuecPython应用层https://github.com/littlefs-project/littlefs
FATFS兼容Windows FAT32格式嵌入式系统SD卡和EMMC存储各型号SD卡和EMMC存储场景http://elm-chan.org/fsw/ff/00index_e.html

VFS

虚拟文件系统。在上述实体文件系统基础之上抽象的文件系统,提供统一的接口访问不同的实体文件系统。VFS API兼容POSIX标准API。具体操作步骤如下。

初始化实体文件系统

该步主要进行存储介质硬件初始化,然后挂载实体文件系统,最后获取到实体文件系统的句柄和文件操作接口。每个实体文件系统有各自独立的硬件初始化和挂载接口。如果初始化成功,这些接口最终返回实体文件系统的对象,对象中包含有实体文件系统句柄和文件操作接口等信息。

  • littleFS对应初始化接口为uos.VfsLfs1()(接口用法参考注册littleFS存储设备 - SPI NOR FLASH)
  • SPI SD卡FATFS对应初始化接口为uos.VfsFat()(接口用法参考注册存储设备 - SPI - SD卡)
  • SDIO SD卡FATFS对应初始化接口为uos.VfsSd()(接口用法参考注册存储设备 - SDIO - SD卡)
挂载虚拟文件系统

该步将实体文件系统接口绑定到虚拟文件系统接口。具体接口为uos.mount(vfs_obj, path)。其中参数vfs_obj为上一步初始化实体文件系统返回的对象,参数path为虚拟文件系统的根目录,虚拟文件系统正是以根目录来区分不同的实体文件系统,即每一个实体文件系统绑定一个不同的根目录。根据应用场景,文件系统根目录可以分为:内置NOR flash用户区usr、内置NOR flash备份区bak、外置NOR flash区ext、SD卡区sd、EMMC区emmc。这样不同的存储区域可以使用同一套软件接口传入不同的根目录进行访问。接口用法参考挂载文件系统。

卸载虚拟文件系统

将实体文件系统接口和虚拟文件系统接口解绑,具体接口为uos.umount(path),其中path同uos.mount()接口传入的path一致。

应用

基础文件操作

POSIX API

当进行基础的文件操作时可直接使用POSIX的接口进行开发。

打开文件
open(file, mode="r")

  • file: 文件路径(在QuecPython里,用户文件路径在/usr下,所以用户创建读取文件都要在此目录下进行)

  • mode: 打开模式,可选,默认为只读

模式描述
w以只写方式打开文件。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
r以只读方式打开文件。文件的指针将会放在文件的开头。
w+以读写方式打开文件。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
r+以读写方式打开文件。该文件必须存在。文件的指针将会放在文件的开头。
wb以只写方式打开二进制文件。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
rb以只读方式打开二进制文件。文件的指针将会放在文件的开头。
wb+以读写方式打开二进制文件。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
rb+以读写方式打开二进制文件。该文件必须存在。文件的指针将会放在文件的开头。
a打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行读写。
ab打开一个二进制文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab+打开一个二进制文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行读写。
写入文件
write(str)

  • str: 要写入的数据

该接口返回成功写入的字符串长度

读取文件
read(size)

  • size:要读取的长度,单位字节

该接口返回成功读到的数据

读取文件的一行

readline(size)

  • size:要读取的长度,单位字节

调用此接口会根据结束符自动读取,并返回一个等于size长度的字符串,如果size为-1则返回整行。

读取文件所有行

readlines()

调用此接口会根据结束符自动分行读取,并返回一个包含所有分行的列表。

移动文件指针
seek(offset, whence)

  • offset: 开始的偏移量,也就是代表需要移动偏移的字节数
  • whence: 可选,默认值为 0。给offset参数一个定义,表示要从哪个位置开始偏移;0代表从文件开头开始算起,1代表从当前位置开始算起,2代表从文件末尾算起。
关闭文件
close()

调用后关闭文件,不再继续操作该文件。

综合示例
# 创建一个test.txt文件,注意路径在/usr下
f = open("/usr/test.txt", "w+")
i = f.write("hello world\n")
i = i + f.write("hello quecpython\n")
print("write length{}".format(i))
f.seek(0)
str = f.read(10)
print("read content:{}".format(str))
f.seek(0)
str = f.readline()
print("read line content:{}".format(str))
f.seek(0)
str = f.readlines()
print("read all lines content:{}".format(str))
f.close()
print("test end")

运行结果
import example
>>> example.exec('/usr/example_fs_basic_operation_posix.py')write length29
read content:hello worl
read line content:hello worldread all lines content:['hello world\n', 'hello quecpython\n']test end
>>>

点此在github中下载完整代码。

uos API

当进行目录等操作的时候,需要调用uos库进行操作,接口用法参考uos - 基本系统服务。

综合示例
import uosdef main():# 创建文件 此处并没有对文件进行写入操作f = open("/usr/uos_test","w")f.close()del f# 查看文件是否存在t = uos.listdir("/usr")print("usr files:{}".format(t))if "uos_test" not in t:print("file not exist test fail")return# 查看文件状态a = uos.stat("/usr/uos_test")print("file size:{}bytes".format(a[6]))# 重命名文件uos.rename("/usr/uos_test", "/usr/uos_test_new")t = uos.listdir("/usr")print("test file renamed, usr files:{}".format(t))if "uos_test_new" not in t:print("renamed file not exist test fail")return# 删除文件uos.remove("/usr/uos_test_new")t = uos.listdir("/usr")print("remove test file, usr files:{}".format(t))if "uos_test_new" in t:print("remove file fail, test fail")return# 目录操作t = uos.getcwd()print("current path:{}".format(t))uos.chdir("/usr")t = uos.getcwd()print("current path:{}".format(t))if "/usr" != t:print("dir change fail")returnuos.mkdir("testdir")t = uos.listdir("/usr")print("make dir, usr files:{}".format(t))if "testdir" not in t:print("make dir fail")returnuos.rmdir("testdir")t = uos.listdir("/usr")print("remove test dir, usr files:{}".format(t))if "testdir" in t:print("remove dir fail")returnif __name__ == "__main__":main()

运行结果

>>> example.exec('/usr/example_fs_basic_operation_uos.py')usr files:['system_config.json', 'example_fs_basic_operation_uos.py', 'uos_test']
file size:0bytestest file renamed, usr files:['system_config.json', 'example_fs_basic_operation_uos.py', 'uos_test_new']remove test file, usr files:['system_config.json', 'example_fs_basic_operation_uos.py']
current path:/
current path:/usrmake dir, usr files:['system_config.json', 'example_fs_basic_operation_uos.py', 'testdir']remove test dir, usr files:['system_config.json', 'example_fs_basic_operation_uos.py']
>>>

点此在github中下载完整代码。

高级文件操作

ql_fs - 高级文件操作,接口用法参考ql_fs - 高级文件操作。

综合示例
import ql_fsdef main():# 递归式创建文件夹, 传入文件夹路径ql_fs.mkdirs("usr/a/b")# 查看文件或文件夹是否存在ret = ql_fs.path_exists("/usr/a/b")if ret:print("make dir success")else:print("make dir fail")return# 创建文件或者更新文件数据data = {"test":1}ql_fs.touch("/usr/a/b/config.json", data)# 查看文件或文件夹是否存在ret = ql_fs.path_exists("/usr/a/b/config.json")if ret:print("create file success")else:print("create file fail")return# 读取json文件data = ql_fs.read_json("/usr/a/b/config.json")print("config json read content:{}".format(data))data = ql_fs.read_json("/usr/system_config.json")len = ql_fs.path_getsize('usr/system_config.json')print("system_config json length:{}".format(len))print("system_config json read content:{}".format(data))# 文件拷贝ql_fs.file_copy("/usr/a/b/config.json", "usr/system_config.json")data = ql_fs.read_json("/usr/a/b/config.json")print("copy json read content:{}".format(data))# 获取文件所在文件夹路径ret = ql_fs.path_dirname("/usr/a/b/config.json")print("path of the file:{}".format(ret))# 删除文件夹和其下的文件ql_fs.rmdirs("usr/a")# 查看文件或文件夹是否存在ret = ql_fs.path_exists("/usr/a/b/config.json")if ret:print("remove file fail, test fail")ret = ql_fs.path_exists("/usr/a/b")if ret:print("remove dir2 fail, test fail")ret = ql_fs.path_exists("/usr/a")if ret:print("remove dir1 fail, test fail")if __name__ == "__main__":main()

运行结果

>>>
example.exec('/usr/example_fs_advanced_operation.py')
make dir successcreate file success
config json read content:{'test': 1}
system_config json length:15
system_config json read content:{'replFlag': 0}copy json read content:{'replFlag': 0}
path of the file:/usr/a/b>>>

点此在github中下载完整代码。

备份还原

概述 - 备份还原

QuecPython设备软件由固件和用户应用脚本2部分组成。其中固件存放在系统程序分区包括kernel和QuecPython VM,而用户应用脚本存放在设备文件系统分区。为了确保系统稳定性,设计了文件系统备份还原机制。当前设备划分了2个文件系统分区,分别是:用户文件系统usr和备份文件系统bak。用户文件系统存放用户正常运行需要的py脚本和数据文件,可读写。备份文件系统用来备份用户文件系统中的出厂原始文件,只读。如果开启了备份还原功能,当用户文件系统中的文件被误删除或意外被修改时,会自动从备份文件系统还原原始的文件到用户文件系统。NOR flash空间分布如下图:

实现原理

备份

对用户文件系统的文件进行备份,具体流程是:

1.将用户文件系统下的源文件拷贝一份到备份文件系统。

2.在用户文件系统和备份文件系统各生成一个checksum.json文件。该文件存放的内容是每一个源文件的文件名和其对应的checksum值,checksum值是根据每一个源文件的内容通过CRC32算法计算出来的,所以每一个源文件都有唯一对应的一个checksum值。

3.在备份文件系统生成一个备份还原标志文件backup_restore.json,记录是否开启备份还原功能。

还原

当用户文件系统的文件发生损坏时,从备份文件系统中拷贝对应的文件到用户文件系统。具体流程是:
1.开机检测到备份文件系统中备份还原的标志使能,且用户文件系统下的checksum.json文件存在,这时如果用户文件系统下的某个源文件被删除,则会将备份文件系统中对应源文件拷贝到用户文件系统下,并更新该文件对应的checksum值到用户文件系统下的checksum.json文件中。

2.开机检测到备份文件系统中备份还原的标志使能,且用户文件系统下的checksum.json文件存在,这时如果用户文件系统下的某个源文件被破坏,则会将备份文件系统中对应源文件拷贝到用户文件系统下,并更新该文件对应的checksum值到用户文件系统下的checksum.json文件中。

3.开机检测到备份文件系统中备份还原的标志使能,这时如果用户文件系统下的checksum.json文件不存在,则会将备份文件系统中checksum.json文件拷贝一份到用户文件系统,然后再走上述1、2步的检测流程。

OTA更新

当用户文件系统的文件需要OTA升级时,在升级成功后,会将升级后的用户文件的checksum值更新到用户文件系统下的checksum.json文件中。

注意:不会更新备份文件系统下的checksum文件和源文件。

工具操作

1.选择项目。

2.选择待合并的固件。

3.右键选择usr弹出对话框。

4.点击添加文件选项,添加待合并备份的文件。

5.已添加的待合并的文件。

6.勾选备份按钮。

7.点击合并按钮进行合并固件。合并成功即可生成带备份还原功能的量产固件。

1.右键选择usr弹出对话框。

2.点击添加文件选项,添加待合并备份的文件。

1.已添加的待合并的文件。

2.勾选备份按钮。

3.点击合并按钮进行合并固件。合并成功即可生成带备份还原功能的固件。

注意事项

掉电保护

为了防止文件操作过程中突然掉电导致的已有数据丢失或文件系统损坏,部分文件系统被设计可以处理随机电源故障。即所有文件操作都有写时复制保证,如果断电,文件系统将恢复到上次已知的完好状态。前述的SPIFFS和littleFS都支持该特性。

擦写均衡

由于NOR flash存储的擦写寿命是有限的,为了避免对存储空间某一块频繁擦写导致该块无法使用进而影响到整个flash存储的正常使用的问题,需要设计一套算法将用户的擦写操作平均分散到整个NOR flash存储空间上。而部分文件系统在设计时已经考虑了该特性,如前述的SPIFFS和littleFS文件系统。

读写速率

1.读写速率主要取决于硬件通信接口,SPI 6线模式要快于SPI 4线模式,SDIO 4线模式要快于SPI模式。提高速率的方法,减少open、close的频率,文件open一次直到write完所有内容后再close。

2.对于SD卡应用场景,当存储的文件个数超过一定数量如1000个,这时对文件执行open、write、close操作会比较耗时。这时可以减少文件个数,或者增加子目录,在每个子目录中事先创建好每个空文件,以规避这个问题。

3.针对部分应用场景实时性要求比较高的情况,如果可以,使用裸flash接口访问。如GUI字库文件。同时,也可以采用缓存机制提高访问速度,将经常访问的数据缓存到RAM中,下次访问时直接从RAM中读取,如GUI显示用到的图片文件。

空间利用效率

由于NOR flash存储空间有限,这里主要考虑NOR flash littleFS文件系统空间使用情况。

小文件存储机制:

当文件大小小于等于一个块(如4KB)时,其消耗恒定为一个块。即使是远小于一个块的文件(如1byte),也要消耗一整个块的空间。这是因为littlefs1.0文件系统缺乏复用多余块空间的机制。而littlefs2.0中引入内联文件机制,则会将小文件存放到所在文件夹所占空间中以减少空间的占用。

大文件存储机制:

当文件大小达到一个块以上时,需要引入ctz逆序链表的机制。相比于传统文件系统的链表,这种链表为逆序,追加数据时不需要额外的开销来重新建立所有的索引。且引入了ctz指针机制,即block N如果是一个能被2^X整除的数,那么他就存在指向N – 2^X的指针。大文件的指针信息和文件本身的内容存储在同一空间,计算文件实际大小时,需要考虑指针占用的空间。所以,大文件的实际使用空间为其本身占用的空间,加上CTZ指针消耗的空间。

文件夹存储机制:

在littlefs文件系统中,新建一个文件夹需要创建一组新的metadata pair来维护此文件夹下的内容,因此会造成两个block的开销。综上,为了高效使用存储空间,避免大量小文件的存储,及避免使用文件夹。

常见问题

挂载失败

1.如果抛异常”OSError: [Errno1] EPERM“,是因为重复调用uos.mount()。

2.如果外置spi NOR flash挂载抛异常”OSError: [Errno 19] ENODEV“,一般是硬件连接异常,如spi port不对应,硬件连接不可靠。

创建文件失败、写文件失败

1.一种情况是,部分型号如果文件没有close重复open则会返回失败。需要确保open、close成对操作。

2.空间还剩余挺多,写文件抛异常error 28报空间不足。如果有f.seek()操作,由于文件系统掉电保护机制,需要保证剩余空间大于seek的目标文件位置到文件末尾的长度。

with open 和 open的区别

open是python的一个内置接口。with open是使用了with语句的open接口。open()完成后必须调用close()接口关闭文件。因为文件对象会占用系统的资源,同一时间能打开的文件数量也是有限的,另外不成对使用可能会导致其他异常。由于文件读写时有可能产生IO异常,一旦出错,后面的close()就不会调用。with open则可以避免这样的情况,即便在文件读写过程中发生IO异常,也会自动调用close()接口关闭文件。

with open('/usr/test.txt','w+')as f:f.write('1234567890')
http://www.xdnf.cn/news/13362.html

相关文章:

  • 多光谱图像技术在苗期作物与杂草识别中的研究进展
  • C语言学习20250610
  • Dynadot邮箱工具指南(六):将域名邮箱添加至网易邮箱大师
  • Leetcode 3576. Transform Array to All Equal Elements
  • 新能源知识库(34)什么是单一制和两部制
  • 【SAP MM SD FICO】销售视图和会计视图
  • C++ 8.1内联函数之宏定义
  • Metasploitable: 1靶场渗透
  • 在postgresql中,group by时取第一个值
  • 网络编程(Modbus进阶)
  • Manus 框架与 COKE 框架解析及完整 Demo
  • Unreal从入门到精通之使用 CheatManager 自定义控制台命令
  • 操作系统的一些名词
  • 期末考试复习总结-第一章《HarmonyOS介绍》
  • ​计算机网络原理超详解说​
  • 2025-03-14-Google检索技巧
  • 华为云Flexus+DeepSeek征文 | 基于ModelArts Studio、DeepSeek大模型和Dify搭建网站智能客服助手
  • 深度学习——简介
  • Ubuntu下挂载NTFS格式磁盘
  • 访问服务器项目,服务器可以ping通,但是端口访问不到
  • C++ mutex 锁的使用
  • JavaScript BOM 详细介绍
  • 重温经典算法——二分查找
  • 借助AI识别测试盲区:从需求文档中挖掘遗漏场景
  • CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
  • 深度学习:概念、特点和发展史
  • Admin.Net中的消息通信SignalR解释
  • 基于OpenCV的风格迁移:图像金字塔方法
  • jupyterhub的浅浅使用-重点在解决无法登录
  • GD32-开发工程搭建