【问题标题】:How to avoid damaging SD card for large writes?如何避免因大量写入而损坏 SD 卡?
【发布时间】:2014-09-15 21:04:28
【问题描述】:

好的,首先介绍一些背景知识来帮助我弄清楚我的问题:

我正在开发一种设备,该设备从传感器收集某些数据并使用 GSM 调制解调器将它们发布到服务器。由于 GSM 连接不是 100% 可靠的,它会包含一个日志机制,可以将未发送的数据写入 SD 卡。

我们正在使用Chan's FatFs 模块为我们提供文件系统,因为我们希望日志可以在 PC 上读取。

现在我一直在测试 FAT 系统的边界条件,即尝试完全填满卡。

在第一次运行中,我打开了文件并将代码设置为继续写入字符串,直到驱动器已满。该程序将在每次写入后同步。

我让代码在一夜之间运行。

第二天,我检查了 SD 卡。我发现该文件只有 150 MB 大小。大约有 120 万行写入其中。该卡仍然可以读取,但不能写入或格式化。

下一次我尝试了相同类型的测试,但这次我使用f_lseek() 函数将文件预分配为1GB。然后它将写入该文件,直到达到该限制。这次数据将在 50 次写入后同步。然后它将关闭该文件并打开另一个文件以执行相同的操作。

正如你可以猜到的那样,另一张勇敢的小卡片在那一天失去了理智。

所以这些是我想要帮助的:

  1. 如何防止在写入大量数据时损坏卡?
  2. 长时间打开文件是否会产生负面影响?

由于完整的代码可能太长,这里是写作的主要部分

for(file_count=3;file_count>=0;--file_count){

    ax_log_msg(E_LOG_INFO,"===================================");

    ax_log_msg(E_LOG_INFO,file_names[file_count]);


    f_open(&file_ptr,file_names[file_count],FA_WRITE|FA_OPEN_ALWAYS);

    if(result!=FR_OK){

        ax_log_msg(E_LOG_INFO,"\n\rf_open Failed\n\rResult code");
        ax_log_msg(E_LOG_INFO,FRESULT_S[result]);

        continue;

    }

    ax_log_msg(E_LOG_INFO,"\n\rf_open Sucessfull");

    result=f_lseek(&file_ptr,FILE_SIZE_LIMIT_1GB);

    if(result!=FR_OK){

        ax_log_msg(E_LOG_INFO,"\n\rf_lseek Failed for preallocation\n\rResult code");
        ax_log_msg(E_LOG_INFO,FRESULT_S[result]);

        f_close(&file_ptr);

        continue;

    }

    ax_log_msg(E_LOG_INFO,"\n\rf_lseek Sucessfull for preallocation");

    f_lseek(&file_ptr,0);

    bytes_to_write=sizeof(messages[file_count]);

    write_count=0;

    while( (f_tell(&file_ptr) < FILE_SIZE_LIMIT_1GB )){

        result=f_write(&file_ptr,messages[file_count],bytes_to_write,&bytes_written);

        if(result==FR_OK){
            ++write_count;

            if(write_count%50==0){

                f_sync(&file_ptr);
            }

        }else{

            ax_log_msg(E_LOG_INFO,"\n\rWrite failed\n\rFRESULT=");
            ax_log_msg(E_LOG_INFO,FRESULT_S[result]);

            break;

        }

    }

    f_close(&file_ptr);


}

注意:

  1. ax_log_msg() 是设备固件的一部分,可在控制台上打印。
  2. FRESULT_S[result] 用于将枚举结果代码转换为字符串。

如果有任何数据丢失,请务必提及。

谢谢

【问题讨论】:

  • 您自己实现了 HAL 功能吗? (disk_read, disk_write?) 我猜你还没在 ebay 上买过 SD 卡? :)
  • 我将 HAL 函数作为 MSP430 评估板源代码的一部分。但它们确实有效。
  • eBay 问题实际上非常重要。您是否从信誉良好的公司购买了正版卡?仿制卡会死得很快。一张好的 4GB 卡应该可以满足许多 GB 的写入耐久性。

标签: c embedded sd-card fat


【解决方案1】:

您可能需要缓冲整个数据块,可能是 4 KB,以避免每次刷新都刷新整个数据块。但是,文件系统或驱动程序应该为您执行此操作,只要您不显式调用 fflush,这是真正的教训。

为什么需要如此频繁地同步它?也许计时器会比每个记录数的间隔更好?

【讨论】:

  • 文件系统确实会记录数据,直到显式同步或文件指针关闭。在同步到文件之前,我确实等待 50 次写入。频繁同步是因为记录的数据是时间敏感的,我想防止由于断电或其他一些意外因素而丢失数据。
  • @SumanRoy 当缓冲足够时它会写入数据。缓冲区不可能那么大。您已经观察到同步频率和闪存寿命之间的权衡,因此您需要确定最佳平衡点。
  • 实际上我使用的字符串大约是115字节,所以它会在大约5.7 KB的数据后同步。
  • @SumanRoy 如果它没有与块边界对齐,那对你没有任何好处。 5.7 KB 块的开头和结尾都将在 4 KB 块的序列中未对齐,因此大多数情况下,每次 5.7 KB 刷新都需要分配两个块,或写入 8 KB 空间。不过,我不知道这些块实际上有多大。我已经完成了这样的嵌入式日志记录,并且非常小心地直接针对闪存控制器进行了编码。如果您通过 IDE 接口,这是不可能的。
  • @SumanRoy 我的意思是要么依赖文件系统和驱动程序,提供他们期望的那种行为,要么研究硬件并直接自己驱动。
【解决方案2】:

