10. 我的电脑如何将内容存储在磁盘上?

当你在 Unix 下查看硬盘时,你会看到一个由命名目录和文件组成的树状结构。通常你不需要深入了解更多,但是如果你遇到磁盘崩溃并需要尝试恢复文件,那么了解底层原理会很有用。不幸的是,从文件级别向下描述磁盘组织结构并没有好的方法,所以我必须从硬件层面向上描述。

10.1. 低级磁盘和文件系统结构

磁盘的表面区域,即存储数据的区域,被划分得有点像飞镖靶——分成环形的磁道,然后像馅饼一样切分成扇区。由于靠近外边缘的磁道比靠近磁盘中心的轴心的磁道面积更大,因此外磁道比内磁道包含更多的扇区切片。每个扇区(或磁盘块)的大小相同,在现代 Unix 系统下通常为 1 二进制 K(1024 个 8 位字节)。每个磁盘块都有一个唯一的地址或磁盘块号

Unix 将磁盘划分为磁盘分区。每个分区是连续的块范围,与其他分区分开使用,可以用作文件系统或交换空间。分区的最初原因是处理在磁盘速度慢且容易出错的世界中的崩溃恢复;它们之间的边界减少了磁盘中可能因磁盘上的随机坏点而变得无法访问或损坏的部分。如今,更重要的是分区可以声明为只读(防止入侵者修改关键系统文件)或通过我们在此不讨论的各种方式在网络上共享。磁盘上编号最低的分区通常被特殊对待,作为启动分区,你可以在其中放置要启动的内核。

每个分区要么是交换空间(用于实现虚拟内存),要么是文件系统,用于保存文件。交换空间分区只是被视为线性的块序列。另一方面,文件系统需要一种将文件名映射到磁盘块序列的方法。由于文件会随着时间推移而增长、缩小和更改,因此文件的数据块不会是线性的序列,而是可能分散在其分区中的各处(从操作系统需要时可以找到空闲块的任何位置)。这种分散效应称为碎片

10.2. 文件名和目录

在每个文件系统内部,从名称到块的映射是通过一个称为i-节点的结构来处理的。在每个文件系统的"底部"(编号最低的块)附近都有这些东西的池(最低的那些用于我们在此不描述的内务处理和标记目的)。每个 i-节点描述一个文件。文件数据块(包括目录)位于 i-节点之上(在编号较高的块中)。

每个 i-节点都包含一个它描述的文件中的磁盘块号列表。(实际上这是一个半真半假的事实,仅对小文件正确,但其余细节在此并不重要。)请注意,i-节点包含文件的名称。

文件名存在于目录结构中。目录结构只是将名称映射到 i-节点号。这就是为什么在 Unix 中,一个文件可以有多个真实名称(或硬链接);它们只是恰好指向同一个 i-节点的多个目录条目。

10.3. 挂载点

在最简单的情况下,你的整个 Unix 文件系统都位于一个磁盘分区中。虽然你会在一些小型个人 Unix 系统上看到这种安排,但这并不常见。更典型的情况是它分布在多个磁盘分区中,可能在不同的物理磁盘上。例如,你的系统可能有一个小分区用于存放内核,一个稍大的分区用于存放操作系统实用程序,以及一个更大的分区用于存放用户主目录。

系统启动后你唯一可以立即访问的分区是你的根分区,它(几乎总是)是你从中启动的分区。它保存着文件系统的根目录,即所有其他内容都挂载于其上的顶层节点。

系统中的其他分区必须附加到这个根目录,你的整个多分区文件系统才能被访问。在启动过程的中途,你的 Unix 将使这些非根分区可访问。它会将每个分区挂载到根分区上的一个目录上。

例如,如果你有一个名为/usr的 Unix 目录,它很可能是一个挂载点,指向一个包含许多随 Unix 安装但初始启动时不需要的程序的分区。

10.4. 文件如何被查找

现在我们可以从上到下查看文件系统。当你打开一个文件时(例如,假设,/home/esr/WWW/ldp/fundamentals.xml),以下是发生的事情

你的内核从你的 Unix 文件系统的根目录(在根分区中)开始。它在那里查找一个名为 ‘home’ 的目录。通常 ‘home’ 是指向其他地方的一个大型用户分区的挂载点,所以它会去那里。在该用户分区的顶层目录结构中,它将查找一个名为 ‘esr’ 的条目并提取一个 i-节点号。它将转到该 i-节点,注意到其关联的文件数据块是一个目录结构,并查找 ‘WWW’。提取 i-节点后,它将转到相应的子目录并查找 ‘ldp’。这将把它带到另一个目录 i-节点。打开该 i-节点,它将找到 ‘fundamentals.xml’ 的 i-节点号。该 i-节点不是目录,而是保存着与该文件关联的磁盘块列表。

