我们通常了解过sparse file
fallocate - preallocate or deallocate space to a file
删除空间是一个杀手锏,可以像内存一样动态的分配和释放空间。像数据库文件等存储动态数据的文件,都需要可以删除文件的空间。
int fallocate(int fd, int mode, off_t offset, off_t len);
mode有5种模式,声明在 linux/falloc.h 头文件。
0,(the default operation, mode is zero)
FALLOC_FL_PUNCH_HOLE
FALLOC_FL_COLLAPSE_RANGE
FALLOC_FL_ZERO_RANGE
FALLOC_FL_INSERT_RANGE
还有两个flag
FALLOC_FL_KEEP_SIZE
FALLOC_FL_UNSHARE_RANGE
Allocating disk space
mode为0,分配磁盘空间,如果指定FALLOC_FL_KEEP_SIZE,那么stat统计的文件大小不变。
如果指定:FALLOC_FL_UNSHARE,那么offset,len指定的区域应该是一个共享的区域(btrfs支持cow),此时会取消共享。
函数执行成功后,可以保证后续写入不会出现磁盘空间不足的错误。
Deallocating file space
mode为FALLOC_FL_PUNCH_HOLE,释放offset,len指定的区域,相当于打了一个洞。这个mode必须指定FALLOC_FL_KEEP_SIZE。
即使区域在文件的末尾。
Collapsing file space
mode为FALLOC_FL_COLLAPSE_RANGE,释放offset,len指定的区域,然后将区域后面的内容往前移,填补释放的区域。
这样就不会留下一个洞。
Increasing file space
mode为:FALLOC_FL_INSERT_RANGE,在文件中间打个洞,然后将洞后面的数据往后移。不可指定FALLOC_FL_KEEP_SIZE。
Zeroing file space
mode为:FALLOC_FL_ZERO_RANGE,将区域offset,len置0,
个人理解:如果该区域已经有数据,则这段数据被标记为0(可能不会实际擦除)。如果该区域是一个sparse区域,则会实际分配内存。
fallocate创建的不是sparse file,它会预留空间。但是ftruncate创建的是sparse file,它不占用磁盘空间,只是改变文件的EOF位置。
# df . Filesystem 1K-blocks Used Available Use% Mounted on /dev/sdc2 112196608 3984 110081744 1% /nas/mnt/pa0 # # truncate -s 10G a # # df . Filesystem 1K-blocks Used Available Use% Mounted on /dev/sdc2 112196608 3984 110081744 1% /nas/mnt/pa0 # # stat a File: a Size: 10737418240 Blocks: 0 IO Block: 4096 regular file Device: 30h/48d Inode: 319 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2025-03-19 10:07:44.485833000 +0800 Modify: 2025-03-19 10:07:44.485833000 +0800 Change: 2025-03-19 10:07:44.485833000 +0800 Birth: -
使用truncate 创建一个10G的文件,可以看到磁盘空间没有变化。stat显示blocks为0。
如果使用fallocate分配一个10G的文件,则df发生了变化,stat显示blocks也有空间。
# fallocate -l 10G a # # df . Filesystem 1K-blocks Used Available Use% Mounted on /dev/sdc2 112196608 10492304 99593424 10% /nas/mnt/pa0 # # stat a File: a Size: 10737418240 Blocks: 20971520 IO Block: 4096 regular file Device: 30h/48d Inode: 320 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2025-03-19 10:12:56.588091391 +0800 Modify: 2025-03-19 10:12:56.588091391 +0800 Change: 2025-03-19 10:
off_t lseek(int fd, off_t offset, int whence);
3.1开始,内核添加内核两个whence,用来查找hole和data。SEEK_DATA、SEEK_HOLE。
打印data和hole的代码:
#define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <fcntl.h> int main(int argc, char **argv) { char *path = argv[1]; int fd; off_t off = 0; fd = open(path, O_RDONLY); if (fd < 0) { fprintf(stderr, "open failed: %m\n"); return 1; } while (1) { off_t next_data, next_hole; next_data = lseek(fd, off, SEEK_DATA); next_hole = lseek(fd, off, SEEK_HOLE); if (next_data == off) { printf("data [%ld - %ld)\n", (long)off, (long)next_hole); off = next_hole; } else if (next_hole == off) { printf("hole [%ld - %ld)\n", (long)off, (long)next_data); if (next_data == (off_t)-1) break; off = next_data; } else break; } close(fd); return 0; }
分配一个1G的文件,然后追加写入2个字节,可以打印出一个hole,一个data
# fallocate -l 1G a # echo 1 >> a # ./dump_hole a hole [0 - 1073741824) data [1073741824 - 1073741826)
对于keep size的文件打印不出尾巴的hole。
参考
man 2 fallocate, fallocate系统调用
man 1 fallocate,fallcoate工具
https://stackoverflow.com/questions/67031801/how-can-we-know-there-is-hole-in-a-file-in-c