Linux 只读文件系统

Wednesday, March 14, 2018

简介

文件系统对于嵌入式系统,正如记忆对于大脑,嵌入式系统失去了文件系统,这往往是严重的问题,轻则导致应用无法启动,数据丢失,严重的情况可能是整个系统启动失败。Windows 电脑的蓝屏、手机变砖,这其中很大比例是由于重要系统文件丢失、损坏所致。如何保护嵌入式文件系统的安全,这是系统开发人员打造一款稳定、可靠产品时应该考虑的事情。

损坏文件系统,除了外部因素如高压、高温、强磁场干扰存储器外,写文件系统时的突然掉电往往会造成文件系统结构本身的损坏,例如分区信息。在 Linux 嵌入式系统常见的挂载 VFS 失败就是由于rootfs 受损所致,而这之前的 U-Boot Linux kernel 往往任可以正常工作。本文针对此类情况,将介绍如何在 iMX6 平台上,如何使用只读文件系统,并将用户应用和数据保存在独立的分区,从而避免对 rootfs 的写操作,以及利用 Toradex Easy Installer 完成分区和系统安装操作。

成只读文件系统

Yocto/OpenEmbedded 构建框架的一个特点是,系统软件会根据所构建的目标镜像的要求自动调整编译软件的功能。例如在 IMAGE_FEATURES 中甚至 read-only-rootfs,那么 BSP 中包含的软件将不会往 Flash 上写入文件,包括系统日志。

local.conf 配置

EXTRA_IMAGE_FEATURES = "debug-tweaks package-management read-only-rootfs"
DISTRO_EXTRA_RDEPENDS_remove = " angstrom-libc-fixup-hack"

设置分区挂载

修改用户分区挂载目录

layers/meta-toradex-demos/recipes-core/base-files/base-files/fstab
/dev/root            /                    auto       ro,noatime              1  1
proc                 /proc                proc       defaults              0  0
devpts               /dev/pts             devpts     mode=0620,gid=5       0  0
usbdevfs             /proc/bus/usb        usbdevfs   noauto                0  0
tmpfs                /run                 tmpfs      mode=0755,nodev,nosuid,strictatime 0  0
tmpfs                /var/volatile        tmpfs      defaults              0  0

# uncomment this if your device has a SD/MMC/Transflash slot
#/dev/mmcblk0p1       /media/card          auto       defaults,sync,noauto  0  0
/dev/mmcblk0p3       /mnt          ext4       defaults,sync,noauto  0  0

将系统根目录设置为只读状态,并将独立的用户分区挂载到 /mnt 目录,该目录任具有读写权限。用户应用可以在该分区下保存用户配置文件、应用日志,甚至升级用户应用本身。

设置应用自启动脚本

由于文件系统需要配置为只读属性,我们需要在 Yocto/OpenEmbedded 构建时直接包含应用开机启动脚本。目前 Toradex Linux BSP 已经采用 systemd 启动管理,用户需要添加对于的 systemd service 文件。为了方便演示,我们直接在 layers/meta-toradex-bsp-common/recipes-core/ 目录下创建用户自己的recipe 文件,如 user-demo,当然你也可以在 Yocto/OpenEmbedded 根目录中添加自己的 layer,将所需的 recipe 都包含进来,具体方法请参考这里

tdx_cn_ben@LinuxDevSH1:~/Toradex/OE/v2.7/layers/meta-toradex-bsp-common/recipes-core/user-demo$ tree -L 2
.
├── files
│   └── user-demo.service
└── user-demo.bb

user-demo.bb

SUMMARY = "Add user demo"
DESCRIPTION = "create folder within home and install auto-run service script"
LICENSE = "CLOSED"
PR = "r3"

SRC_URI = " \
file://user-demo.service \
"

do_install () {
install -m 0644 ${WORKDIR}/user-demo.service ${D}${systemd_unitdir}/system

}

NATIVE_SYSTEMD_SUPPORT = "1"
SYSTEMD_PACKAGES = "${PN}"
SYSTEMD_SERVICE_${PN} = "user-demo.service"

inherit allarch systemd

user-demo.service

[Unit]
Description=launch user's demo on dedicated partition
ConditionFileIsExecutable=/mnt/helloworld
StartLimitIntervalSec=200
StartLimitBurst=5
After=multi-user.target

[Service]
Type=simple
ExecStart=/mnt/helloworld
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

启动脚本中使用 ConditionFileIsExecutable 来判断用户分区是否成功挂载并存在可执行文件,等待分区文件准备完成后才启动,同时设置 Restart ,当启动失败后会再次尝试。