10.5. 文件所有权、权限和安全性

为了防止程序意外或恶意地踩踏它们不应该访问的数据,Unix 具有权限功能。这些功能最初旨在通过保护同一台机器上的多个用户免受彼此的影响来支持分时系统,那时 Unix 主要运行在昂贵的共享小型计算机上。

为了理解文件权限,你需要回忆一下你登录时发生了什么?一节中对用户和组的描述。每个文件都有一个所有者用户和一个所有者组。这些最初是文件创建者的所有者和组;它们可以使用程序 chown(1) 和 chgrp(1) 进行更改。

可以与文件关联的基本权限是“读取”(允许从中读取数据的权限)、“写入”(允许修改它的权限)和“执行”(允许将其作为程序运行的权限)。每个文件都有三组权限;一组针对其所有者用户,一组针对其所有者组中的任何用户,以及一组针对其他所有人。“特权”当你登录时获得的就是对那些权限位与你的用户 ID 或你所属的组之一匹配的文件,或对已向世界公开的文件执行读取、写入和执行的能力。

为了了解这些权限如何交互以及 Unix 如何显示它们,让我们看一下假设的 Unix 系统上的一些文件列表。这是一个

snark:~$ ls -l notes
-rw-r--r--   1 esr      users         2993 Jun 17 11:00 notes

这是一个普通的数据文件。列表告诉我们它由用户 ‘esr’ 拥有,并使用所有者组 ‘users’ 创建。可能我们所在的机器默认将每个普通用户都放在这个组中;你在分时机器上常见的其他组是 ‘staff’、‘admin’ 或 ‘wheel’(出于显而易见的原因,组在单用户工作站或 PC 上不是很重要)。你的 Unix 可能使用不同的默认组,也许是以你的用户 ID 命名的组。

字符串 ‘-rw-r--r--’ 代表文件的权限位。第一个短划线是目录位的占位符;如果文件是目录,它将显示 ‘d’,如果文件是符号链接,它将显示 ‘l’。之后,前三个位置是用户权限,第二个三个是组权限,第三个是其他人(通常称为“世界”权限)的权限。在这个文件上,所有者用户 ‘esr’ 可以读取或写入该文件,‘users’ 组中的其他人可以读取它,而世界上其他所有人都可以读取它。这对于普通数据文件来说是一组非常典型的权限。

现在让我们看一下权限非常不同的文件。这个文件是 GCC,GNU C 编译器。

snark:~$ ls -l /usr/bin/gcc
-rwxr-xr-x   3 root     bin         64796 Mar 21 16:41 /usr/bin/gcc

这个文件属于一个名为 ‘root’ 的用户和一个名为 ‘bin’ 的组;它只能由 root 写入(修改),但可以被任何人读取或执行。这对于预装的系统命令来说是一个典型的所有权和权限设置。‘bin’ 组存在于某些 Unix 系统上,用于将系统命令分组在一起(该名称是一个历史遗留物,是 ‘binary’ 的缩写)。你的 Unix 系统可能使用 ‘root’ 组代替(与 ‘root’ 用户不太一样!)。

‘root’ 用户是数字用户 ID 0 的常用名称,这是一个特殊的、特权帐户,可以覆盖所有特权。Root 访问权限很有用但也很危险;当你以 root 身份登录时,一个输入错误可能会破坏关键的系统文件,而从普通用户帐户执行的同一命令无法触及这些文件。

由于 root 帐户非常强大,因此应该非常小心地保护对其的访问。你的 root 密码是你系统上最关键的安全信息,任何试图攻击你的黑客和入侵者都会试图获取它。

关于密码:不要写下来——并且不要选择容易被猜到的密码,例如你的女朋友/男朋友/配偶的名字。这是一种非常普遍的坏习惯,对黑客很有帮助。一般来说,不要选择字典中的任何单词;有一些程序叫做字典破解器,它们通过运行常见选择的单词列表来查找可能的密码。一个好的技巧是选择一个由一个单词、一个数字和另一个单词组成的组合,例如 ‘shark6cider’ 或 ‘jump3joy’;这将使搜索空间对于字典破解器来说太大。不过,不要使用这些例子——黑客可能会在阅读本文档后期望这样做,并将它们放入他们的字典中。

现在让我们看一下第三种情况

snark:~$ ls -ld ~
drwxr-xr-x  89 esr      users          9216 Jun 27 11:29 /home2/esr
snark:~$ 

这是一个目录(注意第一个权限槽中的 ‘d’)。我们看到它只能由 esr 写入,但可以被其他任何人读取和执行。

