嵌入式系统文件系统配置与管理指南

在嵌入式系统开发中,文件系统的配置和管理是至关重要的环节。合理的文件系统选择和布局能够提高系统的性能、稳定性和可维护性。本文将详细介绍如何使用NFS挂载根文件系统将文件系统映像写入闪存,以及如何在RAM磁盘上放置磁盘文件系统等内容。

使用NFS挂载的根文件系统将文件系统映像写入闪存

在早期开发阶段,通过NFS将主机的根文件系统导出到目标设备可以简化开发过程,因为可以快速修改目标设备使用的文件。后续,为了实现自托管,目标设备需要将文件系统存储在其闪存中。除了使用某些引导加载程序将映像复制到闪存外,还可以使用目标设备上运行的MTD实用程序来复制NFS挂载的根文件系统中的文件。

以下是将初始RAM磁盘映像复制到目标设备闪存的步骤:
1. 配置目标设备 :使用NFS从主机导出的目录挂载其根文件系统。
2. 复制文件系统映像 :在主机上,将文件系统映像复制到导出到目标设备的目录。虽然文件系统映像实际上不在目标设备上,但内核在启动时使用NFS挂载它后,将在其根文件系统中可见。
3. 复制到闪存 :启动目标设备,并使用目标设备上的MTD实用程序将文件系统映像从NFS挂载的根文件系统复制到目标设备 /dev 目录中的相应闪存设备条目。

在RAM磁盘上放置磁盘文件系统

RAM磁盘顾名思义存在于RAM中,其行为类似于块设备。内核可以同时支持多个活动的RAM磁盘。由于它们的行为类似于块设备,因此可以使用任何磁盘文件系统。但由于其内容仅在系统重启之前存在,因此RAM磁盘通常用于存储磁盘文件系统的压缩映像,如Ext2文件系统。这些映像被称为压缩RAM磁盘映像。

在嵌入式Linux系统的初始化过程中,使用此类压缩RAM磁盘映像是非常有吸引力的。具体来说,内核可以从存储设备中提取初始RAM磁盘(initrd)映像,并将其用作根文件系统。

启动时,内核会验证其引导选项是否指示存在initrd。如果存在,它会将文件系统映像(无论是否压缩)从指定的存储介质提取到RAM磁盘中,并将其挂载为根文件系统。直到内核版本2.6,initrd机制实际上是为内核提供RAM中的根文件系统的最简单方法,并且至今仍被广泛使用。

以下是创建基于Ext2的压缩RAM磁盘映像的步骤:

$cd ${PRJROOT}
$ genext2fs -b 1024 -d rootfs -D device_table.txt -e 0 initrd.img

-e 选项将映像中未分配的空间清零。以这种方式初始化文件系统可以在后续使用gzip压缩整个映像时,为文件系统的未使用部分实现最大压缩比。

最后,使用以下命令压缩文件系统以获得压缩的RAM磁盘:

$ gzip -9 < images/initrd.img > images/initrd.bin
$ ls -al images/initrd*
-rw-rw-r--   1 karim   karim    3101646 Aug 16 14:47 images/initrd.bin
-rw-rw-r--   1 karim   karim    8388608 Aug 16 14:46 images/initrd.img

-9 选项告诉 gzip 命令使用可用的最高压缩算法。在这种情况下,压缩比超过60%,与JFFS2相当,优于Cramfs。当然,JFFS2和Cramfs具有持久性的优点,而RAM磁盘则不具备。

可以将此处创建的RAM磁盘映像 images/initrd.bin 放置在目标设备的适当设备上,并相应地配置引导加载程序。

Rootfs和Initramfs

Linux 2.6将其启动init程序存储在CPIO格式的压缩存档中。启动过程将该存档提取到Tmpfs(或如果Tmpfs不可用则为Ramfs)文件系统的特殊实例中,该实例始终存在于2.6系统中。

Rootfs无法卸载,原因与不能杀死init进程类似。为了简化操作,内核确保某些列表不会为空,而不是编写特殊代码来检查和处理空列表。大多数系统只是在Rootfs上挂载另一个文件系统并忽略它,一个空的Ramfs实例占用的空间非常小。

提取后,内核将尝试在Rootfs文件系统中找到init程序。如果存在,Rootfs将用作根文件系统,其中的init程序负责设置运行系统的其余部分(可能在Rootfs之上挂载不同的根文件系统)。如果Rootfs中没有init程序,2.6内核将回退到执行旧代码,通过内核命令行的root参数或initrd机制查找根文件系统。

