文件打洞和稀疏文件

Last updated on 8 months ago

稀疏文件

稀疏文件是一种文件,其中大部分内容是空的(或者说是零填充的),但文件系统仍然为其分配了存储空间,文件大,实际占用磁盘空间少,我们 使用lseek或truncate到一个固定位置生成的“空洞文件”是不会占据真正的磁盘空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
int fd;
off_t offset = 1024 * 1024 * 1024; // 1 GB
char buf[1]; // Writing a single byte

// Open a file
fd = open("sparse_file.txt", O_WRONLY | O_CREAT, 0666);
if (fd == -1) {
perror("open");
return 1;
}

// 更新 offest, 增大size
if (lseek(fd, offset - 1, SEEK_SET) == -1) {
perror("lseek");
close(fd);
return 1;
}
// Write a single byte at the offset position
if (write(fd, buf, 1) != 1) {
perror("write");
close(fd);
return 1;
}

// Close the file
close(fd);

printf("Sparse file created successfully.\n");
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
#写入后,实际磁盘 占用只有4k(8个block) ,但是size为 1073741824
root@ubuntu:/home# gcc test.c -o test && ./test
Sparse file created successfully.
root@ubuntu:/home# ls -sl sparse_file.txt
4 -rw-r--r-- 1 root root 1073741824 Feb 26 07:15 sparse_file.txt

root@ubuntu:/home# stat sparse_file.txt
File: sparse_file.txt
Size: 1073741824 Blocks: 8 IO Block: 4096 regular file
Device: 801h/2049d Inode: 1102149 Links: 1

文件打洞

上边例子中稀疏文件,是通过在空文件中的某个偏移量写入了1个字节得到的。一个文件开始并非稀疏的,它已经占用了若干的磁盘空间,可能某段范围的内容已经失效了,想把这段失效的文件部分所占用的磁盘空间还给文件系统,我们为了减小文件所占用的磁盘空间,就只能通过文件打洞 (Hole Punching)的方式将非稀疏文件转为稀疏文件,主动释放一段文件所占的物理空间

要想达到该效果,linux 系统上有专门的函数实现 fallocate

fallocate用于预分配块到文件。对于支持fallocate系统调用的文件系统,这是通过分配块并将它们标记为未初始化的,从而快速完成的,不需要IO到数据块。这比用0填充文件要快得多。

在Linux内核v2.6.31的情况下,fallocate系统调用由btrfs、ext4、ocfs2和xfs filesystems支持。 fallocate返回的退出代码在成功时是0,而失败则是1。

1
2
3
int fallocate(int fd, int mode, off_t offset, off_t len);
mode为0,此时会将文件的[offset, offset+len)区域的内容写为0
mode为 FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE [offset, offset+len)区域中的块就会被“打洞” 回收,减少系统占用

假如现在有个 42k大小的文件,有数据的

1
2
3
root@ubuntu:/home# ll -s -h hole 
44K -rw-r--r-- 1 root root 42K Feb 26 07:40 hole

接下来我用 fallocate 回收 hole 文件 在 [4096 ,8192] 的空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
// 注意在CentOS 7中还需要包含linux/falloc.h:
#include <linux/falloc.h>
#include <sys/stat.h>
#include <assert.h>

int main()
{
int ret;
int fd;
fd = open("./hole", O_RDWR|O_CREAT, 0755);
assert(fd != -1);
// 4096 ~ 8192
ret = fallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, 4096,8192);
assert(ret == 0);
close(fd);
return 0;
}

执行程序并看下文件大小, 可以看到, size 并没有变化,还是42k, 但是 磁盘空间变化了

1
2
root@ubuntu:/home# ll -sh hole 
40K -rw-r--r-- 1 root root 42K Feb 26 07:42 hole