让 Inode 正常工作

作者:Clay J. Claiborne, Jr.

事情始于一个电话。我们能否构建一台 Linux 机器,它可以挂载和读取 DEC 驱动器,并通过 Samba 将数据提供给 NT 工作站?答案是“我需要回复您。” 因为来电者是通用动力公司(General Dynamics,这家公司制造了美国海军的大部分核潜艇,以及许多其他东西),而我们是一家营利性的 Linux 公司,我计划尽快回复他们。

Cosmos Engineering Company 自 1984 年以来一直为工业界构建定制计算机系统。1996 年,我们专注于构建 Linux 系统。同年,我们推出了 Linux on a Disk。第二年,我们成为红帽硬件合作伙伴的创始成员。在通用动力公司找到我们时,我们已经使用 Linux 解决问题几年了。这完全符合我们的专业领域。

DEC 驱动器是 Quantum 9GB SCSI-2 驱动器,带有一个使用 UFS 文件系统格式化的 OSF 分区。虽然当时我不确定这一点,但我确实知道 Linux 内核黑客一直在吞噬新的文件格式和分区类型,速度比 Carlie 在Linux Journal派对上发放免费饮料券还快。例如,在 2.2.15 和 2.3.99pre9 之间,支持的外部分区类型数量从 3 个增加到 15 个。UFS 自 2.0.XX 以来就已在内核中。但是,UFS 有不同的“变种”,不是吗?在向 2.4.0-use-me-1 迈进的过程中,这些变种的数量也在增长。

所以我查看了最新的实验性内核树,当时是 2.3.99pre9。是的,现在支持七种不同的 UFS 类型,尽管没有明确提及 DEC,并且刚刚添加了对 OSF 分区的支持。基于此,我回复了通用动力电子系统公司的 John Loeffler 一个暂定的“是”,并表示“是否可以获得一个样品驱动器?” 以便我们可以给他们一个明确的答复。通过一次 FedEx 运送,我正在构建最新的实验性内核,其中启用了所有可能考虑工作的分区类型和文件系统。

正如我从一开始就怀疑的那样,以只读方式挂载驱动器作为 OSF 分区类型和 UFS 类型 SUN 文件系统 似乎 可以工作。样品磁盘上有三个文件:rc.local、hosts 和一个相当大的 tar 包——oilpatch.tar。我们按照他们的要求传真了这两个短文件的内容,结果 Cosmos Engineering Company 获得了合同,为手头的任务构建一系列定制的 Cosmos 500 Linux 服务器。

这项任务是构建 Linux 服务器,每台服务器可以支持四个 9GB SCSI DEC OSF 驱动器,并允许网络连接的 NT 工作站读取数据。这项任务后来扩展到包括删除文件和目录的功能。好的,没问题,内核 2.4.0-test1 具有对 UFS 的实验性写入支持。硬件本身没什么特别的。每台服务器都是一台 500MHz 奔腾 III,围绕 ASUS i440BX ATX 主板构建,配备 128MB 内存,安装在一个中塔式 ATX 机箱中。Red Hat Linux 6.1 安装在 18GB IBM 2Ultra SCSI 系统驱动器上。使这个系统特殊的是它读取外部磁盘格式的能力。这种能力需要创建一个特殊的内核。

与一家习惯于分包驱逐舰部件的公司进行交易涉及的文书工作,嗯,令人印象深刻。我们从未做过涉及如此多政府法规的合同。但话又说回来,我们也从未做过一份合同,其中包含一份单独的合同范本,规定如何处理超过 50 万美元的成本超支。

第一个样品磁盘以及随后的一些其他磁盘显然都包含虚拟数据。我们永远不知道真实数据是什么。无论它是什么,一定有很多,而且他们一定非常希望将数据传输操作保留在内部,才走了他们所走的这条路。我们确实知道最终用户是美国政府,并且在工作中也透露出这意味着军方。后来,当我帮助他们排除一些 SCSI 终端问题时,我被告知 SCSI 驱动器被封装在可以插入到坚固耐用的四盘位托架中的容器中,“我们在所有设备中使用”。这里的“所有设备”指的是核潜艇、驱逐舰和其他武器系统。我也知道,随着对李文和博士的迫害全面展开,以及洛斯阿拉莫斯丢失了一块装满核机密的笔记本电脑硬盘,政府内部对保护国家军事数据非常关注。也许这与隐含的大规模数据迁移无关。这纯粹是猜测。我不知道我们的服务器被用来做什么,而且我也不想知道。在与他们交谈时,我总觉得他们可以告诉我,但那样他们就不得不,嗯,你知道的。

