相关推荐recommended
【深入浅出imx8企业级开发实战 | 04】嵌入式Linux设备掉电数据容错研究
作者:mmseoamin日期:2023-12-02

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

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

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

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

【01】imx8qxp yocto工程构建指南

【02】Yocto工程repo源码gitee加速配置方法

【03】imx8qxp一键独立编译指南

【04】嵌入式Linux设备掉电数据容错研究

文章目录

  • 《深入浅出i.MX8企业级开发实战》快速导航
  • 1. 概述
  • 2. 存储介质
  • 3. ubifs的writeback支持
  • 4. Linux文件系统中的缓存
    • 4.1 Page Cache
    • 4.2 Dirty Page
    • 4.3 Linux系统中查看Page Cache和Dirty Page
    • 4.4 Dirty Page脏页WriteBack参数
    • 4.5 文件读写流程
      • 4.5.1 读文件
      • 4.5.2 写文件
      • 5. 写入缓存测试
      • 6. 优化方法总结
        • 6.1 系统级设计
        • 6.2 调整Linux内核WriteBack参数
        • 6.3 sync方式
          • 6.3.1 修改挂载方式为同步挂载
          • 6.3.2 重要数据写入后执行fsync操作
          • 6.3.3 文件打开时,配置O_SYNC标志
          • 6.3.4 修改文件inodes为同步模式
          • 6.4 其它实现机制
            • 6.4.1 硬件缓冲
            • 6.4.2 软件缓冲
            • 6.4.3 Copy-on-Write
            • 6.4.4 Copy

              写在开始:

              • 博客简介:专注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文件系统中的缓存

                【深入浅出imx8企业级开发实战 | 04】嵌入式Linux设备掉电数据容错研究,在这里插入图片描述,第2张

                缓存是用来减少高速设备访问低速设备所需平均时间的组件,文件读写涉及到计算机内存和磁盘,内存操作速度远远大于磁盘,如果每次调用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方式

                                  【深入浅出imx8企业级开发实战 | 04】嵌入式Linux设备掉电数据容错研究,68afcbd2664cd60d35e8ab8b74967880.png,第3张

                                  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

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

                                    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

                                      — 博主热门专栏推荐—

                                      • Python零基础快速入门系列
                                      • 深入浅出i.MX8企业级开发实战系列

                                      • MQTT从入门到提高系列
                                      • 物体检测快速入门系列
                                      • 自动驾驶模拟器AirSim快速入门
                                      • 安全利器SELinux入门系列
                                      • Python数据科学快速入门系列

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