首页 > 基础资料 博客日记

【深入浅出imx8企业级开发实战 | 04】嵌入式Linux设备掉电数据容错研究

2024-02-26 19:00:05基础资料围观371

这篇文章介绍了【深入浅出imx8企业级开发实战 | 04】嵌入式Linux设备掉电数据容错研究,分享给大家做个参考,收藏Java资料网收获更多编程知识

这是机器未来的第58篇文章

原文首发地址:https://robotsfutures.blog.csdn.net/article/details/126924015

《深入浅出i.MX8企业级开发实战》快速导航

【01】imx8qxp yocto工程构建指南
【02】Yocto工程repo源码gitee加速配置方法
【03】imx8qxp一键独立编译指南
【04】嵌入式Linux设备掉电数据容错研究


写在开始:

  • 博客简介:专注AIoT领域,追逐未来时代的脉搏,记录路途中的技术成长!
  • 博主社区:AIoT机器智能, 欢迎加入!
  • 专栏简介:imx8qxp小白从拿到板子到完成项目的过程记录
  • 面向人群:嵌入式工程师

1. 概述

本文针对嵌入式设备掉电应用数据丢失的问题,研究了掉电数据容错的一些措施。

2. 存储介质

如果是自有平台上开发的话,存储介质很好确认,如果是基于第三方平台开发应用,例如AG35 Open版这样的使用场景,如何判断存储介质的类型呢?

  • 查看分区文件系统
/usrdata # df -T
Filesystem           Type       1K-blocks      Used Available Use% Mounted on
/dev/root            squashfs       41600     41600         0 100% /
tmpfs                tmpfs          76008        28     75980   0% /run
tmpfs                tmpfs          76008      1292     74716   2% /var/volatile
tmpfs                tmpfs             64         4        60   6% /dev
tmpfs                tmpfs          76008      1292     74716   2% /var/lib
/dev/ubiblock1_0     squashfs       30720     30720         0 100% /firmware
/dev/ubi2_0          ubifs           7912      3564      4348  45% /systemrw
/dev/ubi2_0          ubifs           7912      3564      4348  45% /data
/dev/ubi2_0          ubifs           7912      3564      4348  45% /etc
/dev/ubiblock3_0     squashfs         128       128         0 100% /oemapp
/dev/ubi4_0          ubifs         139112      1592    137520   1% /usrdata
/dev/ubi5_0          ubifs           9516        40      9476   0% /persist

这里需要验证/usrdata所在分区在什么存储介质上,通过df -T可以看到其文件系统为ubifs。ubifs应用在裸flash上,因此可以判断存储flash为nandflash。

3. ubifs的writeback支持

ubifs **write-back 支持:**回写(写入到page cache即认为写入完成),同JFFS2的write-through(透写:立即写入内存)相比可以显著的提高文件系统的吞吐量。(**译者注:**write-back即回写,写入到page cache,就认为数据写入完成,而write-through即透写,只有将数据写入存储设备才认为写入成功;write-back相对于write-through,无需磁盘IO,因此具有更好的系统IO,但是数据一致性是得不到保证的)

write-back支持,需要应用程序注意及时同步重要的文件。否则掉电会导致这些文件的损坏和消失,掉电对于嵌入式系统而言是很常见的。

4. Linux文件系统中的缓存

缓存是用来减少高速设备访问低速设备所需平均时间的组件,文件读写涉及到计算机内存和磁盘,内存操作速度远远大于磁盘,如果每次调用read,write都去直接操作磁盘,一方面速度会被限制,一方面也会降低磁盘使用寿命,因此不管是对磁盘的读操作还是写操作,操作系统都会将数据缓存起来。

4.1 Page Cache

页缓存(Page Cache)是位于内存和文件之间的缓冲区,它实际上也是一块内存区域,所有的文件IO(包括网络文件)都是直接和页缓存交互,操作系统通过一系列的数据结构,比如inode, address_space, struct page,实现将一个文件映射到页的级别,这些具体数据结构及之间的关系我们暂且不讨论,只需知道页缓存的存在以及它在文件IO中扮演着重要角色,很大一部分程度上,文件读写的优化就是对页缓存使用的优化

4.2 Dirty Page

页缓存对应文件中的一块区域,如果页缓存和对应的文件区域内容不一致,则该页缓存叫做脏页(Dirty Page)。对页缓存进行修改或者新建页缓存,只要没有刷磁盘,都会产生脏页。

4.3 Linux系统中查看Page Cache和Dirty Page

LInux中有两种方式查看Page Cache

  • free
/usrdata/QuecOpen # free 
             total       used       free     shared    buffers     cached
Mem:        152656     148696       3960       1352      41992      28580
-/+ buffers/cache:      78124      74532
Swap:            0          0          0
  • cat /proc/meminfo
