Linux-文件写入和文件同步
1.背景
在现代操作系统中,内存分为用户空间和内核空间:
- 用户空间:这是普通应用程序运行的区域。应用程序只能访问它们自己的内存空间,无法直接访问内核空间的内存。
- 内核空间:这是操作系统内核运行的区域。内核可以访问所有的内存,包括用户空间和内核空间。
在Linux-IO模型这篇文章中,讲了文件的读read。
写操作同理,我们是从用户空间发起系统调用,再到内核空间。
内核空间嘛,内存。
内存和磁盘的速度差别是很明显的,为了平衡这个速度,内核空间是有缓存机制的。
所以啊,大概就是下面这个样子,注意图中间的是内存Memory。
写缓存(Buffer)和读缓存(Cache)。写b读c,谐音记忆的话就是下x班b打d车c。
在操作系统中,写入文件和同步文件数据到磁盘涉及几个不同的系统调用,主要包括 write()
和 fsync()
。
操作 | 类型 | 描述 | 优点 | 缺点 |
---|---|---|---|---|
write() | 延迟写 | 将数据从用户空间写入到内核空间的页缓存。数据此时位于内核缓存区中,尚未写入磁盘。 | 快速返回,效率高,减少磁盘 I/O 频率 | 数据尚未持久化,系统崩溃时可能丢失 |
fsync() | 刷盘 | 将文件描述符对应的文件的所有数据和元数据从内核缓存区同步到磁盘,确保数据持久化。 | 确保数据和元数据持久化,数据安全性高 | 阻塞操作,性能开销大,需等待写入完成 |
fdatasync() | 刷盘 | 将文件描述符对应的文件的数据和必要的元数据(如文件大小)从内核缓存区同步到磁盘,不包括其他元数据。 | 确保数据和必要元数据持久化,性能稍优于 fsync() |
阻塞操作,性能开销较大,需等待写入完成 |
1.文件写入: write
write()
用于将数据从用户空间写入到内核空间(内核的页缓存)。
ssize_t write(int fd, const void *buf, size_t count);
参数:
fd
:文件描述符,表示要写入的文件。buf
:要写入的数据缓冲区。count
:要写入的数据字节数。
返回值:
- 成功时,返回实际写入的字节数。
- 失败时,返回 -1,并设置
errno
以指示错误。
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd = open("example.txt", O_WRONLY | O_CREAT, 0644);
if (fd == -1) {
perror("open");
return 1;
}
const char *data = "Hello, World!\n";
// 写入文件
ssize_t bytes_written = write(fd, data, strlen(data));
if (bytes_written == -1) {
perror("write");
close(fd);
return 1;
}
close(fd);
return 0;
}
2.文件同步: fsync和fdatasync
2.1 fsync
fsync()
是一个系统调用,用于将文件描述符对应的文件的所有数据和元数据(例如文件大小、修改时间等)从内核缓存区同步到磁盘。
int fsync(int fd);
参数:
fd
:文件描述符,表示要同步的文件。
返回值:
- 成功时,返回 0。
- 失败时,返回 -1,并设置
errno
以指示错误。
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd = open("example.txt", O_WRONLY | O_CREAT, 0644);
if (fd == -1) {
perror("open");
return 1;
}
const char *data = "Hello, World!\n";
ssize_t bytes_written = write(fd, data, strlen(data));
if (bytes_written == -1) {
perror("write");
close(fd);
return 1;
}
// 同步文件
if (fsync(fd) == -1) {
perror("fsync");
close(fd);
return 1;
}
close(fd);
return 0;
}
2.2 fdatasync
fdatasync()
类似于 fsync()
,但它只将文件数据和必要的元数据(例如文件大小)同步到磁盘,而不包括文件的其他元数据(例如修改时间)。
int fdatasync(int fd);
参数:
fd
:文件描述符,表示要同步的文件。
返回值:
- 成功时,返回 0。
- 失败时,返回 -1,并设置
errno
以指示错误。
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd = open("example.txt", O_WRONLY | O_CREAT, 0644);
if (fd == -1) {
perror("open");
return 1;
}
const char *data = "Hello, World!\n";
ssize_t bytes_written = write(fd, data, strlen(data));
if (bytes_written == -1) {
perror("write");
close(fd);
return 1;
}
// 同步文件
if (fdatasync(fd) == -1) {
perror("fdatasync");
close(fd);
return 1;
}
close(fd);
return 0;
}
3.总结
3.1 只write() 不 fsync()
如果只使用 write()
而不调用 fsync()
,数据将会被写入到内核缓存区,但不会立即被写入到磁盘。
要注意的点是这几个:
数据可能丢失
- 如果系统在
write()
后发生崩溃或断电,由于数据只在内核缓存区而未写入到磁盘,这些数据将会丢失。内核缓存区中的数据不会在系统崩溃后恢复。
操作系统决定何时写入磁盘
- 操作系统会根据自己的调度策略在适当的时候将数据从内核缓存区写入到磁盘。
- 这种延迟写入可以提高系统性能,因为操作系统可以将多个写操作合并后一起写入磁盘。
- 但是,这也意味着应用程序无法控制数据何时被持久化,存在一定的不确定性
数据写入的时机不确定
- 由于内核会根据自己的调度策略和缓存策略决定何时将数据写入磁盘,所以数据的持久化时间是不可预测的。
哎,那么只用write一定不合适吗?这就要看我们的场景了,看数据是否重要,比如日志的话,就可以使用write,安全性要求不高,还能兼具性能。
3.2 只fync()不write()
通常需要先使用 write()
将数据写入到内核缓存区,然后再使用 fsync()
确保数据被持久化到磁盘。
直接用的话,就是将内核缓存区的数据同步到磁盘,做持久化,所以也叫作刷盘嘛。
那关键点就在于我们内核缓存区里面有没有数据,没有的话,刷过去的就是空的。
所以说,我们也可以几次write再结合一次fsync,自己控制刷盘时机。