最近在看《APUE》,发现有讲file hole的概念,之前有遇到过,但一直没搞懂,趁机弄明白下。
文件空洞是什么?
文件空洞是指一个文件拥有一段连续的磁盘空间。但部分空间未被写入数据,这未被写入数据的部分被称为空洞。
如何构建一个空洞文件?
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#define SEEK_OFFSET 30010
char buf1[] = "ABCDEFG123";
char buf2[] = "abcdefg123";
void err_quit(char *fmt)
{
fputs(fmt, stderr);
exit(-1);
}
int main(void)
{
int fd;
size_t sbuf1 = sizeof(buf1);
size_t sbuf2 = sizeof(buf2);
printf("buf1 size %lu\n:", sbuf1);
if ((fd = creat("file.hole", 0644)) < 0)
err_quit("create error\n");
/* 写入一些数据 */
if (write(fd, buf1, sbuf1) != sbuf1)
err_quit("write failed\n");
/* 将写字符串的游标向后移动30010个字节 */
if (lseek(fd, SEEK_OFFSET, SEEK_CUR) == -1)
err_quit("lseek error\n");
/* 在移动后的地方写入数据 */
if (write(fd, buf2, sbuf2) != sbuf2)
err_quit("write failed\n");
exit(0);
}
生成结果如下:
root@helious ~/code/apue/files/examples ±ch3/exmaple/3-9 # ls -lh file.hole
-rw-r--r-- 1 root root 30K Oct 20 01:28 file.hole
root@helious ~/code/apue/files/examples ±ch3/exmaple/3-9 # ls -l file.hole
-rw-r--r-- 1 root root 30032 Oct 20 01:28 file.hole
root@helious ~/code/apue/files/examples ±ch3/exmaple/3-9 # du -h file.hole
8.0K file.hole
root@helious ~/code/apue/files/examples ±ch3/exmaple/3-9 # du file.hole
8 file.hole
du 输出的值,通过查阅du man page中指明block size
Display values are in units of the first available SIZE from --block-size, and the DU_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables. Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set).
那么du输出的是size num of 1024 byte,文件占用了8k字节,
ls -ls 输出的是block 和字节数,这个大家都明白。
30032字节= “ABCDEFG123 + 30010 + ABCDEFG123”
那么,这里就产生了差异,du 和 ls 输出的结果不一致,在回答为什么之前,我们需要先了解一些概念。
Blocks,block devices and block sizes是什么
和block相关的信息可以参见这里
blocks:用来存储字节或比特的一种的逻辑形式
block size:指当前文件系统下,文件存储的最小单位。
block devices:一个存储了以blocks存储数据的设备
kernel block size: 内核文件系统下每个block size为1024 byte
file system block size: 文件系统的每个block的大小,可以用stat -f .
查看,一般4096 字节
root@helious:~/code/apue/files/examples# stat -f .
File: "."
ID: fedc9aa3bd65bc57 Namelen: 255 Type: ext2/ext3
Block size: 4096 Fundamental block size: 4096
Blocks: Total: 65793553 Free: 62565881 Available: 59206342
Inodes: Total: 16777216 Free: 16584039
举一个具体的例子,一块磁盘(block devices)是由n个大小为block-size的blocks组成。
为什么ls 和 du 输出的值不一样呢
参见What is the difference between file size in ls -l and du-sh? – Unix & Linux Stack Exchange,或者Why ls and du show different size? – Super User
总结就是ls -lh计算的是分配的字节,而du,df计算的是文件实际占用的block数目
前面我们知道了block size的概念。还有另一个疑惑为什么block是8,而不是4呢(之所以说4,是因为实际有效的字节仅占22字节,远小于4k(file system block size),4k/1k = 4)
这里我们写入数据是连续的写入block中(ABCD….0000000000…..abcd),系统每次分配都是按file-system block size去分配的,至少占4k byte,根据上图的Block Size指出,4个block,末尾4个byte(4个block)
4k byte = 1 file-system block = 4 kernel blocks
其他须注意的,cat 会把文件的空洞给填充
root@helious:~/code/apue/files/examples# cat file.hole > file.cat
root@helious:~/code/apue/files/examples# ls -ls file.cat
32 -rw-r--r-- 1 root root 30032 Oct 23 14:46 file.cat
root@helious:~/code/apue/files/examples# cp file.hole file.copy
root@helious:~/code/apue/files/examples# du -s file.*
32 file.cat
8 file.copy
8 file.hole
32 file.nohole
32是怎么计算的呢?
30032/1k = 29,向上取整4的倍数(按 file-system block size分配),即为32
文件实际占用了多少空间(以du为准还是ls)
参考这个stackexchange的答案
The basic idea is that a file containing n bytes uses n bytes of disk space, plus a bit for some control information: the file’s metadata (permissions, timestamps, etc.), and a bit of overhead for the information that the system needs to find where the file is stored. However there are many complications.
du,df 显示的是占用block大小,而ls 显示的占用字节数。以哪个为标准呢?
那么假设我们把磁盘写满,看看能不能继续写入数据。
我的磁盘大小,空余空间是227G
root@helious ~/code/apue/files/examples ±ch3/exmaple/3-9 # df -h .
Filesystem Size Used Avail Use% Mounted on
/dev/sdc 251G 13G 227G 6% /
root@helious ~/code/apue/files/examples ±ch3/exmaple/3-9⚡ # df .
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sdc 263112772 12638264 237036352 6% /
改改上面代码建一个226G的 空文件。
root@helious ~/code/apue/files/examples ±ch3/exmaple/3-9⚡ # ls -lh file.largehole
-rw-r--r-- 1 root root 226G Feb 12 18:34 file.largehole
此时我们用df查看可用block数
root@helious ~/code/apue/files/examples ±ch3/exmaple/3-9⚡ # df .
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sdc 263112772 12638272 237036344 6% /
只减少了8个blocks,说明还是只计算了实际占用的block部分。
我们严谨一点,尝试用dd写一个5G的文件,看看能否写入
root@helious ~/code/apue/files/examples ±ch3/exmaple/3-9⚡ dd if=/dev/urandom of=./dd.urand bs=1G count=5 status=progress
5368709120 bytes (5.4 GB, 5.0 GiB) copied, 259 s, 20.7 MB/s
测试结果,依然可以继续写入,那么我们看看df的显示
root@helious ~/code/apue/files/examples ±ch3/exmaple/3-9⚡ # df .
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sdc 263112772 17881160 231793456 8% /
dd.urand的block真实占用了。
本篇文章也参考了这篇博客由一次磁盘告警引发的血案 — du 和 ls 的区别
结论
ls -lh 占用的空间不一定是它真实使用的空间,应该看df或者du,或者ls -s来查看占用的block数