文件系统使用statvfs()计算的已用空间大于文件系统中所有文件大小之和

5

我有一个50MiB的小分区,格式为ext4,只有一个目录包含一组照片,挂载在/mnt/tmp上。

然后我使用statvfs()来计算分区中已使用的字节,使用lstat()来计算每个文件的大小,为此编写了以下程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/statvfs.h>
#include <stdint.h>
#include <string.h>
#include <dirent.h>
#include <stdlib.h>

//The amount of bytes of all files found
uint64_t totalFilesSize=0;

//Size for a sector in the fs
unsigned int sectorSize=0;

void readDir(char *path) {
    DIR *directory;
    struct dirent *d_file;  // a file in *directory

    directory = opendir (path);

    while ((d_file = readdir (directory)) != 0)
    {
        struct stat filestat;
        char *abPath=malloc(1024);
        memset(abPath, 0, 1024);
        strcpy(abPath, path);
        strcat(abPath, "/");
        strcat(abPath, d_file->d_name);

        lstat (abPath, &filestat);

        switch (filestat.st_mode & S_IFMT)
        {
        case S_IFDIR:
        {
            if (strcmp (".", d_file->d_name) && strcmp ("..", d_file->d_name))
            {
                printf("File: %s\nSize: %d\n\n", abPath, filestat.st_size);

                //Add slack space to the final sum
                int slack=sectorSize-(filestat.st_size%sectorSize);

                totalFilesSize+=filestat.st_size+slack;

                readDir(abPath);
            }
            break;
        }
        case S_IFREG:
        {
            printf("File: %s\nSize: %d\n\n", abPath, filestat.st_size);

            //Add slack space to the final sum
            int slack=sectorSize-(filestat.st_size%sectorSize);

            totalFilesSize+=filestat.st_size+slack;

            break;
        }
        }

        free(abPath);
    }

    closedir (directory);
}

int main (int argc, char **argv) {

    if(argc!=2) {
        printf("Error: Missing required parameter.\n");
        return -1;
    }

    struct statvfs info;
    statvfs (argv[1], &info);

    sectorSize=info.f_bsize; //Setting global variable

    uint64_t usedBytes=(info.f_blocks-info.f_bfree)*info.f_bsize;

    readDir(argv[1]);

    printf("Total blocks: %d\nFree blocks: %d\nSize of block: %d\n\
Size in bytes: %d\nTotal Files size: %d\n",
            info.f_blocks, info.f_bfree, info.f_bsize, usedBytes, totalFilesSize);

    return 0;
}

将分区的挂载点作为参数传递(/mnt/tmp),程序显示以下输出:

File: /mnt/tmp/lost+found
Size: 12288

File: /mnt/tmp/photos
Size: 1024

File: /mnt/tmp/photos/IMG_3195.JPG
Size: 2373510

File: /mnt/tmp/photos/IMG_3200.JPG
Size: 2313695

File: /mnt/tmp/photos/IMG_3199.JPG
Size: 2484189

File: /mnt/tmp/photos/IMG_3203.JPG
Size: 2494687

File: /mnt/tmp/photos/IMG_3197.JPG
Size: 2259056

File: /mnt/tmp/photos/IMG_3201.JPG
Size: 2505596

File: /mnt/tmp/photos/IMG_3202.JPG
Size: 2306304

File: /mnt/tmp/photos/IMG_3204.JPG
Size: 2173883

File: /mnt/tmp/photos/IMG_3198.JPG
Size: 2390122

File: /mnt/tmp/photos/IMG_3196.JPG
Size: 2469315

Total blocks: 47249
Free blocks: 19160
Size of block: 1024
Size in bytes: 28763136
Total Files size: 23790592

在最后两行中需要注意的是,在FAT32文件系统中,数量是相同的,但在ext4文件系统中是不同的。
问题是:为什么?