最后执行命令,生成用于 Toradex Easy Installer 的安装文件

bitbake console-tdx-image

将下面文件复制到 SD 或者 U盘中

├── Colibri-iMX6_Console-Image.bootfs.tar.xz
├── Colibri-iMX6_Console-Image.rootfs.tar.xz
├── image.json
├── prepare.sh
├── slides_vga
├── SPL
├── toradexlinux.png
├── u-boot.imx
├── userapp.tar.xz
└── wrapup.sh

修改 image.json 配置文件

Toradex Easy Installer 中的 image.json  文件可用于配置 iMX 6 模块上 Flash 的分区情况,并将文件写入对应的分区中。我们需要在 Flash 创建分区,存放用户文件 userapp.tar.xz,并去掉 rootfs 分区 want_maximised 属性。

{
"want_maximised": false,
"content": {
"mkfs_options": "-E nodiscard",
"filesystem_type": "ext4",
"uncompressed_size": 140.03515625,
"filename": "Colibri-iMX6_Console-Image.rootfs.tar.xz",
"label": "RFS"
},
"partition_size_nominal": 512
},
{
"want_maximised": true,
"content": {
"mkfs_options": "-E nodiscard",
"filesystem_type": "ext4",
"uncompressed_size": 7.01,
"filename": "userapp.tar.xz",
"label": "UserData"
},
"partition_size_nominal": 512

userapp.tar.xz 压缩包中包含了用户文件,如 helloworld taq.mp4,直接将其压缩成 xz 格式即可。

用户应用

由于用户应用存放在独立的分区上,因此并不需要将其集成到 Yocto/OpenEmbedded,借助  Toradex Easy Installer 可以直接将程序、数据等写入分区。以下面应用为例,应用将打印“Hello world!”,并在用户分区上写入数据文件。该应用由上面的 user-demo.service 脚本在开机时启动。

int main(int argc, char *argv[]){
int file_hd;
unsigned char data[16]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};

file_hd = open ("/mnt/log-file", O_CREAT |O_WRONLY | O_TRUNC);
if(file_hd==-1)
{
printf("file create failed");
return -1;
}
write (file_hd, (unsigned char *)data, 16);
close(file_hd);

printf("Hello world!\n");
return 0;
}

测试文件系统

查看挂载情况

root@colibri-imx6:/etc/udev/rules.d# mount -l
/dev/mmcblk0p2 on / type ext4 (ro,noatime,data=ordered) [RFS]
……
/dev/mmcblk0p3 on /media/mmcblk0p3 type ext4 (rw,relatime,data=ordered) [UserData]

在只读文件系统上无法创建目录

root@colibri-imx6:~# pwd
/home/root
root@colibri-imx6:~# mkdir testfolder
mkdir: can't create directory 'testfolder': Read-only file system

UserData 分区下的用户文件

root@colibri-imx6:/media/mmcblk0p3# ls
helloworld lost+found taq.mp4

应用启动情况

root@colibri-imx6:~# journalctl -u user-demo.service
-- Logs begin at Wed 2018-03-07 04:48:18 UTC, end at Mon 2018-03-12 08:30:56 UTC. --
Mar 12 08:30:51 colibri-imx6 systemd[1]: Started launch user's demo on dedicated partition.
Mar 12 08:30:51 colibri-imx6 helloworld[522]: Hello world!

root@colibri-imx6:~# hexdump -b /mnt/log-file
0000000 000 001 002 003 004 005 006 007 010 011 012 013 014 015 016 017
0000010

总结

通过将 rootfs 设置为只读模式,用户的写操作只缩小到一个单独分区,可以降低由于 rootfs 损坏导致系统启动失败的发生。只读模式的 rootfs 也会带来其他影响,有些文件是在系统运行时必须创建或者更新的,如密码、random seedSSH Keys、网络配置参数等。这些文件的缺失使得系统无法保存之前的配置,每次使用时都需重新建立。以上只是一个简单的说明,如何使用只读文件系统提高系统稳定性。用户也可以将应用直接集成到 BSP 中,当用户分区上的文件损坏或者丢失时,可以将其用作备份恢复。甚至采用额外的存储介质,实现系统恢复、A/B 分区、OTA 等其他功能。

Author 胡珊逢,FAE,韬睿(上海)

Leave a comment

Your email ID will be kept confidential. Required fields are marked *


Please enter the letters as they are shown in the image above. Letters are not case-sensitive.

* Your comment will be reviewed and then added. Thank you.