昨天我和某人就以下内容的逻辑和/或真实性进行了一场小辩论我的回答在这里相对而言,在一个像样的 (GB+) 大小的 SD 卡上记录和维护文件系统元数据永远不会足够重要,不会在合理的时间(年复一年)内磨损该卡。反驳的要点似乎是我一定是错的,因为网上有很多关于人们用完 SD 卡的故事。
因为我确实有带有 SD 卡的设备,其中包含 24/7 保留的 rw 根文件系统,所以我之前已经测试过这个前提,令我自己满意。我稍微调整了这个测试,重复了它(实际上使用同一张卡)并在这里展示它。我的两个核心问题是:
- 我用来破坏卡片的方法是否可行,记住它的目的是重现不断重写的效果小的数据量?
- 我用来验证该卡的方法是否仍然可行?
我将问题放在这里而不是 SO 或超级用户,因为对第一部分的反对可能必须断言我的测试并没有真正按照我确定的方式写入卡,并断言这需要一些Linux的特殊知识。
[也可能是 SD 卡使用某种智能缓冲或缓存,这样对同一位置的重复写入将被缓冲/缓存在不易磨损的地方。我没有在任何地方找到任何迹象,但我是询问那件事在苏]
测试背后的想法是向卡上的同一个小块写入数百万次。这远远超出了此类设备可以承受多少写入周期的任何说法,但假设磨损均衡是有效的,如果卡的大小合适,数百万次这样的写入仍然不重要,因为“相同的块”实际上不会是相同的物理块。为此,我需要确保每次写入都真正刷新到硬件,并且刷新到相同的硬件明显的地方。
为了刷新到硬件,我依赖于 POSIX 库调用fdatasync()
:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
// Compile std=gnu99
#define BLOCK 1 << 16
int main (void) {
int in = open ("/dev/urandom", O_RDONLY);
if (in < 0) {
fprintf(stderr,"open in %s", strerror(errno));
exit(0);
}
int out = open("/dev/sdb1", O_WRONLY);
if (out < 0) {
fprintf(stderr,"open out %s", strerror(errno));
exit(0);
}
fprintf(stderr,"BEGIN\n");
char buffer[BLOCK];
unsigned int count = 0;
int thousands = 0;
for (unsigned int i = 1; i !=0; i++) {
ssize_t r = read(in, buffer, BLOCK);
ssize_t w = write(out, buffer, BLOCK);
if (r != w) {
fprintf(stderr, "r %d w %d\n", r, w);
if (errno) {
fprintf(stderr,"%s\n", strerror(errno));
break;
}
}
if (fdatasync(out) != 0) {
fprintf(stderr,"Sync failed: %s\n", strerror(errno));
break;
}
count++;
if (!(count % 1000)) {
thousands++;
fprintf(stderr,"%d000...\n", thousands);
}
lseek(out, 0, SEEK_SET);
}
fprintf(stderr,"TOTAL %lu\n", count);
close(in);
close(out);
return 0;
}
我运行了大约 8 个小时,直到我积累了超过 200 万次写入到分区的开头/dev/sdb1
。1 我可以轻松使用/dev/sdb
(原始设备而不是分区),但我看不出这会产生什么区别。
然后我通过尝试在 上创建和安装文件系统来检查该卡/dev/sdb1
。这有效,表明我整夜写入的特定块是可行的。然而,这并不意味着卡的某些区域没有被磨损并因磨损均衡而移位,而是可以访问。
为了测试这一点,我badblocks -v -w
在分区上使用了。这是一个破坏性的读写测试,但无论是否进行磨损均衡,它都应该是卡的可行性的有力指示,因为它仍然必须为每个滚动写入提供空间。换句话说,这相当于完全填写卡片,然后检查所有内容是否正常。有好几次,因为我让坏块通过几种模式工作。
[与下面 Jason C 的评论相反,以这种方式使用坏块并没有什么错误或错误。虽然由于 SD 卡的性质,它对于实际识别坏块没有用,但使用-b
和开关进行任意大小的破坏性读写测试是很好的-c
,这就是修订后的测试的所在(请参阅我自己的答案) )。卡控制器的任何魔法或缓存都无法欺骗测试,即可以将几兆字节的数据写入硬件并正确地再次读回。杰森的其他评论似乎是基于误读——我认为故意的一,这就是为什么我懒得去争论。有了这个头脑,我就让读者来决定什么是有意义的以及什么是才不是.]
1该卡是一张旧的 4 GB Sandisk 卡(上面没有“类别”编号),我几乎没有使用过。再次记住,这并不是对同一物理位置进行 200 万次写入;而是对同一物理位置进行了 200 万次写入。由于磨损均衡,“第一个块”将在测试期间被控制器不断移动,如术语所述,消除磨损。
答案1
我认为 SD 卡的压力测试通常存在以下两点问题:
磨损均衡 无法保证对下一个的写入实际上是在 SD 上使用相同的物理位置。请记住,大多数现有的 SD 系统都会主动获取我们所知的块,并根据每个位置所遭受的感知“磨损”来移动支持该块的物理位置。
不同的技术(MLC 与 SLC) 我看到的另一个问题是技术的差异。我预计 SLC 类型的 SSD 比 MLC 类型的寿命要长得多。此外,MLC 上的容差要严格得多,而 SLC 上则不必处理这些问题,或者至少它们对这种失败的容忍度要高得多。
- MLC - 多层单元
- SLC - 单层单元
MLC 的问题在于,给定单元可以存储多个值,这些位本质上是使用电压堆叠的,而不仅仅是物理 +5V 或 0V,因此这可能会导致比 SLC 高得多的潜在故障率相等的。
预期寿命
我发现这个链接讨论了一些关于硬件可以持续多久的问题。它的标题是:了解您的 SSD - SLC 与 MLC。
SLC
根据最佳估计,SLC 固态硬盘的平均寿命在大多数情况下可以在 49 到 149 岁之间。 Memoright测试可以验证128Gb SSD的写入寿命超过200年,平均每天写入100Gb。
多层电容
这就是 MLC 设计的不足之处。目前尚未发布任何内容。没有人真正研究过MLC能保证什么样的预期寿命,只是它会大大降低。我收到了几种不同的看法,平均认为 slc 设计的使用寿命为 10 比 1。保守的猜测是,大多数寿命估计将在 7 到 10 年之间,具体取决于每个制造商控制器内“磨损均衡算法”的进步。
比较
通过写入周期进行比较,SLC 的使用寿命为 100,000 个完整的写入周期,而 MLC 的使用寿命为 10,000 个写入周期。这可能会显着增加,具体取决于所使用的“磨损均衡”的设计。
答案2
您的测试存在很多问题,有些模糊,有些不模糊。这也取决于您的目标。两个微妙的、模糊的问题是:
- 您没有从正在写入的同一区域进行读取,您的读取测试有效,然后什么也不做(除非控制器具有读取干扰校正,在这种情况下,它可能偶尔会将正在读取的页面移动到其他地方,但这仍然会发生)不影响你的测试)。
- 您假设(很可能,但不能保证)控制器检测到并报告了对坏块的读/写操作 - 您需要写入数据,读回数据,然后进行比较以进行有保证的检查。
然而,这些可以说是迂腐的。更严重的是:
- 您无法使用它
badblocks
来显示闪存上的故障页面;所有故障检测和后续页面映射均由控制器完成,对操作系统透明。如果驱动器支持,您可以从 SMART 获取一些信息(我知道没有 SD 卡支持它,也许有高端拇指驱动器支持它)。 - 磨损均衡,由于您的测试未考虑之前的 TRIM 命令、测试期间驱动器的空闲/已使用状态以及保留空间而变得复杂。
磨损均衡:主要问题是磨损均衡是测试中的主要变量。它发生在控制器上(通常),并且在任何情况下它对于直接设备查找+读/写都是透明的。在您的示例中,您实际上并不知道磨损均衡状态(特别是最近是否向空闲块发出了 TRIM 命令?)...
对于设备上的动态磨损均衡(几乎存在于所有消费级存储设备中),它可能处于任何状态:在一种极端情况下,没有任何页面被标记为空闲,因此控制器必须工作的唯一页面with 是保留空间中的那些(如果有)。请注意,如果有是设备上的保留空间,它将要在开始保证页面写入失败之前必须完全失败(假设没有其他页面标记为空闲剩余)。在另一个极端,每个页面都被标记为免费,在这种情况下,理论上您必须拥有每一个在您开始看到写入失败之前,设备上的页面会失败。
对于静态磨损均衡(SSD 往往有,SD 卡往往没有,拇指驱动器各不相同):除了重复写入设备上的每个页面之外,确实没有办法解决这个问题。
...换句话说,有些磨损均衡细节是您无法知道的,当然也无法控制的——特别是是否使用动态磨损均衡、是否使用静态磨损均衡以及设备上为磨损均衡保留的空间量(在控制器[或某些情况下的驱动程序,如 M-Systems 旧的 DiskOnChip] 中不可见)。
SLC/MLC:至于 SLC 与 MLC,这对您期望看到的限制有非常直接的影响,但两者的一般磨损均衡程序和测试程序是相同的。尽管任何声称每页 100k+ 循环限制的闪存驱动器都可能是 SLC(简化的权衡是 SLC = 耐用性,MLC = 密度),但许多供应商并未公布他们的设备是 SLC 还是 MLC,以供其更便宜的消费产品使用。
缓存:至于缓存,有点不确定。在OS层面,一般情况下,当然fsync/fdatasync并不能保证数据真正写入。但是,我认为在这种情况下可以安全地假设它是(或者至少控制器已承诺这样做,即写入不会被缓存吞没),因为可移动驱动器通常是针对常见使用模式而设计的“弹出”(卸载>同步)然后移除(断电)。虽然我们不确定,但有根据的猜测表明,可以安全地假设同步保证写入绝对会发生,特别是在写入 -> 同步 -> 读回时(如果不是,驱动器将不可靠)弹出后)。除了“sync”之外,没有其他命令可以在弹出时发出。
在控制器上,一切皆有可能,但上述假设还包括这样的假设:控制器至少没有做任何“复杂”到足以导致同步后数据丢失的风险的假设。可以想象,如果相同的数据被重写(在有限的范围内),控制器可以缓冲和分组写入,或者不写入数据。在下面的程序中,我们在两个不同的数据块之间交替,并在读回之前执行同步,专门用于破坏合理的控制器缓存机制。当然,仍然没有保证,也没有办法知道,但我们可以根据这些设备的正常使用和合理/通用的缓存机制做出合理的假设。
测试:
不幸的是,事实是,除非你知道由于设备没有保留空间并且没有进行静态调平,因此无法明确测试特定页面的循环限制。但是,您可以获得的最接近结果如下(假设没有静态磨损均衡):
这第一的您需要做的就是用数据填充整个卡。这很重要,并且是原始测试中留下的主要变量。除了任何保留空间(您无法访问)之外,这将标记尽可能多的已使用块。请注意,我们正在使用整个设备(这将破坏其上的所有数据),因为使用单个分区仅影响设备上的一个特定区域:
dd if=/dev/urandom bs=512k of=/dev/sdb conv=fsync oflag=sync
如果您是进度条类型:
pv -pterb -s <device_size> /dev/urandom | dd bs=512k of=/dev/sdb conv=fsync oflag=sync
编辑:对于具有 4MB 擦除块的卡,请尝试以下操作以获得更快的写入速度:
dd if=/dev/urandom bs=4M of=/dev/sdb conv=fsync oflag=direct,sync iflag=fullblock
接下来,您可以编写一个循环测试程序,如下所示,利用 和O_DIRECT
(O_SYNC
以及可能偏执的、冗余的使用fsync()
)从画面中删除尽可能多的操作系统缓冲和缓存,理论上,直接写入控制器并等待它报告操作已完成:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdlib>
#include <cstdio>
#include <cstring>
using namespace std;
static const int BLOCK_SIZE = 512;
static const int ALIGNMENT = 512;
static const int OFFSET = 1024 * ALIGNMENT; // 1024 is arbitrary
int main (int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "usage: %s device\n", argv[0]);
return 1;
}
int d = open(argv[1], O_RDWR | O_DIRECT | O_SYNC);
if (d == -1) {
perror(argv[1]);
return 1;
}
char *block[2], *buffer;
int index = 0, count = -1;
// buffers must be aligned for O_DIRECT.
posix_memalign((void **)&(block[0]), ALIGNMENT, BLOCK_SIZE);
posix_memalign((void **)&(block[1]), ALIGNMENT, BLOCK_SIZE);
posix_memalign((void **)&buffer, ALIGNMENT, BLOCK_SIZE);
// different contents in each buffer
memset(block[0], 0x55, BLOCK_SIZE);
memset(block[1], 0xAA, BLOCK_SIZE);
while (true) {
// alternate buffers
index = 1 - index;
if (!((++ count) % 100)) {
printf("%i\n", count);
fflush(stdout);
}
// write -> sync -> read back -> compare
if (lseek(d, OFFSET, SEEK_SET) == (off_t)-1)
perror("lseek(w)");
else if (write(d, block[index], BLOCK_SIZE) != BLOCK_SIZE)
perror("write");
else if (fsync(d))
perror("fsync");
else if (lseek(d, OFFSET, SEEK_SET) == (off_t)-1)
perror("lseek(r)");
else if (read(d, buffer, BLOCK_SIZE) != BLOCK_SIZE)
perror("read");
else if (memcmp(block[index], buffer, BLOCK_SIZE))
fprintf(stderr, "memcmp: test failed\n");
else
continue;
printf("failed after %i successful cycles.\n", count);
break;
}
}
请注意,对于O_DIRECT
,缓冲区必须适当对齐。 512 字节边界通常就足够了。您可以使用以下命令进行编译:
g++ -O0 test.cpp -o test
如果需要的话添加-D_POSIX_C_SOURCE=200112L
。
然后,按照上述方法完全填充设备后,让它运行过夜:
./test /dev/sdb
512 字节、对齐写入就可以了,这将使您擦除并重写一整页。您可以通过使用更大的块大小来显着加快测试速度,但随后获得具体结果就会变得复杂。
我目前正在测试一个看起来相当破旧的 4GB PNY 拇指驱动器,这是我昨天在人行道上发现的(似乎是一个http://www3.pny.com/4GB-Micro-Sleek-Attach---紫色-P2990C418.aspx)。
上面的程序本质上是一个有限版本,badblocks
在所有保留空间耗尽之前您不会看到失败。因此,期望(每次迭代写入 1 页)上述过程平均应该失败保留页数 * 写入周期限制迭代(同样,磨损均衡是一个主要变量)。遗憾的是,拇指驱动器和 SD 卡通常不支持 SMART,而 SMART 能够报告保留空间大小。
顺便说一句,出于本次测试的目的, fsync
vs对于您正在执行的块设备写入没有任何影响。fdatasync
你的open()
模式很重要。
如果您对技术细节感到好奇;以下是您可能想了解的有关 SD 卡内部工作原理的所有信息(以及更多信息):https://www.sdcard.org/downloads/pls/simplified_specs/part1_410.pdf
编辑:字节与页:在这些类型的测试中,重要的是要根据页面而不是字节来考虑事物。反其道而行之可能会产生很大的误导。例如,在 SanDisk 8GB SD 上,根据控制器(可通过 访问/sys/classes/mmc_host/mmc?/mmc?:????/preferred_erase_size
)的页面大小为完整的 4MB。写入 16MB(与 4MB 边界对齐),然后擦除/写入 4 页。然而,以彼此 4MB 偏移量写入四个单字节也会擦除/写入 4 个页面。
那么说“我用 16MB 写入进行测试”是不准确的,因为它与“我用 4 字节写入进行测试”的磨损量相同。更准确地说,“我测试了 4 页写入”。
答案3
只是在 slm 的答案中添加一些要点 - 请注意,这些对于 SSD 来说比“哑”SD 卡更合适,因为 SSD 可以玩很多对数据的更肮脏的伎俩(例如重复数据删除):
您正在将 64KB 写入设备的开头 - 这本身有两个问题:
闪存单元通常具有 16KB 以上大小的擦除块(不过更有可能在 128-512KB 范围内)。这意味着它至少需要这个大小的缓存。因此,写 64KB 对我来说似乎还不够。
对于低端(阅读“非企业”)解决方案(我更希望 SD/CF 卡比 SSD 更是如此),制造商可能会选择使设备的开头部分比其他部分更具耐磨性,因为重要的结构 - 设备上单个分区上的分区表和 FAT(大多数存储卡都使用此设置) - 位于此处。因此,测试卡的开头可能会有偏差。
fdatasync()
并不能真正保证数据写入物理介质(尽管它可能在操作系统的控制下做得最好) - 请参阅手册页:呼叫阻塞直到设备报告转移已完成
如果事实证明有一个小电容器,它能够在失去外部电源的情况下提供将缓存数据写入闪存的能量,我不会感到太惊讶。
无论如何,假设卡上存在缓存(请参阅我对你关于SU的问题的回答),写入 64KB 并同步(与
fdatasync()
)对于此目的似乎没有足够的说服力。即使没有任何“电源备份”,固件仍然可能会不安全,并且使数据未写入的时间比预期的要长一些(因为在典型的使用情况下,它不应该产生任何问题)。您可能想在写入新块并进行比较之前读取数据,只是为了确保它确实有效(如果您足够偏执,请使用已清除的缓冲区进行读取)。
答案4
Peterph的回答确实让我进一步考虑可能的缓存问题。在深入研究之后,我仍然不能确定是否有任何、部分或所有 SD 卡都这样做,但我确实认为这是可能的。
但是,我不认为缓存会涉及大于擦除块的数据。为了确实确定,我使用 16 MB 块而不是 64 kB 重复了测试。这是 4 GB 卡总体积的 1/250。执行此操作 10,000 次大约需要 8 个小时。如果磨损均衡尽力分散负载,这意味着每个物理块将被使用 40 次。
虽然不多,但测试的初衷是展示磨损均衡的功效通过向同一(明显)位置重复写入适量数据,我无法轻易损坏该卡。 IMO 之前的 64 kB 测试可能是真实的,但 16 MB 的测试一定是真实的。系统已将数据刷新到硬件,并且硬件已报告写入且没有错误。如果这是一个骗局,那么该卡就没有任何用处,并且除了主存储之外,它不能在任何地方缓存 16 MB,这正是测试的目的。
希望 10,000 次写入(每次写入 16 MB)足以证明,即使在低端名牌卡(价值:5 美元 CDN)上,运行一个每天 24/7 写入适量数据的 rw 根文件系统不会在合理的时间内磨损该卡。 一万天就是27年……卡还好好的……
如果我得到报酬来开发比这更繁重的工作的系统,我会想做至少一些测试来确定一张卡能持续多久能最后的。我的预感是,像这样的写入速度较低,可能需要数周、数月或数年的时间以最大速度连续写入(事实上,网上没有大量此类比较测试,这说明了事实上,这将是一个非常漫长的事情)。
关于确认该卡仍然没问题,我不再认为使用badblocks
它的默认配置是合适的。相反,我这样做了:
badblocks -v -w -b 524288 -c 8
这意味着使用 512 kB 块重复 8 次 (= 4 MB) 进行测试。由于这是一种破坏性的读写测试,如果连续循环使用,它可能会像我的自制测试一样对设备施加压力。
我还在其上创建了一个文件系统,复制到一个 2 GB 文件中,diff
将该文件与原始文件进行比较,然后(因为该文件是 .iso)将其安装为映像并浏览其中的文件系统。
卡还是没问题的毕竟,这可能是预料之中的……