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



我有一个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: %snSize: %dnn", 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: %snSize: %dnn", 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: %dnFree blocks: %dnSize of block: %dn
Size in bytes: %dnTotal Files size: %dn",
            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文件系统中,大小不同。

问题是:为什么?

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

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

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

    使用默认选项创建一个850 MB的Ext4文件系统会产生一个大约有54,000个索引节点的文件系统,占用超过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个128字节的索引节点占用1,603 KB,一个4096块的日志占用4,096KB。根据tune2fs,还有199个块是为组描述符表保留的。

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

如果一个文件是稀疏的,它的大小大于实际占用的大小。

如果一个文件有多个硬链接,则共享一个公共inode。

关于Ext4的论文在这里,由Kumar等人

最新更新