Linux file hole

最近在看《APUE》,发现有讲file hole的概念,之前有遇到过,但一直没搞懂,趁机弄明白下。

文件空洞是什么?

文件空洞是指一个文件拥有一段连续的磁盘空间。但部分空间未被写入数据,这未被写入数据的部分被称为空洞。

文件空洞.drawio

如何构建一个空洞文件?

#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.drawio

这里我们写入数据是连续的写入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数

发表回复

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