unix data sync
leveldb的file io中,有些io是要同步磁盘的,这里集中看一下。
传统的UNIX实现的内核中都设置有缓冲区或者页面高速缓存,大多数磁盘IO都是通过缓冲写的。
当你想将数据write进文件时,内核通常会将该数据复制到其中一个缓冲区中,如果该缓冲没被写满的话,内核就不会把它放入到输出队列中。
当这个缓冲区被写满或者内核想重用这个缓冲区时,才会将其排到输出队列中。等它到达等待队列首部时才会进行实际的IO操作。
所以,针对某个fd,如果你想保证磁盘上的实际文件和缓冲区中的内容保持一致,这里其实也有几个方法
fcntl(F_FULLFSYNC)
1 2 3
#include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ );
对于 F_FULLFSYNC,实际使用时是这样的:
1
int result = fcntl(fd, F_FULLFSYNC);
fdatasync
1 2 3
#include <unistd.h> int fdatasync(int fd);
fsync
1 2 3
#include <unistd.h> int fsync(int fd);
所有这些函数的参数和返回值说明:
- fd: 文件描述符,指向要同步的文件。
- 返回值:
- 成功时返回 0
- 失败时返回 -1,并设置 errno 来指示错误
注意事项:
fcntl 是一个更通用的函数,可以执行多种文件控制操作。F_FULLFSYNC 是其中的一个特定命令,仅在 macOS 系统上可用。
fdatasync 和 fsync 在大多数 UNIX-like 系统上都可用,它们是 POSIX 标准的一部分。
使用这些函数时,需要包含相应的头文件,并且在某些系统上可能需要链接特定的库。
错误处理通常涉及检查返回值并在失败时检查 errno。例如:
1
2
3
4
if (fsync(fd) == -1) {
perror("fsync failed");
// 处理错误...
}
具体来讲,这三个调用都用于确保数据持久化,但它们在不同的操作系统和文件系统上有不同的行为和保证。让我们逐一比较:
fcntl(fd, F_FULLFSYNC)
- 这是 macOS 特有的系统调用。
- 提供最强的持久化保证。
- 确保所有数据和元数据都被写入到物理存储介质。
- 会等待存储设备完成所有待处理的写操作。
- 在某些情况下可以绕过硬件缓存。
- 性能开销最大,但提供最强的数据安全保证。
fdatasync(fd)
- POSIX 标准的一部分,在大多数 UNIX-like 系统上可用。
- 同。步文件的数据部分到磁盘
- 不一定同步文件的元数据(除非元数据对读取文件内容必要)。
- 通常比 fsync 快,因为它可能不需要更新某些元数据。
- 在某些文件系统上,可能和 fsync 的行为相同。
fsync(fd)
- POSIX 标准的一部分,在大多数 UNIX-like 系统上可用。
- 同步文件的所有数据和元数据到磁盘。
- 确保文件的所有方面(包括修改时间、大小等)都被更新。
- 通常比 fdatasync 慢,因为它需要更新所有相关的元数据。
性能和保证强度比较:
fcntl(F_FULLFSYNC) > fsync > fdatasync
- fcntl(F_FULLFSYNC) 提供最强的保证,但性能开销最大。
- fsync 提供全面的文件同步,包括所有元数据。
- fdatasync 专注于数据同步,可能会在某些情况下性能更好。
使用建议:
- 如果你在 macOS 上开发,并且需要最强的持久化保证,使用 fcntl(F_FULLFSYNC)。
- 如果你需要确保文件的所有方面都被同步,包括元数据,使用 fsync。
- 如果你主要关心文件的数据内容,而不太在意元数据(如访问时间),使用 fdatasync 可能会有更好的性能。
- 在跨平台应用中,fsync 通常是最安全和最通用的选择。
需要注意的是,实际行为可能因操作系统和文件系统而异。在某些系统上,这些调用的实际实现可能没有显著区别。始终建议在目标系统上进行测试,以确保达到所需的持久化级别。