/usrdata/QuecOpen # cat /proc/meminfo
MemTotal:         152656 kB
MemFree:            3188 kB
MemAvailable:      77660 kB
Buffers:           41992 kB
Cached:            28584 kB
SwapCached:            0 kB
Active:            66176 kB
Inactive:          34832 kB
Active(anon):      31644 kB
Inactive(anon):      136 kB
Active(file):      34532 kB
Inactive(file):    34696 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:         30468 kB
Mapped:            10196 kB
Shmem:              1356 kB
Slab:              25820 kB
SReclaimable:      11076 kB
SUnreclaim:        14744 kB
KernelStack:        2936 kB
PageTables:         2116 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:       76328 kB
Committed_AS:    1660112 kB
VmallocTotal:     781312 kB
VmallocUsed:       20520 kB
VmallocChunk:     729084 kB

关注Cached和Dirty即可。

4.4 Dirty Page脏页WriteBack参数

Linux有几个内核参数可以用来调整write-back

/usrdata/QuecOpen # sysctl -a 2>/dev/null | grep dirty
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 20
vm.dirty_writeback_centisecs = 500
  • vm.dirty_background_ratio

    内存可以填充脏页的百分比(dirty数据与全部内存的最大百分比),当脏页总大小达到这个比例后,系统后台进程就会开始将脏页刷磁盘(vm.dirty_background_bytes类似,只不过是通过字节数来设置)

  • vm.dirty_ratio

    绝对的脏数据限制,内存里的脏数据百分比不能超过这个值。如果脏数据超过这个数量,新的IO请求将会被阻挡,直到脏数据被写进磁盘

  • vm.dirty_writeback_centisecs

    指定多长时间做一次脏数据写回操作,单位为百分之一秒,linux周期性write-back线程写出dirty数据的周期,这个机制可以确保所有的脏数据在某个时间点都可以写入介质

  • vm.dirty_expire_centisecs

    指定脏数据能存活的时间,单位为百分之一秒,比如这里设置为30秒,在操作系统进行写回操作时,如果脏数据在内存中超过30秒时,就会被写回磁盘.

这些参数可以通过 sudo sysctl -w vm.dirty_background_ratio=5 这样的命令来修改,需要root权限,也可以在root用户下执行如下命令修改

echo 5 > /proc/sys/vm/dirty_background_ratio

4.5 文件读写流程

在有了页缓存和脏页的概念后,我们再来看文件的读写流程

4.5.1 读文件

  • 1.用户发起read操作
  • 2.操作系统查找页缓存
    • a.若未命中,则产生缺页异常,然后创建页缓存,并从磁盘读取相应页填充页缓存
    • b.若命中,则直接从页缓存返回要读取的内容
  • 3.用户read调用完成

4.5.2 写文件

  • 1.用户发起write操作
  • 2.操作系统查找页缓存
    • a.若未命中,则产生缺页异常,然后创建页缓存,将用户传入的内容写入页缓存
    • b.若命中,则直接将用户传入的内容写入页缓存
  • 3.用户write调用完成
  • 4.页被修改后成为脏页,操作系统有两种机制将脏页写回磁盘
  • 5.用户手动调用fsync()
  • 6.由pdflush进程定时将脏页写回磁盘

5. 写入缓存测试

  • 查看WriteBack回写周期
/usrdata/QuecOpen # sysctl -a 2>/dev/null | grep dirty
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 20
vm.dirty_writeback_centisecs = 500
  • 修改文件后立即掉电
/usrdata/QuecOpen # cat /dev/null > ag35.config 
# 此时在3s内断电
/usrdata/QuecOpen # client_loop: send disconnect: Broken pipe
zhoushimin@zsm:~$ ./login_fouter.sh 
spawn ssh root@198.18.1.1
root@198.18.1.1's password: 
root@frouter:~#  mkdir -p /home/root/.android/;echo 0x2c7c > /home/root/.android/adb_usb.ini;export PATH=$PATH:/mnt/tmp/adb
root@frouter:~# 
root@frouter:~# # adb shell
root@frouter:~# ls       
app      init.sh  ko
root@frouter:~# adb shell
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
/ # ls
WEBSERVER   cache       firmware    mnt         run         sys         tmp
bin         data        home        oemapp      sbin        system      usr
boot        dev         lib         persist     sdcard      systemrw    usrdata
build.prop  etc         media       proc        share       target      var
/ # cd /usrdata/QuecOpen/
/usrdata/QuecOpen # ls
ag35.config     app.config      ecm_call        factory         ota_result.dat
ag35_app.md5    app_monitor.sh  ecm_call-0.csv  log_index.dat   upgrade
/usrdata/QuecOpen # ls -la
total 1592
drwxrwxr-x    3 root     root           864 Oct  9 05:40 .
drwxr-xr-x    3 root     root           376 Sep  1 09:19 ..
-rw-r--r--    1 root     root            10 Oct  9 05:40 ag35.config               # 发现文件内容未被清空
-rw-r--r--    1 root     root             0 Oct  9 05:40 ag35_app.md5

执行写入为空后,快速断电,还是很容易复现文件信息未生效的现象。

6. 优化方法总结

6.1 系统级设计

