阅读前言
本文以QNX系统官方的文档英文原版资料为参考,翻译和逐句校对后,对QNX操作系统的相关概念进行了深度整理,旨在帮助想要了解QNX的读者及开发者可以快速阅读,而不必查看晦涩难懂的英文原文,这些文章将会作为一个或多个系列进行发布,从遵从原文的翻译,到针对某些重要概念的穿插引入,以及再到各个重要专题的梳理,大致分为这三个层次部分,分不同的文章进行发布,依据这样的原则进行组织,读者可以更好的查找和理解。
1. 文件系统
QNX Neutrino RTOS 提供了丰富多样的文件系统。与大多数提供服务的进程一样,这些文件系统在内核之外进行执行;应用程序通过与之通信来使用它们提供的服务,通过 POSIXAPI 的共享库中生成的消息来与之通信。
这些文件系统也是本书中所描述的资源管理器。每个文件系统采用路径名空间的一部分(称为挂载点),并通过标准 POSIXAPI (open()
,close()
,read()
,write()
,lseek()
等)提供文件系统服务。文件系统资源管理器接管挂载点并管理它下面的目录结构。文件系统资源管理器还检查各个路径名组件的权限和访问授权。
这个实现意味着:
- 文件系统可以动态地启动和停止。
- 多个文件系统可以并发运行。
- 无论底层文件系统的配置和数量如何,应用程序都提供一个统一的路径名空间和接口。
- 如果使用Qnet,在某个节点上运行的文件系统也可以从任何其他节点透明地访问。
有一些特殊类型的文件系统:
- 直通文件系统【pass-through filesystem】,位于另一个文件系统的前面,并操作底层文件系统中的文件。这方面的一个例子是【解压缩文件系统】Inflator filesystem,我们将在本章后面描述它。
在重叠的路径名空间上运行多个直通文件系统或资源管理器可能会导致死锁。
- 虚拟文件系统【virtual filesystem】,该类型的文件系统的文件或目录不一定直接绑定到底层介质,可能是按需制造的。/proc文件系统就是这样的一个例子;参见 QNX Neutrino 程序员指南中 “Processes” 章节中的“Controlling processes via the /proc filesystem” 的内容。
- QNX可信磁盘设备【QNX Trusted Disk devices】,在安全引导环境中使用QNX可信磁盘设备时,为底层磁盘数据提供完整性保护。
1.1. 文件系统和路径名解析
你可以无缝地定位并连接到已在进程管理器中注册的任何服务或文件系统。当文件系统资源管理器注册一个挂载点时,进程管理器在内部挂载表中为该挂载点及其相应的服务器ID(即nd、pid、chid标识符)创建一个条目。
这个表有效地将多个文件系统目录连接成用户所认为的单个目录。进程管理器负责处理路径名的挂载点部分;而单个文件系统资源管理器负责路径名的其余部分。文件系统可以以任何顺序注册(即挂载)。
当路径名被解析后,进程管理器会联系所有能够处理该路径名中某个组件的文件系统资源管理器。得到的结果是一个可以解析该路径名的文件描述符的集合。
如果路径名表示一个目录,则在调用readdir()时,进程管理器会向所有可以解析路径名的文件系统查询该目录中的文件列表。如果路径名不是目录,那么将会只访问能够解析该路径名的第一个文件系统。
有关路径名解析的更多信息,请参阅本指南中 “进程管理器” 章节中的 “Pathname management” 小节的内容。
1.2. 文件系统分类
许多可用的文件系统可以分为以下几类:
- Image
镜像文件系统是一种特殊的文件系统,表示在镜像中的模块,并且一直存在。注意,procnto进程管理器会自动提供一个镜像文件系统和一个RAM文件系统。
- Block
块文件系统是一种传统的文件系统,在硬盘和DVD驱动器等块设备上运行。这包括电源安全文件系统【 Power-Safe filesystem】、DOS文件系统【DOS】和通用磁盘格式文件系统【Universal Disk Format】。
- Flash
根据 flash memory devices 的特性而明确设计的非面向块的文件系统。对于NOR设备,使用FFS3文件系统;对于 NAND 设备,使用 ETFS。
- Network
提供对远程主机上的文件系统的网络文件访问的文件系统。包括 NFS 和 CIFS(SMB)文件系统。
- Pass-through
位于另一个文件系统前面并操作底层文件系统中的文件的文件系统。这包括Inflator filesystem【解压缩文件系统】,它可以解压缩以前被压缩过的文件(使用deflate
实用程序)。
在重叠的路径名空间上运行多个直通文件系统或资源管理器可能会导致死锁。
- Virtual
虚拟文件系统,其中的文件或目录并非必须直接绑定到底层介质,而是可能是按需制造的。包括/proc文件系统;参见 QNX Neutrino 程序员指南的 “进程” 章节中的 “Controlling processes via the/procfilesystem” 的内容。
1.2.1. 作为共享库的文件系统
由于在 QNX Neutrino RTOS 下运行许多文件系统是很常见的事情,因此这些文件系统会被设计为一系列的驱动程序和共享库,从而最大限度地提高代码重用。这意味着添加额外文件系统的成本通常比预期的要小。
一旦初始文件系统开始运行,额外文件系统的增量内存开销就很小,因为只有实现新文件系统协议的代码才会被添加到系统中。
各种文件系统的分层如下:
如图所示,文件系统和io- block被实现为共享库(本质上是驻留在内存中的被动代码块),而devb-*驱动程序是调用这些共享库的执行进程。在运行中,驱动程序进程首先启动,然后调用块级共享库(io-blk.so
)。文件系统共享库可以在之后动态加载,以提供文件系统接口和服务。
“文件系统”共享库(shared library)就是在物理磁盘设备的一组块(blocks)上实现文件系统协议或实现文件系统“个性【personality】”的软件组件。文件系统并没有内置到操作系统内核中;相反,它们是可以根据需要加载或卸载的动态实体。
@>> 阅读扩展:
“文件系统”共享库(shared library)就是在物理磁盘设备的一组块(blocks)上实现文件系统协议或实现文件系统“个性【personality】”的软件组件。 简而言之,它允许操作系统或应用程序以一种特定的方式(即文件系统协议或个性)来管理和访问存储在物理磁盘上的数据。这样,不同的文件系统共享库可以支持不同的文件系统格式,如FAT、NTFS、EXT4等,使得操作系统能够识别和使用多种类型的磁盘存储。
例如,可以随时插入可移动存储设备(可移动盒式磁盘等),并在其上存储许多文件系统中的任何一个。虽然与驱动程序进行接口的硬件不太可能会动态改变,但磁盘上的数据结构可能变化很大。文件系统的动态特性非常自然地处理了这个问题。
1.2.2. io-blk
大多数文件系统共享库都位于块I/O模块(io-blk.so)之上。
io-blk.so模块还会充当资源管理器,并为每个物理设备导出块设备专用文件。对于有两个硬盘的系统,默认文件如下所示:
- /dev/hd0
第一个硬盘。
- /dev/hd1
第二个硬盘。
这些文件代表每个原始磁盘,可以使用所有正常的POSIX文件原语(open(),close(),read(),write(),lseek()等)进行访问。虽然io-blk模块可以支持64位的寻址偏移量,但驱动程序接口是32位的,因此最大允许访问2tb的磁盘。
1.2.2.1. 内建RAM磁盘
io-blk
模块支持通过命令行选项(blk ramdisk=size
)创建的内部RAM磁盘设备。由于该RAM磁盘位于io-blk
内部(而不是由devb-ram
等其他设备驱动程序进行创建和维护),因此其性能要明显优于专用的RAM磁盘驱动程序。
通过在io-blk
层直接合并 RAM-disk 设备,设备的 data memory 与 main cache 平行,因此对该设备的 I/O 操作可以绕过 buffer cache,消除内存拷贝的同时仍然可以保持数据一致性。与此相反,在驱动程序级实现(例如通过devb-ram
实现)中,将RAM透明地表示为块设备,涉及到了额外的内存拷贝以及在buffer cache中的复制数据的操作。在dll
之间的外部调用也会被消除。此外,对于有硬盘并且需要 RAM 磁盘的系统,在驱动程序的安装占用方面也有好处:只需要一个驱动程序即可。
1.2.2.2. 分区【partitions】
QNX Neutrino RTOS 符合实际的磁盘分区行业标准。
这允许多个文件系统共享同一个物理磁盘。每个分区也同样会表示为一个块专用文件,分区类型附加在其所在磁盘的文件名之后。在上面的“双磁盘”示例中,如果第一个磁盘有一个Power-Safe分区和一个DOS分区,而第二个磁盘只有一个Power-Safe分区,那么系统中的默认文件将是:
- /dev/hd0
第一个硬盘
- /dev/hd0t6
第一个硬盘上的DOS分区
- /dev/hd0t179
第一个硬盘上的Power-Safe分区
- /dev/hd1
第二个硬盘
- /dev/hd1t179
第二个硬盘上的Power-Safe分区
下表显示了分配的一些典型的分区类型:
Type | Filesystem |
1 | DOS (12-bit FAT) |
4 | DOS (16-bit FAT; partitions <32M) |
5 | DOS Extended Partition (enumerated but not presented) |
6 | DOS 4.0 (16-bit FAT; partitions ≥32M) |
7 | OS/2 HPFS |
7 | Windows NT |
11 | DOS 32-bit FAT; partitions up to 2047G |
12 | Same as Type 11, but uses Logical Block Address Int 13h extensions |
14 | Same as Type 6, but uses Logical Block Address Int 13h extensions |
15 | Same as Type 5, but uses Logical Block Address Int 13h extensions |
77 | QNX 4 |
78 | QNX 4 |
79 | QNX 4 |
99 | UNIX |
131 | Linux (Ext2) |
175 | Apple Macintosh HFS or HFS Plus |
177 | QNX Power-Safe POSIX partition (secondary) |
178 | QNX Power-Safe POSIX partition (secondary) |
179 | QNX Power-Safe POSIX partition |
185 | QNX Trusted Disk (QTD) |
1.2.2.3. Buffer cache
io-blk共享库实现了所有文件系统都继承的buffer cache【缓冲区缓存】。该buffer cache会试图存储被频繁访问的 filesystem blocks,从而尽量减少系统对磁盘必须执行物理I/O的次数。
读操作是同步的;写操作通常是异步的。当应用程序写入文件时,数据会先进入缓存【cache】中,文件系统管理器立即响应客户端进程,以指示数据已被写入。然后再将数据写入到磁盘中。
关键的文件系统块【Critical filesystem blocks】(如位图块【bitmap blocks】、目录块【directory blocks】、扩展块【extent blocks】和inode块【inode blocks】)会立即同步写入到磁盘中。
应用程序可以逐个文件地修改写入行为。例如,数据库应用程序可能导致对于所给定文件的所有写操作进行同步执行。这将确保在面对可能导致数据库处于不一致状态的潜在硬件或电源问题时仍然保持高等级的文件完整性。
1.2.3. 文件系统限制
POSIX 定义了文件系统必须提供的一组服务。然而,并不是所有的文件系统都能够提供所有这些服务。
[a]:文件名的内部表示是UTF-8字符集,每个字符的字节数是可变的。许多磁盘上的格式使用的是UCS2字符集(是一种固定长度的Unicode字符编码格式,使用2个字节表示一个字符)。因此,当我们把磁盘上的“字符编码格式”转换为操作系统字符编码格式时,以字符为单位的长度限制,可能是以字节数为单位的长度限制的1倍、2倍或3倍。Power-Safe和EXT2文件系统的长度以字节为单位;而UDF和DOS/VFAT文件系统的长度以字符为单位。
[b]:RAM“文件系统”(/dev/shmem)并不是一个真正的文件系统;它是一个查看共享内存名称的窗口,具有一些类似文件系统的特征。请参阅本章后面的 “Builtin RAM disk” 的内容。
[c]:VFAT或FAT32(Windows 95或更高版本)。
[d]:VFAT或FAT32使用255个字符的文件名长度(例如,Windows 95)。
[e]:在HFS上使用31个字符的文件名长度。
[f]:受到远程文件系统的限制。
1.3. Image文件系统
每个 QNX Neutrino 系统映像, 都提供了一个简单的只读文件系统,表示内置于操作系统镜像中的一组文件。
由于这个镜像可能包括可执行文件和数据文件,因此该文件系统对于许多嵌入式系统来说就已经足够了。如果需要额外的文件系统,可以将它们作为模块放置在镜像中,根据需要启动它们。
1.4. RAM文件系统
每个 QNX Neutrino 系统还提供了一个简单的基于 RAM 的“文件系统”,允许把读/写文件放在/dev/shmem
路径之下。
注意/dev/shmem
实际上并不是一个文件系统。它是一个查看共享内存名称的窗口,这些名称的组织形式恰好具有一些类似于文件系统的特征。
这种RAM文件系统在小型嵌入式系统中使用最多,在这些系统中可能并不需要一个跨 reboot 的持久化存储,而是需要一个具有有限特性的小型的、快速的以及临时存储【temporary-storage】的文件系统。
该文件系统是procnto
所提供的免费工具,而不需要任何设置。你可以简单地在/dev/shmem
下创建文件并将其扩展到任意大小(取决于RAM资源)。
尽管RAM文件系统本身并不支持硬链接或软链接或目录,但你可以通过使用进程管理器链接 【process-manager links】创建到RAM文件系统的链接。例如,你可以创建一个到基于RAM的/tmp
目录的链接:
ln -sP /dev/shmem /tmp
该代码告诉进程管理器procto创建一个指向/dev/shmem的进程管理器链接,称为“/tmp
”。应用程序可以打开位于/tmp
目录下的文件,就好像它是一个普通的文件系统一样。
为了最小化进程管理器中的 RAM 文件系统代码的大小,该文件系统特意不包括某些“大型文件系统”的特性(例如“文件锁定”和“目录创建”等特性)。
1.5. 嵌入式事务文件系统(ETFS)
ETFS实现了一个高可靠性的文件系统,用于嵌入式固态存储设备,特别是NAND闪存。
文件系统支持具有POSIX语义的完全分层的目录结构,如上表所示。
ETFS是一个完全由事务组成的文件系统。每个写操作,无论是用户数据还是文件系统元数据,都由一个事务组成。事务要么成功,要么被视为从未发生过。
事务永远不会覆盖实时数据。在文件或目录更新过程中,写操作总是会被写入到新的未使用的区域。这样,如果操作中途失败(由于崩溃或电源故障),旧数据仍然是完整的。
某些基于日志的文件系统也在不覆盖实时数据的原则下运行。但ETFS将这一点发挥到了极致,它把一切都变成了事务日志。文件系统层次结构是通过处理设备中的事务日志动态创建的。对事务日志的这种扫描在启动时进行,但这被设计成为只读取一小部分数据并进行CRC检查,从而在不牺牲可靠性的情况下加快启动时间。
事务在设备中与位置无关,可以以任何顺序发生。你可以从一台设备读取事务记录,然后以不同的顺序将它们写入到另一台设备。这很重要,因为它允许对包含坏块的设备进行主体【bulk】编程,坏块可能位于任意位置。
这种设计非常适合 NAND flash 闪存。NAND flash 闪存可能会带有坏块,这些坏块在出厂时会被标记,并且可能出现在任何位置。
1.5.1. 事务内部组成【Inside a transaction】
每个事务由一个 header 和数据组成。header 包含以下内容:
- FID,标识事务所属文件的唯一文件ID。
- Offset,文件中数据部分的偏移量。
- Size,数据部分的大小。
- Sequence,单调递增的数字(以使能时间排序)。
- CRCs,数据完整性检查(对于NAND, NOR, SRAM)。
- ECCs,纠错(对于NAND)。
- Other,为将来的扩展预留。
1.5.2. 存储介质类型【Types of storage media】
每个事务由一个 header 和数据组成。header 包含以下内容:
- FID,标识事务所属文件的唯一文件ID。
- Offset,文件中数据部分的偏移量。
- Size,数据部分的大小。
- Sequence,单调递增的数字(以使能时间排序)。
- CRCs,数据完整性检查(对于NAND, NOR, SRAM)。
- ECCs,纠错(对于NAND)。
- Other,为将来的扩展预留。
1.5.2. 存储介质类型【Types of storage media】
虽然最适合NAND设备,ETFS也支持其他类型的嵌入式存储介质,通过使用如下所示的驱动程序类别:
虽然 ETFS 也可以支持 NOR flash,但我们建议你使用 FFS3 文件系统(devf-*),因为它是专门为NOR flash设备设计的。
1.5.3. 可靠性的特点【Reliability features】
ETFS文件系统被设计成可以在电源故障时幸存,即使在活动的闪存写入或块擦除期间发生电源故障也是如此。以下特点有助于实现其可靠性:
- 动态磨损均衡【dynamic wear-leveling】
- 静态磨损均衡【static wear-leveling】
- CRC错误检测【CRC error detection】
- ECC错误纠正【ECC error correction】
- 自动刷新读取降级监控【read degradation monitoring with automatic refresh】
- 事务回滚【transaction rollback】
- 原子文件操作【atomic file operations】
- 自动文件碎片整理【automatic file defragmentation】
@>> 知识扩展:
Read degradation monitoring with automatic refresh【自动刷新读取降级监控】是一种用于监控和自动刷新存储设备读取性能下降的技术。这种技术通过实时监测存储设备的读取性能,一旦发现性能下降(如读取速度变慢、错误率增加等),就会自动触发刷新操作,以恢复或提升存储设备的读取性能。这种机制有助于确保存储系统的稳定性和可靠性,减少因读取性能下降而导致的系统延迟或故障。然而,具体的实现细节(如监控指标、刷新策略等)可能因不同的存储系统和应用场景而异。
动态磨损均衡
Flash memory 允许在 flash block 上进行有限次数的擦除循环,然后该 block 就会失效。这个上限次数有可能低至10万次。ETFS可以跟踪每个 block 的擦除次数。当选择要使用的块时,ETFS尝试将擦除次数均匀地分布在设备的每个 block 上,从而显著增加其使用寿命。采用这种策略的寿命差异可能是极端的:从没有磨损平衡的故障使用场景的几天寿命到采用磨损平衡的使用场景下的超过40年的寿命。
静态磨损均衡
文件系统通常由大量只能读但不能写的静态文件组成。这些文件将会占用没有理由会被擦除的 flash 块。如果 flash中的大多数文件是静态的,会导致包含动态数据的剩余块以急剧增加的速度出现磨损。
ETFS可以注意到这些欠工作量的静态块,并通过将它们的数据复制到工作过度的块来强制使这些静态块投入到服务中。这解决了两个问题:让过度工作的块得到休息,因为这些过度工作的 flash 块现在会变为包含静态数据,并且强制欠工作量的静态块进入到动态块池中。
CRC错误检测
每个事务都受到循环冗余校验(CRC)的保护。这确保了对损坏数据的快速检测,并形成了在启动时对损坏或不完整事务进行回滚操作的基础。CRC可以检测掉电过程中可能出现的多个比特错误。
ECC错误纠正
当发生CRC错误时,ETFS可以采用纠错编码(error correction coding,ECC)来尝试恢复数据。这适用于在正常使用过程中可能出现单比特错误的 NAND flash 闪存。ECC错误是一个警告信号,表示发生错误的 flash 块可能正在变弱,即失去电荷。
ETFS 会将弱块标记为需要刷新操作,刷新操作会把数据复制到新的 flash 块并擦除弱块。擦除操作会为 flash 块进行充电。
自动刷新读取降级监控
NAND flash块内的每次读取操作都会削弱维护数据位的电荷。大多数的设备在出现数据丢失的危险之前可以支持大约100,000次的读取次数。ECC可以恢复单比特错误,但可能无法恢复多比特错误。
ETFS 通过跟踪读取并在达到100,000次读取限制之前标记刷新块来解决这个问题。
事务回滚
当 ETFS 启动时,它处理所有交易并回滚(丢弃)最后的部分或损坏的交易。回滚代码设计用于处理回滚操作期间的电源故障,从而允许系统从多个嵌套故障中恢复。事务的有效性由每个事务上的CRC代码保护。
原子的文件操作
ETFS 在设备上实现了一个非常简单的目录结构,允许通过一个单次 flash 写入进行重大修改。例如,在大多数文件系统中,将一个文件或目录移动到另一个目录通常是一个多步骤操作。但是在 ETFS 中,每次移动都是通过一个单次 flash 写入完成的。
自动文件碎片整理
基于日志的文件系统经常受到碎片的困扰,因为每次对现有文件的更新或写入都会导致创建一个新事务。ETFS使用写缓冲(write-buffer)把小的写操作合并为大的写操作,以尽量减少由大量小的事务引起的碎片。ETFS 还会监视每个文件的碎片化级别,并对碎片化变得严重的文件进行后台整理操作。请注意,这个后台活动总是会被用户数据请求抢占,以确保立即访问正在整理的文件。
未完,请继续看下一篇:《 2-2-18-9 QNX系统架构之文件系统(二)》