可以通过 CONFIG_INITRAMFS_SOURCE 内核配置选项来填充Initramfs,该选项可用于将Initramfs存档的位置硬编码到内核二进制文件中。选项值可以指向现有的gzip压缩CPIO存档、包含要压缩到新存档中的文件的目录,或包含以下语法行的文本文件:

# a comment
file name location mode uid gid
dir name mode uid gid
nod name mode uid gid dev_type maj min
slink name target mode uid gid
pipe name mode uid gid
sock name mode uid gid

各参数含义如下表所示:
| 参数 | 含义 |
| ---- | ---- |
| name | 存档中文件、目录等的名称 |
| location | 当前文件系统中文件的位置 |
| target | 链接的目标 |
| mode | 文件的权限(八进制表示,如777表示完全权限) |
| uid | 用户ID(0表示root) |
| gid | 组ID(0表示root) |
| dev_type | 设备类型(b表示块设备,c表示字符设备) |
| maj | 节点的主编号 |
| min | 节点的次编号 |

以下是一个简单的示例:

  dir /dev 755 0 0
  nod /dev/console 644 0 0 c 5 1
  nod /dev/loop0 644 0 0 b 7 0
  dir /bin 755 1000 1000
  slink /bin/sh busybox 777 0 0
  file /bin/busybox initramfs/busybox 755 0 0
  dir /proc 755 0 0
  dir /sys 755 0 0
  dir /mnt 755 0 0
  file /init initramfs/init.sh 755 0 0

Linux内核构建不需要任何外部实用程序来创建或提取CPIO存档,相关代码是自包含的。但如果想自己创建Initramfs CPIO存档,可以运行以下命令:

$ cd ${PROJDIR}/rootfs
$ find . | cpio -o -H newc | gzip > ${PROJDIR}/initramfs.cpio.gz

以下命令可用于提取现有的GZIP压缩CPIO存档:

$ gunzip initramfs.cpio.gz
$ cpio -i -d -H newc -F initramfs_data.cpio --no-absolute-filenames

需要注意的是, cpio 手册页中的一些建议可能会破坏Initramfs存档。在创建Initramfs存档时,目录条目必须先于要放入这些目录的文件,因为Linux内核CPIO提取器不会在不存在的目录中创建文件。上述示例将生成正确的CPIO存档。

如果内核编译时支持初始RAM磁盘(Initrd),Linux内核可以使用外部压缩的CPIO存档代替RAM磁盘。内核将检测到Initrd的类型是Initramfs存档而不是文件系统映像,并在尝试查找init程序(默认位于 /sbin/init )之前将存档的内容提取到Rootfs中。此外部存档中的文件将覆盖内核映像文件中内部内置存档中的任何文件。

由于Initramfs使用Linux dentry和页面缓存来存储文件系统的文件和元数据内容,因此它比RAM磁盘能更有效地利用系统内存。因此,在所有新系统中,建议优先使用Initramfs机制而不是Initrd机制。

选择文件系统类型和布局

Linux中的文件系统类型和相关选项数量众多,一开始可能会让人感到困惑,不太清楚如何选择合适的文件系统。做出正确选择的关键是列出要存储在文件系统中的数据类型及其预期用途,例如构成设备运行软件的可执行二进制文件和库,以及保存可变配置信息的XML文件。然后将此列表简化为一组要求,使用与前面定义不同嵌入式文件系统特性(如在线可写、持久性、掉电可靠性和压缩性)相同(或相似)的术语。

接下来,在系统可用的文件系统类型和硬件存储介质中,找到最符合要求的组合。需要注意的是,开发人员通常会使用多种不同类型的文件系统,将不同的文件系统用于不同的系统功能。使用多个文件系统并不一定需要多个存储设备,Linux支持对几乎任何存储介质进行分区,同一存储介质上的不同分区可以包含不同类型的文件系统。

下面将针对不同类型的数据,介绍如何选择合适的文件系统。

应用程序、库和静态数据

嵌入式系统将应用程序的可执行代码、代码库和静态数据(如只读数据库、XML文件等)存储在某种持久存储中,以便系统在重启后能够重新加载软件。但实际上,对于这些元素,只需要一种特定类型的持久性:希望设备软件在重启后以最后一次软件更新(或出厂安装)时存储的形式加载到RAM中。除了专门为升级软件而进行的更改外,不需要保留对软件或RAM中文件所做的任何更改。

