在Linux中如何使用ioctl(原始分区)正确刷新磁盘缓存

4
我正在尝试使用ioctl来确保直接写入卷的更改已经到达磁盘。在原始分区中,fsync()显然不可用。 sync()也是一个可怕的解决方案(为了刷新64MB,我需要等待整个生命周期)。所以...这就是我正在尝试做的事情 - 获取errno 25。/dev/sda3是SSD驱动器上未挂载的原始分区。
open(_fd, "/dev/sda3", ...)
pwritev(_fd, ...)

ioctl(_fd, BLKFLSBUF, 0)   <== errno = 25. 

Ubuntu 14.04,c
注意:
hdparm -W 0 /dev/sda3

失败原因:设备上的 ioctl 不合适。

如何找到适合我的固态硬盘的刷新方法?


使用直接I/O或/dev/raw是一个选项吗? - Mark Plotnick
2
“fsync() obviously not available in raw partition”是什么意思?fsync(_fd)fdatasync(_fd)应该会将内容刷新到底层设备,即使_fd指向块设备。实际的刷新是由[fs/block_dev.c:blkdev_fsync()] (https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/fs/block_dev.c#n368)(通过[fs/sync.c:fdatasync()] (https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/fs/sync.c#n230) → do_fsync() → vfs_fsync() → vfs_fsync_range(),然后通过file_operations结构的blkdev_fsync())完成的。 - Nominal Animal
@MarkPlotnick - 我正在打开 /dev/sda3。而不是 /dev/raw。我拿了另一台机器,在那里 hdparam -W 0 起作用了。但是 ioctl 失败,错误号为25。所以我的问题是 - 我可以在 /dev/sdxN 上使用 ioctl BLKFLSHBUF 吗? - Adi
@NominalAnimal - fsync 给我内存速率,而不是磁盘速率。它似乎什么也没做。我的情况是:1)创建未挂载的分区2)fd = open("/dev/sdaxN",..permissions..)3)写入..现在fsync什么也没做,ioctl(fd,BLKFLSBUF)失败了。我错过了什么?我想确保数据被物理写入。 - Adi
1个回答

3

我无法在使用4.2.0-42-generic内核的Ubuntu 14.04.4 LTS x86_64上复制ioctl(fd, BLKFLSBUF)错误。

我测试了完整的块设备和它们上面的单个分区。你可以尝试以下最小化的测试程序吗?

将以下内容保存为例如 block-flush.c

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    int arg, descriptor, result;

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s BLOCK-DEVICE-OR-PARTITION ...\n", argv[0]);
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    for (arg = 1; arg < argc; arg++) {

        do {
            descriptor = open(argv[arg], O_RDWR);
        } while (descriptor == -1 && errno == EINTR);
        if (descriptor == -1) {
            const int cause = errno;
            fprintf(stderr, "%s: Cannot open device: %s [%d].\n", argv[arg], strerror(cause), cause);
            return EXIT_FAILURE;
        }

        errno = 0;
        result = ioctl(descriptor, BLKFLSBUF);
        if (result && errno) {
            const int cause = errno;
            fprintf(stderr, "%s: Cannot flush device: %s [%d].\n", argv[arg], strerror(cause), cause);
            return EXIT_FAILURE;
        } else
        if (result)
            fprintf(stderr, "%s: Flush returned %d.\n", argv[arg], result);
        else
        if (errno) {
            const int cause = errno;
            fprintf(stderr, "%s: Flush returned zero, but with error: %s [%d]. Ignored.\n", argv[arg], strerror(cause), cause);
        }

        result = close(descriptor);
        if (result == -1) {
            const int cause = errno;
            fprintf(stderr, "%s: Error closing device: %s [%d].\n", argv[arg], strerror(cause), cause);
            return EXIT_FAILURE;
        }

        fprintf(stderr, "%s: Flushed.\n", argv[arg]);
    }

    return EXIT_SUCCESS;
}

使用以下方式进行编译:

gcc -Wall -O2 block-flush.c -o block-flush

以root权限运行,并在命令行指定分区或块设备进行操作:

sudo ./block-flush /dev/sda3

对于未挂载的分区,以及磁盘(/dev/sdx)本身,此命令会输出/dev/sdxN: Flushed.。我使用一个外部USB SATA托架和一个“响亮”的3.5英寸硬盘进行了测试(这种托架需要外部电源;USB电源对于这些带有旋转碟片的较大驱动器不足)。我可以清楚地听到ioctl()确实访问了物理设备,因此它不是无操作(而且,在我的测试中,最小化的测试程序从未报告任何错误)。在关闭描述符后,直到打开磁盘或分区进行进一步访问之前,磁盘也处于静止状态。当然,这些观察结果只适用于USB连接的硬盘,仅适用于特定的内核和硬件架构,但在我看来,这表明ioctl(descriptor, BLKFLSBUF);应该按预期方式适用于未挂载的分区和完整的块设备。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接