合同签订后,我们开始构建和测试第一批服务器。当我们发现从 DEC 驱动器复制长文件时存在问题时,该项目遇到了障碍。文件在 96KB 后被截断。那可不行。为什么是 96KB?我想知道的就是这个。

这个问题的进一步特点是,长文件,例如内核树 tar 包,可以复制到 DEC 驱动器,然后在该会话中完整地复制回来。但是,如果系统关闭并重新启动,然后从 DEC 驱动器复制回内核 tar 包,则结果的长度是正确的,但内容是垃圾。我怀疑这与内存中和磁盘上 inode 的管理方式有关。

这开始了我的漫长 inode 之旅,而这才是这个故事的真正主题。以前我听说过 inode。我知道它们是与文件相关的磁盘结构。我知道我总是需要有足够的空闲 inode。曾经遇到过有 4GB 空闲空间,但仍然收到“设备上没有空间”的消息吗?没有空闲 inode。inode 对我来说在很大程度上是一个谜。在接下来的几周里,我学到的关于 inode 的知识比我能记住的还要多。

Inode 是定义文件的小数据结构。在 ext2 文件系统中,一个 inode 长 128 位。方便的是,以下所有内容都适用于 Linux ext2 文件系统和 DEC UFS 文件系统。Inode 是一个信息表,包括文件的所有者、文件权限、创建和修改日期、文件大小,最重要的是实际文件数据的位置。实际上,除了文件名之外的所有内容。文件名对于文件来说是偶然的。它在那里是为了我们的方便。它是一个目录条目。假设你想听一首歌。所以你双击 The_Train_They_Call_the_City_of_New_Orleans.mp3。这是一个目录条目,它指向一个 inode,该 inode 知道音乐的存放位置以及谁可以播放它。同一个 inode 可能有其他目录条目。这就是所谓的硬链接。一个文件可以有多个名称,但只有一个 inode。这就是为什么 Sun 的人说,“Inode 就是文件”。每个文件,包括设备、目录和软链接,都需要一个 inode。

Inode 是一种有限的资源。当你创建一个文件系统时,你决定你创建的 inode 数量,就这样了。如果你因为在一个小的 inode 表的分区上放置了大量的小文件而用完了 inode,即使你有千兆字节的空闲空间,你也是 SOL。你或许可以使用指向另一个分区上的子目录的符号链接来欺骗一个在特定分区上空间不足的系统。但是你无法欺骗一个已经用完空闲 inode 的系统,至少在你删除某些东西并释放出一个 inode 之前是无法欺骗的。软链接是指向其他目录条目的文件,需要一个 inode。

Inode 是数据结构,但它们通常存储在磁盘上,并读取到内存中以供参考和修改。

查看 inode 数据结构,很容易理解为什么 96KB 很重要。inode 中的第一个数据块与时间、日期和文件所有权等有关。然后,在偏移量 0x28 处,我们得到了我认为的“好东西”——文件数据在磁盘上的位置。哦,我是否提到过不同类型的文件有不同类型的 inode 结构,而我们目前只讨论 DEC ufs 常规文件的 inode 结构?

在 ext2 或 DEC UFS 32 位文件系统数据结构中,一个四字节的字指向文件系统中许多可能的数据块之一。因此,本节的前 48 个字节由 12 个四字节指针组成,指向文件数据的前 12 个块。在这种文件系统中,每个数据块长 8KB。这些被称为直接块,因为它们的地址可以直接存储在 inode 中。这也意味着可以更快地访问小型文件(96KB 或更小)。对于较大的文件,inode 中的第 13 个地址块不是指向另一个 8KB 的数据块,而是指向一个 8KB 的地址块,或 2048 个其他 8KB 数据块的地址。这被称为间接块。对于更大的文件,inode 中的第 14 个地址块指向一个 8KB 的块,其中包含 2048 个 8KB 块的地址,每个 8KB 块又包含 2048 个数据地址。这被称为双重间接块。这允许非常大的文件。

我的问题是 Linux 只能读取 DEC 文件数据的直接块。尝试读取需要使用间接块寻址的文件失败,并显示“尝试访问超出设备末端”错误。

解决这个问题需要两件事:1) 清楚地了解磁盘上数据的结构,最重要的是 inode 结构,以及地址信息的存储方式;2) 清楚地了解操作系统正在如何处理它在磁盘上找到的数据,最重要的是它是如何读取 inode 的。这两者我都不具备。

