【问题标题】:Detect disc removal on fwrite in C在 C 中检测 fwrite 上的磁盘移除
【发布时间】:2013-02-15 21:30:34
【问题描述】:

我正在编写一个应用程序来不断地向驱动器(无论是硬盘驱动器还是 SD 卡或其他任何东西)写入和读取文件。我正在编写某种模式,然后将其读回作为验证。一旦应用程序失败,我想立即输出某种刺耳的错误。基本上,我们正在用辐射击中硬件,并且需要检测它何时发生故障。到目前为止,我的应用程序可以很好地读取和写入文件,但可以在执行过程中拉出 SD 卡,并且它会继续运行,就好像它还在那里一样。我真的需要检测 SD 卡被移除的那一刻。我看到一些建议使用libudev。我不能使用它,因为这是在没有它的嵌入式 linux 系统上。这是我到目前为止的代码:

#include <stdio.h>
#include <time.h>

const unsigned long long size = 16ULL*1024;
#define NANOS 1000000000LL
#define KB 1024

long long CreateFile(char* filename)
{
    struct timespec time_start;
    struct timespec time_stop;
    long long start, elapsed, microseconds;
    int timefail = 0;
    size_t stat;

    if(clock_gettime(CLOCK_REALTIME, &time_start) < 0)
        timefail = 1;
    start = time_start.tv_sec*NANOS + time_start.tv_nsec;

    int a[size];
    int i, j;

    for(i=0;i<size;i++)
        a[i] = i;

    FILE* pFile;
    pFile = fopen(filename, "wb");
    if(pFile < 0)
    {
        perror("fopen");
        return -1;
    }
    for(j=0; j < KB; j++)
    {
        stat = fwrite(a, sizeof(int), size, pFile);
        if(stat < 0)
            perror("fwrite");
        stat = fsync(pFile);
        //if(stat)
        //  perror("fysnc");
    }

fclose(pFile);

    if(clock_gettime(CLOCK_REALTIME, &time_stop) < 0)
        timefail = 1;

    elapsed = time_stop.tv_sec*NANOS + time_stop.tv_nsec - start;
    microseconds = elapsed / 1000 + (elapsed % 1000 >= 500);

    if(timefail)
        return -1;

    return microseconds / 1000;
}

long long ReadFile(char* filename)
{
    struct timespec time_start;
    struct timespec time_stop;
    long long start, elapsed, microseconds;
    int timefail = 0;

    if(clock_gettime(CLOCK_REALTIME, &time_start) < 0)
        timefail = 1;
    start = time_start.tv_sec*NANOS + time_start.tv_nsec;

    FILE* pFile;
    pFile = fopen(filename, "rb");

    int a[KB];
    int i=0, j=0;
    for(i=0; i<size; i++)
    {
        if(ferror(pFile) != 0)
        {
            fprintf(stderr, "**********************************************");
            fprintf(stderr, "READ FAILURE\n");
            fclose(pFile);
            return -1;
        }
        fread(a, sizeof(a), 1, pFile);
        for(j=0; j<KB;j++)
        {
            if(a[0] != a[1]-1)
            {
                fprintf(stderr, "**********************************************");
                fprintf(stderr, "DATA FAILURE, %d != %d\n", a[j], a[j+1]-1);
                fclose(pFile);
                return -1;
            }
        }
    }
    fclose(pFile);
    if(clock_gettime(CLOCK_REALTIME, &time_stop) < 0)
            timefail = 1;

    if(timefail)
        return -1;

    elapsed = time_stop.tv_sec*NANOS + time_stop.tv_nsec - start;
    microseconds = elapsed / 1000 + (elapsed % 1000 >= 500);

    return microseconds/1000;
}

int main(int argc, char* argv[])
{
    char* filenamebase = "/tmp/media/mmcblk0p1/test.file";
    char filename[100] = "";
    int i=0;
    long long tmpsec = 0;
    long long totalwritetime = 0;
    int totalreadtime = 0;
    int numfiles = 10;
    int totalwritten = 0;
    int totalread = 0;

    for(i=0;i<numfiles;i++)
    {
        sprintf(filename, "%s%d", filenamebase, i);
        fprintf(stderr, "Writing File: %s ...", filename);
        tmpsec = CreateFile(filename);
        if(tmpsec < 0)
            return 0;
        totalwritetime += tmpsec;
        totalwritten++;
        fprintf(stderr, "completed in %lld seconds\n", tmpsec);
        fprintf(stderr, "Reading File: %s ...", filename);
        tmpsec = ReadFile(filename);
        if(tmpsec < 0)
            return 0;
        totalreadtime += tmpsec;
        totalread++;
        fprintf(stderr, "completed in %lld seconds\n", tmpsec);
    }

    fprintf(stderr, "Test Complete\nTotal Files: %d written, %d read\n", totalwritten, totalread);
    fprintf(stderr, "File Size: %lld KB\n", size);
    fprintf(stderr, "Total KBytes Written: %lld\n", size*totalwritten);
    fprintf(stderr, "Average Write Speed: %0.2f KBps\n", (double)size*totalwritten/(totalwritetime/1000));
    fprintf(stderr, "Total KBytes Read: %lld\n", size*totalread);
    fprintf(stderr, "Average Read Speed: %0.2f KBps\n", (double)size*totalread/(totalreadtime/1000));

    return 0;
}

