fallocate系统调用及示例

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 是一个非常有用的系统调用,特别适用于以下场景:

  1. 数据库系统预分配文件空间
  2. 大文件写入前的空间预留
  3. 虚拟机磁盘映像创建
  4. 需要避免文件碎片的高性能应用

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

此条目发表在linux文章分类目录,贴了标签。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注