实现这种精细持久性要求的一种方法是使用只读文件系统,要么是系统在线时根本不支持写入文件系统的类型,要么是通过挂载选项将文件系统设置为只读。另一种方法是使用可写但非持久的文件系统,该文件系统从某些持久存储中“播种”,前面介绍的Initramfs机制就是实现此功能的绝佳方式。

如果文件系统内容较大而预期存储介质较小,可能需要使用压缩文件系统,这取决于具体的设备系统属性。

将文件系统保留在RAM中

前面讨论的Tmpfs文件系统,特别是通过Initramfs机制用作Rootfs时,可以很好地满足不可写持久性的要求。静态链接到内核映像中的压缩CPIO存档,或在系统启动时由引导加载程序加载的存档,提供了所需的持久性。CPIO是压缩的,节省了宝贵的存储空间。由于Tmpfs是非持久的,系统关闭时,对RAM中文件系统上的代码和静态数据所做的任何意外更改都将被丢弃,只有故意更新CPIO存档中的内核映像才能影响软件,这种更新通常仅作为软件升级的一部分进行。

此外,由于Tmpfs使用Linux页面和dentry缓存作为临时存储区域,并且Linux内核通常会将数据和可执行代码文件块保留在这些缓存中以供执行或访问,因此在嵌入式设备频繁使用系统软件和静态数据的常见情况下,使用Tmpfs可以节省内存。

使用只读持久存储

如果RAM供应不足,并且系统软件和静态数据文件很大但通常不会同时使用,使用Tmpfs可能不是一个可行的选择。在这种情况下,可以选择只读压缩文件系统,Cramfs和Squashfs是两个最合适的选择。如果Cramfs的限制对系统没有阻碍,建议使用Cramfs,否则建议使用Squashfs。这两种文件系统都是持久的、压缩的、只读文件系统,满足需求。

在这种情况下,系统软件升级将通过更新存储上的整个文件系统映像来执行,可以通过以下两种方式进行:
- 系统离线时更新 :可以从引导加载程序进行更新,或者通过替换可移动存储设备(如CompactFlash)来更新。
- 交替更新 :在不同的分区或存储设备上存储两个(或更多)此类文件系统,然后交替更新它们:在更新另一个分区或设备时从一个分区或设备运行系统,然后在下一次更新时切换角色。

使用在线可写持久存储

作为前面选择的替代方案(但认为不太理想),可以使用适合所选存储设备的其他持久文件系统之一。在正常系统使用期间,以只读模式(使用 ro 挂载选项)挂载文件系统,并在执行软件更新时动态切换到读写模式(使用 remount 挂载选项),更新结束后再切换回只读模式。

如果存储设备是带有NFTL层(如DoC)的CompactFlash或NAND闪存,建议使用Ext3文件系统,因为它既具有持久性又具有掉电可靠性。如果存储介质是没有NFTL的NAND闪存,并且存储空间不是问题(因为NAND闪存通常较大),建议使用YAFFS2文件系统,因为它除了具有持久性外,还提供掉电可靠性和损耗均衡支持。最后,如果不使用NFTL,对于NOR或小容量NAND闪存,可以使用JFFS2文件系统,因为它在持久性和掉电可靠性的基础上增加了压缩功能。

但由于更新过程的非原子性,使用此选项需要特别小心和注意,因此不建议使用此选项。

动态配置文件和数据

几乎每个嵌入式系统都有一些动态配置文件或数据,如设备的IP网络信息、日志文件或许可证信息。存储此类信息的文件系统必须具有持久性、在线可写性和掉电可靠性。由于预期会频繁更新文件信息,如果使用NAND或NOR闪存,硬件或软件层面必须支持一定程度的损耗均衡。在许多情况下,压缩也可能是一项要求。

强烈建议将此类信息存储在与其他所有信息分开的分区或存储设备中,因为应该为这些文件预留特定的存储空间。否则,可能会发现无法更新配置文件,例如因为软件更新填满了设备或分区,或者日志文件增长过大。

