fallocate – 预分配文件空间
1. 函数介绍
[fallocate](file:///root/代码目录/fio/helpers.h#L7-L7) 是 Linux 系统调用,用于为文件预分配磁盘空间。你可以把它想象成”预订”磁盘空间,就像你在餐厅预订座位一样——你告诉系统你需要多少空间,系统就为你预留出来,但此时还没有实际写入数据。
这个函数的主要优势是:
- 提高文件系统性能:避免文件碎片
- 确保文件有足够空间:防止写入时空间不足
- 快速操作:比实际写入数据更快
2. 函数原型
#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()](file:///root/代码目录/fio/helpers.h#L8-L8): POSIX标准版本,可移植性更好
truncate()
: 改变文件大小ftruncate()
: 改变文件描述符对应的文件大小lseek()
: 移动文件指针位置
7. 示例代码
示例1:基本的文件空间预分配
#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:创建空洞文件(释放指定范围的空间)
#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[] = "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模式释放文件中间部分的空间
#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[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
是一个非常有用的系统调用,特别适用于以下场景:
- 数据库系统预分配文件空间
- 大文件写入前的空间预留
- 虚拟机磁盘映像创建
- 需要避免文件碎片的高性能应用
记住,不是所有文件系统都支持所有模式,使用时需要检查返回值并做好错误处理。