为什么目录的大小报告与其他文件不同?

为什么目录的大小报告与其他文件不同?

我想知道为什么一个空目录占用了 4096 字节的空间,我看到问题。据称,空间是按块分配的,因此新目录的大小为 4096 字节。

不过,我非常确定“普通”文件的分配也是按块完成的。至少是这样的Windows 文件系统我猜测它至少在 ext* 中必须相似。

现在据我了解,其他类型文件(例如文件、符号链接等)的大小列表是根据实际大小完成的。因为当我创建一个空文件时,我看到大小为 0。当输入几个字符时,我会看到<字符数>字节作为大小等。

所以我的问题是,虽然其他文件的分配也是以块为单位完成的,但为什么报告目录和文件大小的策略不同?

澄清

我认为这个问题足够清楚,但显然不是。我将尝试在这里澄清这个问题。

1)我认为目录是什么:

我将尝试通过以下示例来解释我对目录的理解。阅读后,如有错误,请告知。

假设我们有一个名为 的目录mydir。假设它包含 3 个文件,分别是:f0f1f2。假设每个文件的长度为 1 个字节。

现在,什么是mydir?它是一个指向 inode 的指针,其中包含以下内容:字符串“f0”和f0指向的 inode 编号。字符串“f1”及其f1指向的索引节点号。以及字符串“f2”和f2指向的索引节点号。 (至少这是我认为的目录。如果我错了,请纠正我。)

现在计算目录大小可能有两种方法:

mydir1)计算指向的inode的大小。

2) 对内容指向的索引节点的大小求和mydir

尽管 1 更违反直觉,但我们假设它是正在使用的方法。 (对于这个问题,实际使用的是哪种方法并不重要。)然后, 的大小mydir计算如下:

2 + 2 + 2 + 3 * <space_required_to_store_an_inode_number>

2 是因为每个文件名都是 2 个字节长。

2)问题:

现在的问题是:假设我认为目录是正确的,则mydir无论使用方法 1 还是方法 2 来计算其大小,报告的大小都应远小于 4096。

现在,您会说报告 4096 字节的原因是因为分配是在块中完成的。因此,报告的尺寸那么大。

但接下来我会说:对于常规文件来说,分配也是以块的形式完成的。 (看特里格的回答供参考)但是,尽管如此,它们的尺寸仍以实际尺寸报告。 (如果包含 1 个字符,则为 1 个字节;如果包含 2 个字符,则为 2 个字节,等等)

所以我的问题是,为什么报告目录大小的策略与报告常规文件大小的策略如此不同?

更多说明:

我们知道,为非空文件和空目录分配的初始块数都是8块。 (看特里格的回答)因此,即使为常规文件和目录分配了相同数量的块,为什么报告的目录大小要大得多?

答案1

我认为您感到困惑的原因是因为您不知道目录是什么。为此,让我们退后一步,检查一下 Unix 文件系统是如何工作的。

Unix 文件系统有几个分离磁盘上数据寻址的概念:

  • 数据块是磁盘上的一组块,它们具有内容一个文件的。
  • 索引节点是文件系统上的特殊块,具有该文件系统中唯一的数字地址,其中包含有关文件的元数据,例如:
    • 权限
    • 访问/修改时间
    • 尺寸
    • 指向数据块的指针(可以是块、范围等的列表)
  • 文件名是文件系统根上映射到 inode 的分层位置。

换句话说,一个“文件”实际上是由三个不同的东西组成的:

  1. 文件系统中的 PATH
  2. 带有元数据的索引节点
  3. inode指向的数据块

大多数时候,用户将文件想象为“与文件名关联的实体”的同义词 - 只有当您处理低级实体或文件/套接字 API 时,您才会想到 inode 或数据块。目录是这些低级实体之一。

您可能认为目录是包含一堆其他文件的文件。这只是对了一半。目录是将文件名映射到索引节点号的文件。它不“包含”文件,而是指向文件名的指针。将其视为包含如下条目的文本文件:

  • 。 - 索引节点 1234
  • .. - 索引节点 200
  • 文档 - 索引节点 2008
  • 自述文件.txt - 索引节点 2009

上面的条目称为目录条目。它们基本上是从文件名到索引节点号的映射。目录是包含目录条目的特殊文件。

当然,这是一种简化,但它解释了基本思想和其他目录怪异之处。

  • 为什么目录不知道自己的大小?
    • 因为它们只包含指向其他内容的指针,所以您必须迭代它们的内容才能找到大小
  • 为什么目录永远不会是空的?
    • 因为它们至少包含 .和..条目。因此,正确的目录至少应与可包含这些条目的最小文件大小一样小。在大多数文件系统中,4096 字节是最小的。
  • 为什么重命名文件时需要父目录的写权限?
    • 因为您不仅要更改文件,还要更改指向该文件的目录条目。
  • 为什么 ls 显示目录的奇怪数量的“链接”?
    • 一个目录可以被它本身、它的父目录、它的子目录引用(链接到)。
  • 硬链接有什么作用以及它与符号链接有何不同?
    • 硬链接添加了一个目录项指向同一个 inode 号。因为它指向一个 inode 号,所以它只能指向同一文件系统中的文件(inode 是文件系统本地的)
    • 符号链接添加一个指向单独文件名的新索引节点。因为它引用文件名,所以它可以指向树中的任意文件。

可是等等!奇怪的事情正在发生!

ls -ld somedirectory始终显示文件大小为 4096,而ls -l somefile显示文件的实际大小。为什么?