但是,我确实得到了 Linux 社区的帮助。很早的时候,我就告诉我的用户组 Linux Users Los Angeles 的成员我们正在做的工作以及我们面临的挑战。许多人提供了见解和建议。Christopher Smith 走得更远。他给我发了一封电子邮件,“如果你想把这些驱动器之一和一个 SCSI 控制器送来,我会在我充裕的空闲时间里尝试摆弄它。” 我给他送去了一台原型服务器和一份我们从通用动力公司收到的样品驱动器之一。

他的帮助极大地澄清了问题以及如何解决它。Dan Kegel 建议我发布到 linux-kernel 列表。这给了我这样做的勇气。他还为我找到了 linux-fsdevel 列表。我从这两个列表中的人们那里得到了很多帮助。

Peter Swain 写道,“看起来你的间接块处理被扭曲了,要么是由于字节序问题,要么是 64 位问题。DEC 很可能在那里有一个不符合标准的实现,但你应该符合它,至少作为一个挂载选项。” Jim Nance 也认为,“这听起来像是某种 32/64 位问题,或者可能是某种字节顺序问题。” 他还建议我们使用内核的用户模式端口进行调查,并向我们发送了有关如何获取它的信息。Tru64 QMG Performance Engineering 的 Peter Rival 建议我们联系 Mission Critical Linux 的 Marcus Barrow,他形容 Marcus Barrow 是“这里直到他去 MCL 之前的 UFS 工程师”。

Marcus Barrow 回复我的电子邮件说,“我很乐意看看这个问题。” 他警告说,“避免使用 Linux 写入所有三个磁盘。你的文件系统可能会损坏。” 我们已经在使用克隆驱动器。他广泛地写了关于这个问题,说

首先,我认为问题可能与处理 8K/1K 块/片段问题有关。再想一下,我更困惑了。我不明白为什么你可以读取直接块,但不能读取间接块。特别是当 Linux 可以读取它自己的间接块时。