读取权限使你能够列出目录——也就是说,查看它包含的文件和目录的名称。写入权限使你能够在目录中创建和删除文件。如果你记得目录包含它包含的文件和子目录的名称列表,这些规则就会有意义。

目录上的执行权限意味着你可以通过该目录来打开其下的文件和目录。实际上,它授予你访问目录中 i-节点的权限。完全关闭执行权限的目录将是无用的。

有时你会看到一个世界可执行但世界不可读的目录;这意味着随机用户可以访问其下的文件和目录,但只能通过知道它们的准确名称(目录无法列出)。

重要的是要记住,目录上的读取、写入或执行权限与目录下的文件和目录的权限无关。特别是,目录上的写入访问权限意味着你可以在那里创建新文件或删除现有文件,但不会自动授予你对现有文件的写入访问权限。

最后,让我们看一下 login 程序本身的权限。

snark:~$ ls -l /bin/login
-rwsr-xr-x   1 root     bin         20164 Apr 17 12:57 /bin/login

这具有我们对系统命令的期望的权限——除了所有者执行位应该在的位置上的 ‘s’。这是称为 ‘set-user-id’ 或 setuid 位的特殊权限的可见表现。

setuid 位通常附加到需要以受控方式向普通用户授予 root 权限的程序。当它在可执行程序上设置时,当程序代表你运行时,你将获得该程序文件的所有者的权限,无论它们是否与你自己的权限匹配。

与 root 帐户本身一样,setuid 程序很有用但也很危险。任何可以破坏或修改 root 拥有的 setuid 程序的人都可以使用它来生成具有 root 权限的 shell。因此,在大多数 Unix 系统上,打开文件进行写入会自动关闭其 setuid 位。许多针对 Unix 安全性的攻击都试图利用 setuid 程序中的漏洞来破坏它们。因此,注重安全的系统管理员对这些程序格外小心,并且不愿安装新的程序。

在上面讨论权限时,我们忽略了几个重要的细节;即,当首次创建文件或目录时,如何分配所有者组和权限。组是一个问题,因为用户可以是多个组的成员,但其中一个(在用户的/etc/passwd条目中指定)是用户的默认组,并且通常拥有用户创建的文件。

初始权限位的故事有点复杂。创建文件的程序通常会指定它要开始使用的权限。但是这些权限将由用户环境中的一个名为 umask 的变量修改。umask 指定在创建文件时要关闭哪些权限位;最常见的值,也是大多数系统上的默认值,是 -------w- 或 002,它关闭了世界写入位。有关详细信息,请参阅 shell 手册页上的 umask 命令文档。

初始目录组也有点复杂。在某些 Unix 系统上,新目录获得创建用户的默认组(这是 System V 约定);在其他系统上,它获得创建它的父目录的所有者组(这是 BSD 约定)。在包括 Linux 在内的一些现代 Unix 系统上,可以通过在目录上设置 set-group-ID (chmod g+s) 来选择后一种行为。

10.6. 事情可能如何出错

早些时候暗示过文件系统可能是脆弱的东西。现在我们知道,要访问一个文件,你必须跳过可能任意长的目录和 i-节点引用链。现在假设你的硬盘出现坏点怎么办?

如果幸运的话,它只会损坏一些文件数据。如果不幸的话,它可能会损坏目录结构或 i-节点号,并使你系统的整个子树处于悬而未决的状态——或者,更糟的是,导致损坏的结构以多种方式指向同一个磁盘块或 i-节点。这种损坏可以通过正常的文件操作传播,破坏不在原始坏点中的数据。

幸运的是,随着磁盘硬件变得更加可靠,这种意外情况已经变得非常不常见。尽管如此,这意味着你的 Unix 系统会希望定期进行文件系统完整性检查,以确保一切正常。现代 Unix 系统在每次启动时,在挂载每个分区之前,都会对每个分区进行快速完整性检查。每隔几次重启,它们就会进行更彻底的检查,这会多花几分钟时间。

如果这一切听起来像是 Unix 非常复杂且容易发生故障,那么知道这些启动时检查通常会在正常问题变得真正灾难性之前捕获并纠正它们,可能会让人感到放心。其他操作系统没有这些设施,这会稍微加快启动速度,但当尝试手动恢复时,可能会让你陷入更严重的困境(并且这是假设你首先有 Norton Utilities 或其他类似工具的副本……)。

当前 Unix 设计的趋势之一是日志文件系统。这些文件系统安排到磁盘的流量,以确保它处于一致状态,以便在系统重新启动时可以恢复。这将大大加快启动时的完整性检查速度。