在嵌入式系统中为了保证系统的可靠性,至少要保证内核、rootfs数据的可靠性,保证系统可以运行起来,可以根据数据的类型分为系统和数据,为了考虑数据掉电容错,可以将系统分区设计为只读分区和可读写分区,例如squashfs+overlayfs/ext4。

详情参见:https://blog.csdn.net/toradexsh/article/details/109737842

6.2 调整Linux内核WriteBack参数

详见4.4 Dirty Page脏页WriteBack参数

6.3 sync方式

6.3.1 修改挂载方式为同步挂载

如果你想切换到同步模式,在mount文件系统是使用 -o 同步选项。然而,要注意文件系统性能将会下降。此外,要记住UBIFS mount为同步模式仍然不如JFFS2提供更多的保证

6.3.2 重要数据写入后执行fsync操作

  • 一定要时刻记住运行fsync在你修改重要数据后;当然,没有必要sync临时的文件;要先考虑文件数据的重要性,不要执行没必要的fsync, 因为fsync操作会降低性能
  • 如果你想更精确些,那么就使用fdatasync, 仅仅修改的数据被flushed, 而inode meta-data变化不会被flush(比如mtime或者permissions)

6.3.3 文件打开时,配置O_SYNC标志

你也可以在open()调用时是一年个O_SYNC标志;这使得这个文件所得修改的data(不包括meta-data)都会在write()操作返回前写入media;通常来说,最好使用fsync(), 因为O_SYNC使得每个写都是同步的,而fsync允许多个累积的写

对于一些频次低,数据小,重要的文件建议采用这种方案。

6.3.4 修改文件inodes为同步模式

可以使一定数目inodes为同步模式,通过设置inode的sync标志,一旦应用程序对这个文件执行了写操作,使系统立刻把修改的结果写到磁盘

在shell中执行chattr +S

chattr +S /usrdata/QuecOpen/ag35.config

在C程序中,则可以在ioctl命令使用FS_IOC_SETFLAGS配置同步 标志FS_SYNC_FL

int attr;
fd = open("pathname", ...);

ioctl(fd, FS_IOC_GETFLAGS, &attr);  /* Place current flags
                                       in aqattraq */
attr |= FS_SYNC_FL;              	
ioctl(fd, FS_IOC_SETFLAGS, &attr);  /* Update flags for inode
                                       referred to by aqfdaq */

注意, mkfs.ubifs工具会检查原始的FS树,如果文件在原始文件树是同步的,那么在UBIFS image也会是同步的

要强调的是,上面的方法对于任何文件系统都是可行的,包括JFFS2

fsync()可能包括目录 - 它同步目录inode的meta-data。 “sync” flag也可以用在目录上,使得目录inode变成同步的。但是"sync" flag是可继承的, 意味这这个目录下的所有新节点都有这个标志。 这个目录的新文件和新子目录也就变成同步的,子目录的child也是如此。 这个功能是非常有用的,如果想创建一个需要整个目录树都同步的目录。

fdatasync()调用对 UBIFS的目录是不起作用的, 因为UBIFS对目录项的操作都是同步的,当然不是所有文件系统都如此。类似的, “dirsync” inode 标志对UBIFS没有作用

以上提到的功能都是作用于文件描述符,而不是文件stream(FILE *)。 同步一个stream,你应该通过libc的fileno()取得文件描述符,首先使用fflush()来flush stream ,然后调用fsync或者fdatasync. 你也可以使用其他的同步方法,但是记得在同步文件前要先flush stream. fflush() 和sync(), fsync,fdatasync的区别在于前者仅仅同步libc-level的buffer,而后者则是kernel-level buffers。

6.4 其它实现机制

6.4.1 硬件缓冲

增加超级电容或电池,掉电时做退出处理。这种成本相对较低,但是仍然需要考虑超级电容或电池的使用寿命问题,是否在产品的生命周期之内。

6.4.2 软件缓冲

如果设备作为从设备挂载在主机上,主机在断电前,先通知从机即将断电,做一个断电通知,让从机正常做退出处理。

6.4.3 Copy-on-Write

COW(copy-on-write 的简称),是一种计算机设计领域的优化策略,其核心思想是:如果有多个调用者(callers)同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源(摘自维基百科**)。**

6.4.4 Copy

对于重要文件,每次操作前,先备份副本并同步后再操作,如果正式版本出错,还可以从备份版本恢复。

参考文献:

  • https://blog.csdn.net/weixin_31972679/article/details/111956256
  • https://blog.csdn.net/shichaog/article/details/45932339
  • https://www.cnblogs.com/youngerchina/p/5624559.html
  • http://www.linux-mtd.infradead.org/doc/ubifs.html
  • https://blog.csdn.net/rjszcb/article/details/118057620
  • https://www.cnblogs.com/byronsh/p/norflash_nanflash_name_explaination.html
  • https://blog.csdn.net/weixin_42653531/article/details/90745042
  • https://www.cnblogs.com/zengkefu/p/5683590.html
  • https://blog.csdn.net/weixin_42444974/article/details/116872740

— 博主热门专栏推荐—


文章来源:https://blog.csdn.net/RobotFutures/article/details/127269708
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云