困惑点1:当我们说“尺寸”时,我们可以指两件事:

  • 文件大小,它是存储在 inode 中的数字;和
  • 分配的大小,它是与 inode 关联的块数乘以每个块的大小。

一般来说,这些不是同一个数字。尝试运行stat常规的文件,您会看到这种差异。

当文件系统创建非空文件时,它通常会急切地成组分配数据块。这是因为文件有任意快速增长和收缩的趋势。如果文件系统仅根据需要分配尽可能多的数据块来表示文件,则增长/收缩会更慢,并且碎片将是一个严重的问题。因此在实践中,文件系统不必为小的变化而重新分配空间。这意味着磁盘上可能有大量空间被文件“占用”但完全未使用。

文件系统如何处理所有这些未使用的空间?没有什么。直到感觉需要为止。如果您的文件系统优化器工具 - 可能是在后台运行的在线优化器,可能是 fsck 的一部分,可能内置于您的文件系统本身 - 感觉像这样,它可能会重新分配文件的数据块 - 移动已使用的块,释放未使用的块块等

现在我们来看看常规文件和目录之间的区别:因为目录构成了文件系统的“骨干”,所以您期望它们可能需要频繁访问或修改,因此应该进行优化。所以你根本不希望它们支离破碎。创建目录时,它们总是最大输出即使它们只有这么多目录条目,它们的所有数据块的大小也是如此。这对于目录来说是可以的,因为与文件不同,目录的大小和增长率通常受到限制。

4096 报告的目录大小是存储在目录 inode 中的“文件大小”数字,而不是目录中的条目数。它不是一个固定数字 - 它是适合为目录分配的块数的最大字节数。通常,这是为具有任何内容的文件分配的 512 字节/块乘以 8 个块 - 顺便说一下,对于目录,文件大小和分配的大小是相同的。由于它被分配为单个组,因此文件系统优化器不会移动其块。

随着目录的增长,会分配更多的数据块,并且也会最大输出通过相应地调整文件大小来阻止这些块。

因此lsstat将显示目录 inode 的文件大小字段,该字段设置为分配给它的数据块的大小。

答案2

我认为初始的空目录大小取决于文件系统。在我可以访问的 ext3 和 ext4 文件系统上,我还获得 4096 字节的空目录。在某种安装 NFS 的 NAS 上,我得到一个 80 字节的空目录。我无法访问 ReiserFS 文件系统,新创建的空目录大小会很有趣。

传统上,目录是一个在其索引节点(描述文件的磁盘结构)中设置了一个位的文件,表明它是一个目录。该文件充满了可变长度的记录。内容如下/usr/include/linux/dirent.h

struct dirent64 {
    __u64       d_ino;
    __s64       d_off;
    unsigned short  d_reclen;
    unsigned char   d_type;
    char        d_name[256];
};

您可以使用这些值跳过目录文件条目d_off。如果一个条目被删除(unlink()系统调用,由rm命令使用),则d_off前一个条目的值会增加以解决丢失的记录。没有任何记录被“压缩”。最简单的方法可能是根据分配给文件的磁盘块中的字节数来显示分配情况,而不是尝试找出目录文件中的所有条目占多少字节,或者仅计算出目录文件中的多少字节。最后一个条目。

如今,目录具有 B 树或哈希树。我猜想,按块创建目录要么是一个很大的性能改进,要么它们内部有类似于老式目录的“空白空间”,所以很难确定目录的“实际大小”(以字节为单位),特别是一个已经使用了一段时间并且已删除并添加了很多文件的文件。只需显示块数乘以每块字节数就更容易了。

答案3

一个文件可能没有分配块;标志-stols将显示这种差异,而目录将分配一定数量的最小块,因此是默认大小。 (除非你使用的是一些奇特的现代文件系统,将这些概念抛到了九霄云外。)例如:

% mkdir testfoo
% cd testfoo/
% mkdir foodir
% touch foofile
% ln -s foofile foosln
% ls -ld foo*
drwxrwxr-x  2 jmates  jmates  512 Oct  5 19:48 foodir
-rw-rw-r--  1 jmates  jmates    0 Oct  5 19:48 foofile
lrwxrwxr-x  1 jmates  jmates    7 Oct  5 19:48 foosln -> foofile
% ls -lds foo*
8 drwxrwxr-x  2 jmates  jmates  512 Oct  5 19:48 foodir
0 -rw-rw-r--  1 jmates  jmates    0 Oct  5 19:48 foofile
0 lrwxrwxr-x  1 jmates  jmates    7 Oct  5 19:48 foosln -> foofile
% 

请注意,这里的符号链接不占用任何块,尽管专用了七个字节来存储必要的详细信息readlink(2),多么好奇!无论如何,现在让我们填充foofile一两个字节:

% echo >> foofile a
% ls -lds foo*
8 drwxrwxr-x  2 jmates  jmates  512 Oct  5 19:48 foodir
8 -rw-rw-r--  1 jmates  jmates    2 Oct  5 19:49 foofile
0 lrwxrwxr-x  1 jmates  jmates    7 Oct  5 19:48 foosln -> foofile
%

我们可以看到分配的块foofile已经跳转到,8尽管只有两个字节(附加的a和换行符echo)。

文件也可能是稀疏的,这是报告的文件大小与实际内容可能不同的另一种方式,具体取决于与文件交互的工具如何处理稀疏性。

此外,可以增加目录的大小,创建许多名称很长的文件,并检查在创建每个新的长文件名后目录的大小(以及分配的块)发生了什么ls -lds .

相关内容