fallocate系统调用及示例

fallocate - 预分配文件空间

1. 函数介绍

fallocate 是 Linux 系统调用,用于为文件预分配磁盘空间。你可以把它想象成”预订”磁盘空间,就像你在餐厅预订座位一样——你告诉系统你需要多少空间,系统就为你预留出来,但此时还没有实际写入数据。

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

这个函数的主要优势是:

  • 提高文件系统性能:避免文件碎片

  • 确保文件有足够空间:防止写入时空间不足

  • 快速操作:比实际写入数据更快

2. 函数原型

1
2
3
4
#include <fcntl.h>

int fallocate(int fd, int mode, off_t offset, off_t len);

3. 功能

为文件预分配指定范围的磁盘空间,而不需要实际写入数据。这可以优化文件系统的存储布局,提高I/O性能。

4. 参数

  • int fd: 文件描述符,通过 open() 函数获得

int mode: 操作模式

  • 0: 默认模式,分配空间

  • FALLOC_FL_PUNCH_HOLE: 创建空洞(释放空间)

  • FALLOC_FL_COLLAPSE_RANGE: 折叠范围

  • FALLOC_FL_ZERO_RANGE: 清零范围

off_t offset: 文件中的起始偏移量(字节)

off_t len: 要分配的长度(字节)

5. 返回值

  • 成功时返回 0

  • 失败时返回 -1,并设置 errno

6. 相似函数,或关联函数

  • posix_fallocate(): POSIX标准版本,可移植性更好

  • truncate(): 改变文件大小

  • ftruncate(): 改变文件描述符对应的文件大小

  • lseek(): 移动文件指针位置

7. 示例代码

示例1:基本的文件空间预分配

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
37
38
39
40
41
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main() {
int fd;
int ret;

// 创建一个新文件用于测试
fd = open("test_file.dat", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("打开文件失败");
exit(EXIT_FAILURE);
}

printf("文件创建成功,文件描述符: %d\n", fd);

// 预分配10MB的空间
ret = fallocate(fd, 0, 0, 10 * 1024 * 1024);
if (ret == -1) {
if (errno == EOPNOTSUPP) {
printf("警告: 当前文件系统不支持 fallocate\n");
} else {
perror("fallocate 调用失败");
}
close(fd);
exit(EXIT_FAILURE);
}

printf("成功预分配10MB空间\n");

// 关闭文件
close(fd);
printf("文件已关闭\n");

return 0;
}

示例2:创建空洞文件(释放指定范围的空间)

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
37
38
39
40
41
42
43
44
45
46
47
48
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main() {
int fd;
int ret;
char data&#91;] = "Hello, World!";

// 创建一个新文件
fd = open("sparse_file.dat", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("打开文件失败");
exit(EXIT_FAILURE);
}

printf("创建文件,文件描述符: %d\n", fd);

// 先写入一些数据在文件开头
if (write(fd, data, strlen(data)) == -1) {
perror("写入数据失败");
close(fd);
exit(EXIT_FAILURE);
}

printf("已在文件开头写入: %s\n", data);

// 预分配10MB空间在1MB偏移处(创建空洞)
ret = fallocate(fd, 0, 1024 * 1024, 10 * 1024 * 1024);
if (ret == -1) {
perror("fallocate 调用失败");
close(fd);
exit(EXIT_FAILURE);
}

printf("在1MB偏移处预分配了10MB空间\n");
printf("此时文件大小约为11MB,但实际占用磁盘空间很小\n");

// 关闭文件
close(fd);
printf("文件已关闭\n");

return 0;
}

示例3:使用PUNCH_HOLE模式释放文件中间部分的空间

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main() {
int fd;
int ret;
char data&#91;1024];

// 创建测试文件并填充数据
fd = open("punch_hole_test.dat", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("打开文件失败");
exit(EXIT_FAILURE);
}

printf("创建测试文件\n");

// 填充1MB数据
memset(data, 'A', sizeof(data));
for (int i = 0; i < 1024; i++) {
if (write(fd, data, sizeof(data)) == -1) {
perror("写入数据失败");
close(fd);
exit(EXIT_FAILURE);
}
}

printf("已写入1MB数据\n");

// 使用PUNCH_HOLE模式释放中间512KB的空间
// 注意:PUNCH_HOLE需要与FALLOC_FL_KEEP_SIZE一起使用
ret = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
256 * 1024, 512 * 1024);
if (ret == -1) {
if (errno == EOPNOTSUPP) {
printf("当前文件系统不支持 PUNCH_HOLE 操作\n");
} else {
perror("fallocate PUNCH_HOLE 操作失败");
}
} else {
printf("成功在文件中间创建了512KB的空洞\n");
printf("这部分空间已被释放,但文件大小保持不变\n");
}

// 关闭文件
close(fd);
printf("文件已关闭\n");

return 0;
}

总结

fallocate 是一个非常有用的系统调用,特别适用于以下场景:

数据库系统预分配文件空间

大文件写入前的空间预留

虚拟机磁盘映像创建

需要避免文件碎片的高性能应用

记住,不是所有文件系统都支持所有模式,使用时需要检查返回值并做好错误处理。

data-ad-format="auto" data-full-width-responsive="true">