Lyle Seaman 指出,“如果你有来自 /usr/include/fs/* 的相关文件,你的任务会简单得多,适用于该操作系统……它们一定在某个人的博物馆里。” 我们从这两个列表上的 Linux 用户和开发者那里得到的支持对于完成这项工作至关重要。

我们也有内核源代码,这正是这次旅行成为可能的原因。自由软件意味着了解系统在做什么。它也意味着能够修改它。我打印了一堆文件,比如 linux/fs/ufs/inode.c 和已知的相关文件,并开始查阅书籍。在整个过程中,我在开源社区中找到了一些非常好的材料,解释文件系统和喜欢它们的软件。我在这里提到两个关于 Linux 文件系统的教程:

  • “第二扩展文件系统的设计与实现”,作者:Rémy Card,Laboratoire MASI—Institut Blaise Pascal,电子邮件:card@masi.ibp.fr;Theodore Ts'o,麻省理工学院,电子邮件:tytso@mit.edu;以及 Stephen Tweedie,爱丁堡大学,电子邮件:sct@dcs.ed.ac.uk khg.redhat.com/HyperNews/get/fs/ext2intro.html

  • “Linux 内核”,作者:David A. Rusling (david.rusling@arm.com),第 9 章:文件系统,www.linuxdoc.org/LDP/tlk/tlk.html

因此,至少在理论上,我了解操作系统在做什么,因为我有我正在运行的源代码以及修改它和创建新二进制文件的方法。对于 Linux 系统来说,这是很平常的事情。

为了完全理解文件系统的结构,我必须能够读取磁盘上的数据并理解它是如何组织的。我需要了解目标驱动器上的 inode 的真实结构。我需要读取 inode 并查看间接块是否以及如何指向文件的丢失数据。

到那时,inode 对我来说已经变得非常真实。我设计了一个计划来捕获一个 inode,即行为不端的 oilpatch.tar 的 inode。因为我可以挂载分区,然后使用内核中的实验性写入代码以读/写方式重新挂载它,所以我可以更改 oilpatch.tar 的所有权。所以我这样做了。

我知道我感兴趣的区域会在驱动器的开头附近,所以我使用如下命令将该区域复制到一个文件中

dd if=/dev/sda of=root-patch bs=1k count=100000

然后我挂载了 DEC 分区,并将 oilpatch.tar 的所有者从 root (ID 0) 更改为 nobody (ID 99),卸载它,并使用以下命令创建了另一个文件

dd if=/dev/sda of=nobody-patch bs=1k count=100000
取这两个文件的差异揭示了文件中的某个偏移量处的字节值从 0 变为 99。已知四字节文件所有者 ID # 位于 inode 中的某个偏移量处,因此将正确的数字插入到 dd 命令中,例如
dd if=/dev/sda of=copy_of_inode_for_oilpatch.tar
  bs=128 count=1 skip=offset_from_front
将 inode 写入文件。现在我可以详细检查 inode 了。我可以打印出来,读取它的隐藏消息并找到文件数据。幸运的是,oilpatch.tar 结果是一个可预测结构的文本文件。文本块像拼图游戏的碎片一样组合在一起,当你得到正确的顺序时,它非常明显。

我发现的是,前 12 个指针指向文件的前 12 个块,并且该块的前四个字节指向第 13 个指针,该指针指向文件的第 13 个块。指针块的接下来的四个字节指向文件数据的第 14 个块,依此类推。

它的工作方式没有任何奇怪之处。没有奇怪的“大端序、小端序”问题。完全没有意外的位移位。一切都非常整洁,非常直接。就像我会做的那样。事实上,与 Linux 在 ext2 文件系统中所做的方式相同。也与内核 UFS 代码期望的方式相同,但这无助于解释为什么它不起作用。

我可能会花费大量时间去寻找一个不存在的问题。慢慢地,我得出的结论是问题根本不在内核 UFS 代码中。inode 正在按预期读取。DEC UFS 应该像 ufs_type=sun 一样读取。事实上,UFS 代码已经存在一段时间了,所以很难相信它已经坏了。问题出在一个更深的层次:在 Linux 虚拟文件系统 (VFS)——在当时的 2.4.0-test1 内核和 UFS 代码之间的通信中,这是由上层代码的更改引起的。指向第一个间接块的指针没有正确地传递给 VFS,这破坏了 UFS 代码。

对这个论点的测试也成为了解决我问题的方法。我首先使用 2.3.99,然后使用 2.4.-test 系列内核,因为它支持 2.2 生产系列内核中缺少的 OSF 分区。如果我是对的,并且 UFS 代码只是最近才被破坏,那么 2.2 内核应该能够毫无问题地读取 DEC 驱动器,前提是它也可以处理分区类型。

好吧,将 OSF 分区代码从 2.4 内核源代码反向移植到 2.2.16 很容易,即使对我来说也是如此,并且生成的 2.2.16 内核对 DEC OSF 驱动器做的一切都正确,包括整天读写大文件。由于 2.2.16 在生产环境中是更好的选择,并且我有一个针对这项工作的有效解决方案,我们能够继续前进并完成工作。

我们开始向通用动力公司及其客户运送安装了破解版 2.2.16 内核的机器,他们正在用这些机器做他们正在做的事情。我向 linux-kernel 和 linux-fsdevel 列表报告了 2.4.0-test1 内核中的这个错误。我说,“不幸的是,我发现了一个错误。” Alan Cox 回复说,“发现错误是好消息。很有可能,否则在 2.4.0 之前不会发现这个错误。”

故事大致就是这样。后来,当第一批系统到达现场时,Windows 2000 工作站处理长文件名时出现问题,但 Samba 团队一直紧跟微软的最新伎俩,因此升级到最新的 Samba 版本解决了这个问题。

他们还需要在具有特定 SCSI ID 的 DEC 驱动器和挂载点之间建立静态关系,而不管安装了多少 DEC 驱动器。由于 Linux 喜欢按照找到 SCSI 硬盘驱动器设备的顺序分配 sda、sdb、sdc,因此必须创建一种动态的驱动器挂载方法。

最初的 Tekram 控制器不太适合驱动外部坚固型四盘位托架,因此被 Adaptec 取代。

这些是在此类工作中正常的初期小问题。这里的真正任务是让这些 inode 正常工作。

Making Inodes Behave
Clay J. Claiborne, Jr. (cjc@linuxbeach.net) 是 Cosmos Engineering Company 的首席执行官。他断断续续地在计算机行业工作了 30 年。自 1995 年以来,他一直是 Linux 爱好者。1996 年,他开发了在硬盘上预装 Linux 并生产 Linux on a Disk 的概念。他还在洛杉矶城市学院教授 Linux,并且是 Linux Users Los Angeles (LULA) 的主席。
加载 Disqus 评论