2
从statvfs手册中:“在所有文件系统上,返回的结构的所有成员是否具有有意义的值是未指定的。” - fge
@fge 哎呀,那么,没有办法获得文件系统中可靠的字节数吗?感谢您的回答。 - jlledom
1
好的,你能做的最接近的就是df所做的——我不知道它是如何计算空间的。正如下面的答案所说,还有稀疏文件等等。此外,还要考虑已删除但仍然打开的文件的情况:它将占用磁盘空间,但永远不会显示在目录中。 - fge
相关:https://unix.stackexchange.com/questions/353156/how-to-calculate-the-correct-size-of-a-loopback-device-filesystem-image-for-debo - Ciro Santilli OurBigBook.com
2个回答

5

statvfs() 是一个文件系统级别的操作。使用的空间将从文件系统的角度计算。因此:

  1. 它将包含任何文件系统结构:对于基于Unix传统设计的文件系统,这包括 inode和任何间接块

    在我的某些系统上,我通常每32KB的空间有一个256字节的inode用于根分区。较小的分区可能具有更高的inode密度,以提供足够的inode用于大量文件 - 我相信 mke2fs 的默认值是每16KB的空间有一个inode。

    使用默认选项创建一个850 MB的Ext4文件系统会得到一个包含约54,000个inode的文件系统,占用超过13MB的空间。

  2. 对于Ext3/Ext4,这也将包括日志,其最小大小为1024个文件系统块。对于常见的块大小4KB,每个文件系统至少需要4MB。

    默认情况下,一个850 MB的Ext4文件系统将有一个16MB的日志。

  3. statvfs() 的结果还将包括任何已删除但仍处于打开状态的文件 - 这经常发生在用于应用程序的tmp目录所在的分区上。

  4. 要查看使用lstat()的实际空间,请使用stat结构的st_blocks字段并乘以512。根据程序输出中显示的大小,您正在使用st_size字段,它是文件的确切大小(以字节为单位)。这通常比实际使用的空间小 - 在具有4KB块的文件系统上,5KB文件实际上将使用8KB。

    相反,稀疏文件将使用比其文件大小所示的块数少得多的块。

因此,上述附加的空间使用量将累加到相当显著的数量,这解释了您看到的不一致之处。
编辑:
1.我刚刚注意到您程序中的松弛空间处理。虽然这不是推荐计算实际使用空间(而不是表面空间)的方法,但它似乎有效,所以您在那里没有丢失空间。另一方面,您错过了用于文件系统根目录的空间,尽管那可能只有一个或两个块:-)
2.您可能想查看tune2fs -l /dev/xxx的输出。它列出了几个相关数字,包括为文件系统元数据保留的空间。
顺便说一句,您程序中的大多数功能都可以使用dfdu完成:
# du -a --block-size=1 mnt/
2379776 mnt/img0.jpg
3441664 mnt/img1.jpg
2124800 mnt/img2.jpg
12288   mnt/lost+found
7959552 mnt/
# df -B1 mnt/
Filesystem     1B-blocks     Used Available Use% Mounted on
/dev/loop0      50763776 12969984  35172352  27% /tmp/mnt

顺便提一下,上面显示的Ext4测试文件系统是使用50MB镜像文件上的默认mkfs选项创建的。它具有1,024字节的块大小,12,824个占用1,603 KB的128字节索引节点和使用4,096KB的4096块日志。根据tune2fs,还有199个块预留给组描述符表。


这个程序是我准备用来说明这个问题的证明,所以我不会在现实世界中使用它,在那里有更好的其他选项来完成同样的工作。我的真正问题是,我正在尝试在另一个程序中制作进度条,但它永远不会在100%结束,当它只是结束复制文件时,由于这个问题,进度条停在85%。 - jlledom

3

可能没有计算索引节点,并且它们可能包含一些小数据。

如果文件是稀疏的,则其大小比实际占用的要大。

如果一个文件被硬链接多次,则共享一个常见的索引节点。

有关Ext4的论文在这里,由Kumar等人撰写


我在一个800MiB的硬盘上进行了类似的测试,有9个分区,结果相差超过40MiB。我认为这对于索引节点来说太大了,你觉得呢?程序已经解决了小文件松弛空间的问题。而且这个特定的例子没有硬链接,只有常规文件。 - jlledom
这个语句:“如果一个文件是稀疏的,它的大小小于实际占用的空间。”是错误的。相反的情况才是真的。 - Employed Russian
@Basile Starynkevitch 很棒的链接! - jlledom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接