【问题讨论】:

  • 好吧,我几乎放弃了尝试让愚蠢的代码正确粘贴。不知道为什么这不起作用。
  • perror 是否调用退出?
  • 如果没有,那就是你的错误...如果 fwrite/fsync 返回错误,你应该打破循环。
  • fwrite/fsync 100% 的时间返回错误。但是,当插入卡时,文件被正确写入和读取。如果我拔下设备,程序会继续运行,就好像它正在写入文件一样。我对这种行为完全感到困惑。 (感谢代码编辑)。
  • 但是程序是这样制作的...您甚至不检查 fread 的返回码(在读取函数中)...为了有正确的行为:在您的写入函数中如果发生错误,您应该中断写循环。在读取函数中,您应该检查 fread 是否返回错误...

标签: c++ c linux file-io


【解决方案1】:

你需要改变你的方法。

如果你拉出已经挂载的媒体,你可能会惊慌你的内核(因为它在内存中保留了代表挂载文件系统的复杂数据结构),并破坏了媒体本身。

我已经用这种方式销毁了很多 USB 记忆棒——它们处理分配和磨损均衡的内部小逻辑不喜欢在运行中断电,而且最便宜的似乎没有电容器能够提供足够的电力让它们运行足够长的时间以确保一致的状态 - 但 SD 卡和更昂贵的 USB 记忆棒可能会更好地生存。

根据所使用的驱动程序,内核可能允许您读取和写入媒体,但只需将更改保存在页面缓存中。 (此外,您的 stdio.h I/O 可能只到达页面缓存,而不是实际的媒体,这取决于安装选项(是否直接/同步安装)。您的方法根本不提供您的行为假设确实如此。)

相反,您应该使用低级 I/O(unistd.h,参见 man 2 open 和相关调用,没有 stdio.h),使用 O_RDWR|O_DIRECT|O_SYNC 标志来确保您的读取和写入命中硬件, 并通过块设备节点直接访问原始媒体,而不是安装它。您还可以读取/写入设备上的随机位置,希望磨损均衡不会过多影响您的抗辐射检查。

(编辑添加:如果您在块中写入与测试媒体设备的本机分配块大小完全相同的块,您将避免设备上缓慢的读取-修改-写入周期。设备仍会进行磨损均衡, 但这只是意味着您写入的块位于闪存芯片中的随机物理位置。本机块大小取决于媒体设备。可以通过观察需要多长时间来测量本机块大小读取和写入不同大小的块,但我认为对于损坏测试,足够大的 2 次方应该效果最好——比如 256k 或 262144 字节。最好让用户分别为每个设备设置它,然后使用制造商提供的信息,或单独的测试程序来找出正确的值。)

您不想为此使用mmap(),因为媒体错误和媒体变得不可用导致的 SIGBUS 信号很难正确处理。在我看来,低级 unistd.h I/O 最适合这个。

我相信,但尚未验证,将在读/写中间的媒体拉出到未安装的低级设备,应该只会产生读/写错误。 (不过,我现在没有任何愿意冒险检查的媒体:)

【讨论】:

  • 谢谢,我去看看。明天可能会在这方面工作。
  • 好的,@ChuckClaunch。实际的测试应用程序应该非常简单(我会使用 Xorshift 生成写入媒体的随机数据),并让它成为一个破坏性测试(擦除/弄乱被测试的整个媒体设备)。如果您以前没有使用过低级 I/O,我想我可以展示一些示例代码,说明如何通过对原始块设备进行正确的错误检查来执行 open()/read()/write()。跨度>
  • 谢谢,这有帮助,因为我至少能够在移除卡时让操作挂起。这总比卡还在那儿好。如果发生这种情况,仍然希望它立即出错,但这应该足够了。谢谢!
  • @ChuckClaunch:使用超时计时器在短时间内(但比正常读/写时间长)后发出信号,并为该信号设置一个微不足道的信号处理程序(空体)。信号的传递将中断阻塞的 I/O 调用。最好在初始超时后以非常短的间隔重复计时器,以便在短读/写的情况下,您可以检测到超时。它不是即时的,但应该非常强大。
【解决方案2】:

从我的评论中回答:

在你的 write 函数中你应该有:

for(j=0; j < KB; j++)
{
    uint32_t bytes_written = fwrite(a, sizeof(int), size, pFile);
    if(bytes_written < size)
    {
        perror("fwrite");
        break;
    }
    stat = fsync(pFile);
    if(stat < 0)
    {
        perror("fysnc");
        break;
    }
}

在你的阅读功能中:

uint32_t read_bytes_count = fread(a, sizeof(a), 1, pFile);
if(read_bytes_count < sizeof(a))
    break;

另外,如果您有 C99 编译器,请使用 stdint.h 中提供的固定大小类型,例如:uint32_t,...

【讨论】:

  • 嘿,感谢您提供的代码,这绝对有帮助。我不得不接受 Nominal 的回答,因为这是帮助我解决问题的低级别呼叫。
猜你喜欢
  • 2015-06-27
  • 2010-09-28
  • 1970-01-01
  • 2014-03-23
  • 1970-01-01
  • 2014-10-27
  • 2020-04-28
  • 2017-11-15
  • 2010-09-29
相关资源
最近更新 更多