与前面的文件系统角色类型一样,最适合的文件系统取决于存储介质。如果存储设备是带有NFTL层(如DoC)的CompactFlash或NAND闪存,建议使用Ext3文件系统;如果存储介质是带有NFTL的NAND闪存且存储空间不是问题,建议使用YAFFS2文件系统;最后,如果不使用NFTL,对于NOR或小容量NAND闪存,使用JFFS2文件系统可以在持久性和掉电可靠性的基础上增加压缩功能。

临时文件

许多嵌入式系统使用各种类型的临时文件,这些文件仅在当前系统运行期间有意义,系统关闭时不需要保留。一个常见的例子是临时配置文件,它们存储在易失性存储中,其更改仅影响系统行为直到下一次重启,除非给出特定的保存命令(如 copy running-config startup-config )以保留跨重启的配置更改。

存储此类易失性信息的最佳位置是Tmpfs文件系统(这次不是作为Initramfs)。需要注意的是,通过 size 挂载选项限制用于此目的的Tmpfs文件系统的最大大小非常重要,以免因某个恶意任务创建大量临时文件而耗尽系统RAM。

布局示例

对于带有CompactFlash存储设备的嵌入式系统,一个简单但典型的布局如下:
- 包含系统软件的根文件系统放置在只读、压缩的Squashfs文件系统中。
- 配置文件存储在单独分区的读写Ext3文件系统中。
- 临时文件仅使用Tmpfs存储在RAM中。

处理软件升级

在设计文件系统布局以及嵌入式设备时,需要考虑的最关键任务之一是如何在现场进行软件升级。软件升级的方式取决于所选择的文件系统类型和布局。例如,对于使用只读压缩文件系统(如Cramfs或Squashfs)的系统,可以在系统离线时更新整个文件系统映像,或者通过交替更新不同分区或存储设备上的文件系统来实现。而对于使用在线可写持久存储的系统,需要在正常使用时以只读模式挂载文件系统,在更新时切换到读写模式,更新结束后再切换回只读模式,但这种方式需要特别注意更新过程的非原子性问题。

总之,合理选择文件系统类型和布局,并考虑好软件升级的方式,对于嵌入式系统的稳定运行和可维护性至关重要。在实际开发中,需要根据具体的需求和设备特性进行综合考虑和权衡。

嵌入式系统文件系统配置与管理指南

软件升级策略分析

在嵌入式系统中,软件升级是一个复杂且关键的过程,不同的文件系统选择会导致不同的升级策略。下面将详细分析不同场景下的软件升级方法及其优缺点。

只读压缩文件系统升级

对于采用Cramfs或Squashfs等只读压缩文件系统的嵌入式系统,升级方案主要有离线更新和交替更新两种。

离线更新流程
1. 准备新的文件系统映像:在主机上生成包含最新软件的文件系统映像。
2. 停止系统运行:将嵌入式设备关机或进入维护模式,确保文件系统处于稳定状态。
3. 更新存储介质:可以通过引导加载程序将新的映像写入存储设备,或者直接更换可移动存储设备(如CompactFlash)。
4. 重启系统:设备重新启动后,将使用新的文件系统。

这种方法的优点是操作简单,升级过程稳定。缺点是需要设备离线,可能会影响系统的正常运行时间。

交替更新流程
1. 划分存储区域:在存储设备上划分至少两个分区,分别存储不同版本的文件系统。
2. 选择运行分区:系统启动时,从一个分区加载文件系统并运行。
3. 更新备用分区:在系统运行期间,将新的文件系统映像写入另一个备用分区。
4. 切换分区运行:下次系统启动时,切换到更新后的分区加载文件系统。

交替更新的优点是可以实现无缝升级,减少系统停机时间。缺点是需要额外的存储分区,增加了存储成本。

在线可写持久存储升级

采用在线可写持久存储(如Ext3、YAFFS2、JFFS2)的系统在升级时,需要注意更新过程的非原子性问题。以下是升级的详细步骤。
1. 备份重要数据:在升级前,将系统中的重要配置文件和数据进行备份,防止数据丢失。
2. 切换到读写模式:使用 remount 挂载选项将文件系统从只读模式切换到读写模式。

$ mount -o remount,rw /dev/sda1 /mnt
  1. 更新文件系统:将新的软件文件复制到文件系统中,替换旧的文件。
  2. 检查更新完整性:验证更新后的文件系统是否正常工作,确保所有文件都正确更新。
  3. 切换回只读模式:更新完成后,将文件系统重新挂载为只读模式。
