延迟分配:
这是一个耐人寻味而有争议性的功能。延迟分配允许 ext4 等待分配将写入数据的实际块,直到它准备好将数据提交到磁盘。(相比之下,即使数据仍然在往写入缓存中写入,ext3 也会立即分配块。)
当缓存中的数据累积时,延迟分配块允许文件系统对如何分配块做出更好的选择,降低碎片(写入,以及稍后的读)并显著提升性能。然而不幸的是,它 增加 了还没有专门调用 fsync()
方法(当程序员想确保数据完全刷新到磁盘时)的程序的数据丢失的可能性。
假设一个程序完全重写了一个文件:
fd=open("file", O_TRUNC);
write(fd, data);
close(fd);
使用旧的文件系统,close(fd);
足以保证 file
中的内容刷新到磁盘。即使严格来说,写不是事务性的,但如果文件关闭后发生崩溃,则丢失数据的风险很小。
如果写入不成功(由于程序上的错误、磁盘上的错误、断电等),文件的原始版本和较新版本都可能丢失数据或损坏。如果其它进程在写入文件时访问文件,则会看到损坏的版本。如果其它进程打开文件并且不希望其内容发生更改 —— 例如,映射到多个正在运行的程序的共享库。这些进程可能会崩溃。
为了避免这些问题,一些程序员完全避免上面的现象,不使用 O_TRUNC
。
而使用另一种方法,他们可能会写入一个新文件,关闭它,然后将其重命名为旧文件名:
fd=open("newfile");
write(fd, data);
close(fd);
rename("newfile", "file");
在 没有 延迟分配的文件系统下,这足以避免上面列出的潜在的损坏和崩溃问题:
因为 rename()
有以下优点:
- 是原子操作,所以它不会被崩溃中断
- 进程结束后才会真正改变文件的名字,因此其他进程可以继续读写文件
- 并且运行的程序将继续引用旧的文件
- 现在
file
的未链接版本只要有一个打开的文件文件句柄即可
但是因为 ext4 的延迟分配会导致写入被延迟和重新排序,rename("newfile", "file")
可以在 newfile
的内容实际写入磁盘内容之前执行,这出现了并行进行再次获得 file
坏版本的问题。
为了缓解这种情况,Linux 内核(自版本 2.6.30)尝试检测这些常见代码情况并强制立即分配。这会减少但不能防止数据丢失的可能性 —— 并且它对新文件没有任何帮助。
如果你是一位开发人员,请注意:保证数据立即写入磁盘的唯一方法是正确调用 fsync()
。
以下式测试代码:
int main(int argc, char *argv[])
{
char buf[BUFSIZE] = {"12345"};
int fd = -1;
if((fd = open("test", O_RDWR | O_CREAT, 0666)) < 0)
{
perror("open && creat");
exit(-1);
}
if(write(fd, argv[1], 5) < 0)
{
perror("write");
exit(-1);
}
close(fd);
rename("test", "newname");
exit(0);
}