安全有效地修改POSIX系统上的多个文件?

Ray*_*net 7 filesystems posix

我一直在关注EXT4上的"bug",如果使用"创建临时文件,写入临时文件,将temp重命名为目标文件"进程,会导致文件在崩溃时归零.POSIX说,除非调用fsync(),否则您无法确定数据是否已刷新到硬盘.

显然这样做:

0) get the file contents (read it or make it somehow)
1) open original file and truncate it
2) write new contents
3) close file
Run Code Online (Sandbox Code Playgroud)

即使使用fsync()也不好,因为计算机可能会在2)或fsync()期间崩溃,并且您最终会得到部分写入的文件.

通常认为这是非常安全的:

0) get the file contents (read it or make it somehow)
1) open temp file
2) write contents to temp file
3) close temp file
4) rename temp file to original file
Run Code Online (Sandbox Code Playgroud)

不幸的是它不是.为了使其在EXT4上安全,您需要执行以下操作:

0) get the file contents (read it or make it somehow)
1) open temp file
2) write contents to temp file
3) fsync()
4) close temp file
5) rename temp file to original file
Run Code Online (Sandbox Code Playgroud)

这是安全的,在崩溃时你应该有新的文件内容或旧的,从不归零的内容或部分内容.但是如果应用程序使用大量文件,则每次写入后fsync()都会很慢.

所以我的问题是,如何在需要fsync()以确保更改已保存到磁盘的系统上有效地修改多个文件?我的意思是修改许多文件,就像成千上万的文件一样.修改两个文件并在每个文件之后执行fsync()不会太糟糕,但fsync()在修改多个文件时确实会减慢速度.

编辑:将fsync()关闭临时文件更改为正确的顺序,强调编写许多文件.

Dou*_*der 0

您需要交换上一个列表中的 3 和 4 -fsync(fd)使用文件描述符。我不明白为什么这会特别昂贵 - 无论如何你都希望通过 close() 将数据写入磁盘。因此,您想要发生的事情和将要发生的事情之间的成本将是相同的fsync()

如果成本太高,(并且您有)fdatasync(2)避免同步元数据,因此成本应该较低。

编辑:所以我写了一些非常hacky的测试代码:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <string.h>

static void testBasic()
{
    int fd;
    const char* text = "This is some text";

    fd = open("temp.tmp", O_WRONLY | O_CREAT);
    write(fd,text,strlen(text));
    close(fd);
    rename("temp.tmp","temp");
}

static void testFsync()
{
    int fd;
    const char* text = "This is some text";

    fd = open("temp1", O_WRONLY | O_CREAT);
    write(fd,text,strlen(text));
    fsync(fd);
    close(fd);
    rename("temp.tmp","temp");
}

static void testFdatasync()
{
    int fd;
    const char* text = "This is some text";

    fd = open("temp1", O_WRONLY | O_CREAT);
    write(fd,text,strlen(text));
    fdatasync(fd);
    close(fd);
    rename("temp.tmp","temp");
}

#define ITERATIONS 10000

static void testLoop(int type)
{
    struct timeval before;
    struct timeval after;
    long seconds;
    long usec;
    int i;

    gettimeofday(&before,NULL);
    if (type == 1)
    {
        for (i = 0; i < ITERATIONS; i++)
        {
            testBasic();
        }
    }
    if (type == 2)
    {
        for (i = 0; i < ITERATIONS; i++)
        {
            testFsync();
        }
    }
    if (type == 3)
    {
        for (i = 0; i < ITERATIONS; i++)
        {
            testFdatasync();
        }
    }
    gettimeofday(&after,NULL);

    seconds = (long)(after.tv_sec - before.tv_sec);
    usec = (long)(after.tv_usec - before.tv_usec);
    if (usec < 0)
    {
        seconds--;
        usec += 1000000;
    }

    printf("%ld.%06ld\n",seconds,usec);
}

int main()
{
    testLoop(1);
    testLoop(2);
    testLoop(3);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

在我的笔记本电脑上生成:

0.595782
6.338329
6.116894
Run Code Online (Sandbox Code Playgroud)

这表明这样做的fsync()成本要高出约 10 倍。而且fdatasync()价格稍微便宜一些。

我想我看到的问题是每个应用程序都会认为它的数据对于 fsync() 来说足够重要,因此在一分钟内合并写入的性能优势将被消除。