由于每个扇区的写入周期限制为 100,000 次,因此延长闪存寿命是一项非常具有挑战性的任务。在我对其进行编写测试后,我的一张卡片在一个晚上就死了。然后我计算了时间段,这确实很容易在一个晚上执行 100,000 次写入(在同一扇区中)(不考虑经验计算)。

当时有人告诉我,某些文件系统中有一个智能监视器,它们会计算并保存每个扇区的写入数,以便每个扇区的写入数相同,我猜。我既没有服用也没有测试过。

我现在为 Raspberrypi 找到了一些非常受欢迎/投票率很高的答案/建议,我现在在这里引用它:

这些方法应该通过以各种方式最小化读/写次数来延长 SD 卡的使用寿命:

禁用交换

交换是将 SD 卡的一部分用作易失性存储器的过程。这将增加可用 RAM 的数量,但会导致大量的读/写。不太可能显着提高性能。

使用swapoff 命令禁用交换:

sudo swapoff --all

您还必须防止它在重新启动后再次出现:

  • 对于使用dphys-swapfile 管理交换文件(而不是“正常”交换分区)的 Raspbian,您可以简单地通过sudo apt-get remove dphys-swapfile 将其永久删除。最好删除,因为将 CONF_SWAPSIZE 设置为 0(如 this answer 中所述)似乎不起作用,并且在重新启动后仍会创建一个 100MB 的交换文件。
  • 对于使用交换分区而不是交换文件的其他发行版,请从 /etc/fstab 中删除相应的行

在文件系统上禁用日志

使用诸如ext3ext4 之类的日志文件系统而不使用日志是减少读/写的一种选择。使用禁用日志的文件系统的明显缺点是由于不正常的卸载(即电源故障后、内核锁定等)而导致数据丢失。

您可以通过将 ext3 挂载为 ext2 来禁用日志记录

您可以在卸载的驱动器上禁用 ext4 上的日志记录,如下所示:

tune4fs -O ^has_journal /dev/sdaX
e4fsck –f /dev/sdaX
sudo reboot

noatime 山旗

通过将 noatime 挂载标志添加到/etc/fstab 中分区的选项部分,将 noatime 挂载标志分配给驻留在 SD 卡上的分区。

对文件系统的读取访问将不再导致与文件关联的 atime 信息的更新。 noatime 设置的重要性在于它消除了系统对正在读取的文件进行写入文件系统的需要。由于如上一节所述,写入可能会有些昂贵,因此可以带来可衡量的性能提升。请注意,在启用此选项的情况下,只要写入文件,文件的写入时间信息就会继续更新。

RAM 中的目录

诸如/var/tmp/ 和可能的/var/log 之类的常用目录可以像这样重新定位到/etc/fstab 中的RAM:

tmpfs /var/tmp tmpfs nodev,nosuid,size=50M 0 0

这将允许/var/tmp 使用 50MB 的 RAM 作为磁盘空间。这样做的唯一问题是安装在 RAM 中的任何驱动器在重新启动后都不会持续存在。因此,如果您挂载/var/log 并且您的系统遇到导致其重新启动的错误,您将无法找出原因。

外部硬盘中的目录

您还可以将某些目录挂载到永久性 USB 硬盘上。更多详情请见this question

Raspberry Pi 还可以从外部驱动器启动它的根分区。这可以通过 USB 或以太网进行,这意味着 SD 卡将仅用于在启动期间委托给不同的设备。这需要一些内核破解才能完成,因为我认为默认内核不支持 USB 存储。您可以在this questionexternal blog post 找到更多信息。


这是另一位回答者的另一个有趣的考虑:

关于flash filesystems 的优秀文章。

关于闪存文件系统的重要问题如下:什么是磨损均衡? Wikipedia article。基本上,在闪存盘上,您可以写入有限的次数,直到块变坏。之后,文件系统(如果硬件上没有内置的磨损均衡管理,如 SSD 通常有)必须将该块标记为无效,并避免再使用它。

典型的文件系统(例如 reiserfs、ntfs、ext3 等)是为没有这些限制的硬盘设计的。

JFFS2

包括压缩和优雅的磨损均衡保护。

YAFFS2

  • 与众不同的一件事:成功卸载后,装载时间短。
  • 实现一次写入属性:数据写入一个块后,无需重写。这对于防止磨损均衡很重要。

LogFS

  • 不是很成熟,但已经包含在 Linux 内核树中。
  • 支持比 JFFS2/YAFFS2 更大的文件系统,没有问题。

UBIFS

  • 比LogFS更成熟
  • 写入缓存支持
  • 关于可扩展性:article。在大磁盘上,性能优于 JFFS2

ext4

如果没有驱动程序或卡(例如 SSD 驱动器确实具有内部磨损均衡,至少通常如此)处理磨损均衡,那么 ext4 不是最好的主意,因为它不适合原始闪存使用。

什么是最好的?

当然,这取决于使用和支持。根据我从互联网上阅读的内容,我会推荐 UBIFS。对大型文件系统的良好支持,成熟的开发阶段,足够的性能并且没有太大的缺点。

感谢回答者:

How can I extend the life of my SD card?

Choice of filesystem for GNU/Linux on an SD card

【讨论】:

  • 您的答案更多地基于 Pi 的细节,但理论仍然有帮助。
  • 这个答案中的一切都是关于 Linux 的。问题中没有任何关于 Linux 的内容。
  • 并非此问题中的所有建议都适用于 Raspberry Pi。 JFFS2 等闪存文件系统旨在用于原始闪存设备,而不是 SD 卡。
猜你喜欢
  • 2018-06-11
  • 2019-01-17
  • 2012-06-17
  • 2021-03-16
  • 2010-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多