$ mount -o remount,ro /dev/sda1 /mnt
  1. 恢复备份数据:将之前备份的重要数据恢复到文件系统中。

这种升级方式的优点是可以在系统运行时进行更新,减少停机时间。但由于更新过程的非原子性,可能会出现部分更新失败的情况,导致系统不稳定。

软件升级过程中的风险与应对措施

在软件升级过程中,可能会遇到各种风险,以下是一些常见的风险及相应的应对措施。

风险 描述 应对措施
更新失败 由于网络问题、存储设备故障等原因,导致文件系统更新不完整 在更新前进行充分的检查,如网络连接测试、存储设备健康检查;采用增量更新方式,减少更新数据量;设置更新回滚机制,更新失败时可以恢复到旧版本
数据丢失 更新过程中可能会意外删除或覆盖重要的数据 在更新前备份重要数据;使用文件系统的日志功能,记录文件的更改,以便在出现问题时进行恢复
系统崩溃 更新后的文件系统与硬件或其他软件组件不兼容,导致系统无法正常启动 在开发阶段进行充分的兼容性测试;在升级前进行小规模的试点升级,确保升级的稳定性;提供紧急恢复机制,如通过引导加载程序恢复到旧版本
不同存储介质与文件系统的适配关系

不同的存储介质具有不同的特性,因此需要选择合适的文件系统来充分发挥其性能。以下是常见存储介质与推荐文件系统的适配关系。

存储介质 特性 推荐文件系统
CompactFlash 读写速度较快,容量适中 带有NFTL层时,推荐使用Ext3;需要压缩功能时,可考虑JFFS2
NAND闪存 容量大,成本低,但读写寿命有限 带有NFTL层时,推荐使用Ext3或YAFFS2;不使用NFTL且需要压缩时,使用JFFS2
NOR闪存 读写速度快,可直接执行代码,但容量相对较小 JFFS2,可提供压缩和掉电可靠性
嵌入式系统文件系统管理的最佳实践

为了确保嵌入式系统文件系统的稳定运行和可维护性,以下是一些最佳实践建议。

合理规划文件系统布局

根据系统的功能和数据类型,将不同的文件存储在不同的分区或文件系统中。例如,将系统软件和静态数据存储在只读文件系统中,将动态配置文件和数据存储在可写的持久文件系统中,将临时文件存储在Tmpfs中。这样可以提高系统的安全性和可维护性。

定期备份重要数据

定期对系统中的重要数据进行备份,包括配置文件、日志文件、数据库等。备份数据可以存储在外部存储设备或远程服务器上,以防止数据丢失。

监控文件系统状态

使用系统监控工具(如df、du等)定期检查文件系统的使用情况,包括磁盘空间、文件数量等。及时清理不必要的文件,避免文件系统满导致系统故障。

进行兼容性测试

在进行软件升级或更换文件系统之前,进行充分的兼容性测试。测试内容包括文件系统与硬件的兼容性、与其他软件组件的兼容性等。确保升级或更换后系统能够正常运行。

总结

嵌入式系统文件系统的配置和管理是一个复杂而关键的过程,涉及到文件系统类型的选择、布局的设计、软件升级的策略等多个方面。在实际开发中,需要根据系统的具体需求和设备特性,综合考虑各种因素,选择最合适的文件系统和管理方法。同时,要注意软件升级过程中的风险,采取相应的应对措施,确保系统的稳定运行和可维护性。通过遵循最佳实践建议,可以提高嵌入式系统的性能和可靠性,为用户提供更好的使用体验。

流程图:软件升级流程
graph TD
    A[开始] --> B{选择文件系统类型}
    B -->|只读压缩文件系统| C{选择升级方式}
    C -->|离线更新| D[准备新映像]
    D --> E[停止系统运行]
    E --> F[更新存储介质]
    F --> G[重启系统]
    C -->|交替更新| H[选择运行分区]
    H --> I[更新备用分区]
    I --> J[切换分区运行]
    B -->|在线可写持久存储| K[备份重要数据]
    K --> L[切换到读写模式]
    L --> M[更新文件系统]
    M --> N[检查更新完整性]
    N --> O[切换回只读模式]
    O --> P[恢复备份数据]
    G --> Q[结束]
    J --> Q
    P --> Q

以上就是关于嵌入式系统文件系统配置与管理的详细介绍,希望对大家在实际开发中有所帮助。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