修订历史 | ||
---|---|---|
修订版 2.14 | 修订者:esr | |
更多次要更正。 | ||
修订版 2.13 | 修订者:esr | |
次要更正。 | ||
修订版 2.12 | 2010-07-31 | 修订者:esr |
添加波斯语翻译链接。注意 ISA 已过时。 | ||
修订版 1.0 | 1998-10-29 | 修订者:esr |
初始修订版。 |
本文档旨在帮助通过实践学习 Linux 和互联网用户。虽然这是一种获得特定技能的好方法,但有时会在基础知识方面留下奇怪的空白——这些空白会因缺乏对真实情况的良好心智模型而难以创造性地思考或有效地进行故障排除。
我将尝试用清晰、简单的语言描述这一切是如何运作的。演示文稿将针对在 PC 级机器上使用 Unix 或 Linux 的人员进行调整。不过,我通常会简单地称之为“Unix”,因为我将描述的大部分内容在不同的机器和不同的 Unix 变体中都是恒定的。
我假设您正在使用 Intel PC。如果您运行的是 PowerPC 或其他类型的计算机,则细节略有不同,但基本概念是相同的。
我不会重复内容,所以您必须注意,但这也意味着您将从您阅读的每个字词中学习。初次阅读本文时,最好先浏览一下;在消化了您所学到的知识之后,您应该回来并重新阅读几次。
这是一份不断发展的文档。我打算根据用户的反馈不断添加章节,因此您应该回来并定期查看它。
Unix 和互联网基础知识 HOWTO 的新版本将定期发布到 comp.os.linux.help 和 comp.os.linux.announce 以及 news.answers。它们也将上传到各种网站,包括 Linux 文档项目主页。
您可以通过 URL http:http://www.tldp.org/HOWTO/Unix-and-Internet-Fundamentals-HOWTO/index.html 在万维网上查看此文档的最新版本。
如果您对本文档有任何问题或意见,请随时发送邮件给 Eric S. Raymond,地址为 esr@thyrsus.com。我欢迎任何建议或批评。我特别欢迎指向对个别概念进行更详细解释的超链接。如果您发现本文档有错误,请告知我,以便我可以在下一个版本中更正它。谢谢。
您的计算机内部有一个处理器芯片,它执行实际的计算。它具有内部存储器(DOS/Windows 用户称之为 “RAM”,而 Unix 用户通常称之为 “core”;Unix 术语是来自 RAM 由铁氧体磁芯甜甜圈组成时的民间记忆)。处理器和内存位于主板上,主板是您计算机的心脏。
您的计算机有屏幕和键盘。它有硬盘驱动器和光盘 CD-ROM(或可能是 DVD 驱动器),也可能有一个软盘驱动器。其中一些设备由控制器卡运行,这些卡插入主板并帮助计算机驱动它们;另一些设备由主板上直接的专用芯片组运行,这些芯片组执行与控制器卡相同的功能。您的键盘太简单了,不需要单独的卡;控制器内置在键盘底盘本身中。
稍后我们将详细介绍这些设备的工作原理。现在,以下是一些关于它们如何协同工作需要记住的基本事项
计算机机箱内部的所有部件都通过总线连接。从物理上讲,总线就是您插入控制器卡的地方(显卡、磁盘控制器、声卡,如果您有的话)。总线是处理器、屏幕、磁盘和所有其他设备之间的数据高速公路。
(如果您在 PC 连接中看到过对“ISA”、“PCI”和“PCMCIA”的引用,但没有理解它们,这些都是总线类型。ISA 在细微之处除外,与 IBM 1980 年原始 PC 上使用的总线相同;它不再使用。PCI,即外围组件互连,是大多数现代 PC 以及现代 Macintosh 上使用的总线。PCMCIA 是 ISA 的变体,具有较小的物理连接器,用于笔记本电脑。)
处理器驱动着其他一切,但实际上无法直接看到任何其他部件;它必须通过总线与它们通信。它具有真正快速、直接访问权限的唯一其他子系统是内存(核心)。因此,为了使程序运行,它们必须位于核心中(在内存中)。
当您的计算机从磁盘读取程序或数据时,实际发生的情况是处理器使用总线向磁盘控制器发送磁盘读取请求。稍后,磁盘控制器使用总线向处理器发出信号,表明它已读取数据并将其放入内存中的特定位置。然后,处理器可以使用总线来查看该数据。
您的键盘和屏幕也通过总线与处理器通信,但方式更简单。我们稍后将讨论这些。现在,您已经了解了足够多的知识来理解打开计算机电源时会发生什么。
没有程序运行的计算机只是一堆惰性的电子设备。计算机开机后要做的第一件事是启动一个名为操作系统的特殊程序。操作系统的工作是通过处理控制计算机硬件的繁琐细节来帮助其他计算机程序工作。
启动操作系统的过程称为 启动(最初是自举,指的是通过“拉起自己的鞋带”来提升自己的过程)。您的计算机知道如何启动,因为启动指令内置在其芯片之一 BIOS(或基本输入/输出系统)芯片中。
BIOS 芯片告诉它在一个固定的位置查找,通常是在编号最低的硬盘驱动器(启动磁盘)上查找一个名为启动加载程序的特殊程序(在 Linux 下,启动加载程序称为 Grub 或 LILO)。启动加载程序被拉入内存并启动。启动加载程序的工作是启动真正的操作系统。
加载程序通过查找内核、将其加载到内存并启动它来完成此操作。如果您使用 Linux 并看到屏幕上显示“LILO”后跟一堆点,则表示它正在加载内核。(每个点表示它已加载另一个磁盘块的内核代码。)
(您可能想知道为什么 BIOS 不直接加载内核——为什么使用带有启动加载程序的两步过程?嗯,BIOS 不是很智能。实际上它非常愚蠢,Linux 在启动后根本不使用它。它最初是为带有小型磁盘的原始 8 位 PC 编写的,实际上无法访问磁盘的足够部分来直接加载内核。启动加载程序步骤还允许您从磁盘上不同位置启动多个操作系统之一,以防万一 Unix 对您来说不够好。)
内核启动后,它必须环顾四周,找到其余硬件,并准备好运行程序。它通过探测不是普通的内存位置,而是I/O 端口来完成此操作——特殊的总线地址,设备控制器卡很可能在这些地址上监听命令。内核不会随机探测;它对可能在何处找到什么以及控制器在存在时将如何响应有很多内置知识。此过程称为自动探测。
您可能会也可能不会看到任何这些正在发生的事情。在 Unix 系统使用文本控制台的时候,您会看到启动消息在屏幕上滚动,因为系统已启动。现在,Unix 通常将启动消息隐藏在图形启动画面后面。您可以通过按组合键 Ctrl-Shift-F1 切换到文本控制台视图来查看它们。如果这有效,您应该能够使用不同的 Ctrl-Shift 序列切换回图形启动屏幕;尝试 F7、F8 和 F9。
启动时发出的大多数消息都是内核通过 I/O 端口自动探测您的硬件,弄清楚它有哪些可用硬件,并使自身适应您的机器。Linux 内核在这方面非常出色,比大多数其他 Unix 和远比 DOS 或 Windows 好。事实上,许多 Linux 老手认为 Linux 启动时探测的巧妙性(这使得安装相对容易)是它突破免费 Unix 实验的束缚以吸引大量用户的主要原因。
但是,完全加载并运行内核并不是启动过程的结束;这只是第一阶段(有时称为运行级别 1)。在第一阶段之后,内核将控制权交给一个名为“init”的特殊进程,该进程会生成多个内务处理进程。(最近的一些 Linux 使用一个名为“upstart”的不同程序,该程序执行类似的操作)
init 进程的第一个任务通常是检查以确保您的磁盘正常。磁盘文件系统是脆弱的东西;如果它们因硬件故障或突然断电而损坏,那么在 Unix 完全启动之前采取恢复步骤是有充分理由的。稍后当我们谈到 文件系统如何出错时,我们将详细介绍其中的一些内容。
Init 的下一步是启动多个守护进程。守护进程是一个程序,例如打印假脱机程序、邮件侦听器或 WWW 服务器,它们潜伏在后台,等待执行操作。这些特殊程序通常必须协调可能冲突的多个请求。它们是守护进程,因为编写一个持续运行并了解所有请求的程序通常比尝试确保一群副本(每个副本处理一个请求并且全部同时运行)不会互相干扰更容易。您的系统启动的特定守护进程集合可能会有所不同,但几乎总是包括打印假脱机程序(打印机的网关守护进程)。
下一步是为用户做准备。Init 启动一个名为 getty 的程序的副本来监视您的屏幕和键盘(也可能启动更多副本来监视拨入串行端口)。实际上,现在它通常启动 getty 的多个副本,以便您拥有多个(通常为 7 或 8 个)虚拟控制台,您的屏幕和键盘一次连接到其中一个。但您可能看不到任何这些,因为您的控制台之一将被 X 服务器接管(稍后会详细介绍)。
我们还没有完成。下一步是启动各种支持网络和其他服务的守护进程。其中最重要的是您的 X 服务器。X 是一个管理您的显示器、键盘和鼠标的守护进程。它的主要工作是生成您通常在屏幕上看到的彩色像素图形。
当 X 服务器在机器启动过程的最后一部分启动时,它实际上是从先前处于控制状态的任何虚拟控制台接管硬件。那时您将看到一个图形登录屏幕,它是由一个名为显示管理器的程序为您生成的。
当您登录时,您向计算机标识自己。在现代 Unix 系统上,您通常会通过图形显示管理器执行此操作。但也可以使用 Ctrl-Shift 键序列切换虚拟控制台并执行文本登录。在这种情况下,您通过监视该控制台的 getty 实例来调用程序 login。
您使用登录名和密码向显示管理器或 login 标识自己。该登录名在名为 /etc/passwd 的文件中查找,该文件是一系列行,每行描述一个用户帐户。
其中一个字段是帐户密码的加密版本(有时加密字段实际上保存在权限更严格的第二个 /etc/shadow 文件中;这使得密码破解更加困难)。您输入的帐户密码以完全相同的方式加密,并且 login 程序检查它们是否匹配。此方法的安全性取决于以下事实:虽然从您的明文密码到加密版本很容易,但反过来却非常困难。因此,即使有人可以看到您的密码的加密版本,他们也无法使用您的帐户。(这也意味着如果您忘记了密码,则无法恢复密码,只能将其更改为您选择的其他密码。)
成功登录后,您将获得与您正在使用的个人帐户关联的所有权限。您也可能被识别为组的一部分。组是系统管理员设置的命名用户集合。组可以独立于其成员的权限拥有权限。一个用户可以成为多个组的成员。(有关 Unix 权限如何工作的详细信息,请参阅下面关于权限的部分。)
(请注意,虽然您通常会按名称引用用户和组,但它们实际上在内部存储为数字 ID。密码文件将您的帐户名映射到用户 ID;/etc/group文件将组名映射到数字组 ID。处理帐户和组的命令会自动执行转换。)
您的帐户条目还包含您的主目录,即 Unix 文件系统中存储您的个人文件的位置。最后,您的帐户条目还设置您的shell,即 login 将启动以接受您的命令的命令解释器。
成功登录后会发生什么取决于您的登录方式。在文本控制台上,login 将启动 shell,您就可以开始运行了。如果您通过显示管理器登录,X 服务器将启动您的图形桌面,您将能够从中运行程序——可以通过菜单、桌面图标或运行shell的终端模拟器来运行程序。
在启动后和运行程序之前,您可以将计算机视为包含一个进程动物园,所有进程都在等待执行某些操作。它们都在等待事件。事件可以是您按下某个键或移动鼠标。或者,如果您的机器连接到网络,则事件可以是来自该网络的数据包。
内核是这些进程之一。它是一个特殊的进程,因为它控制其他用户进程何时可以运行,并且通常是唯一可以直接访问机器硬件的进程。实际上,当用户进程想要获取键盘输入、写入屏幕、从磁盘读取或写入磁盘或执行除在内存中处理位之外的任何其他操作时,它们必须向内核发出请求。这些请求称为系统调用。
通常,所有 I/O 都通过内核进行,以便它可以调度操作并防止进程互相干扰。允许少数特殊用户进程绕过内核,通常是通过直接访问 I/O 端口来实现的。X 服务器是这方面最常见的示例。
您将通过两种方式之一运行程序:通过 X 服务器或通过 shell。通常,您实际上会同时执行这两种操作,因为您将启动一个模仿旧式文本控制台的终端模拟器,从而为您提供一个从中运行程序的 shell。我将描述当您这样做时会发生什么,然后我将返回到当您通过 X 菜单或桌面图标运行程序时会发生什么。
shell 之所以被称为 shell,是因为它包装并隐藏了操作系统内核。Unix 的一个重要特性是 shell 和内核是独立的程序,通过一小组系统调用进行通信。这使得可以存在多个 shell,以适应不同的界面品味。
正常的 shell 为您提供登录后看到的“$”提示符(除非您已将其自定义为其他内容)。我们不会在这里讨论 shell 语法以及您可以在屏幕上看到的简单内容;相反,我们将从计算机的角度查看幕后发生的事情。
shell 只是一个用户进程,而不是一个特别的进程。它等待您的击键,(通过内核)监听键盘 I/O 端口。当内核看到它们时,它会将它们回显到您的虚拟控制台或 X 终端模拟器。当内核看到“Enter”时,它会将您的文本行传递给 shell。shell 尝试将这些击键解释为命令。
假设您键入“ls”并按 Enter 键来调用 Unix 目录列表器。shell 应用其内置规则来确定您要运行文件中的可执行命令/bin/ls。它发出系统调用,请求内核启动 /bin/ls 作为新的子进程,并通过内核为其提供对屏幕和键盘的访问权限。然后 shell 进入休眠状态,等待 ls 完成。
当 /bin/ls 完成后,它会通过发出退出系统调用来告诉内核它已完成。然后内核唤醒 shell 并告诉它可以继续运行。shell 发出另一个提示符并等待另一行输入。
但是,当您的“ls”正在执行时,可能会发生其他事情(我们将不得不假设您正在列出一个非常长的目录)。例如,您可能会切换到另一个虚拟控制台,在那里登录并开始玩 Quake 游戏。或者,假设您已连接到互联网。您的机器可能在 /bin/ls 运行时发送或接收邮件。
当您通过 X 服务器而不是 shell 运行程序时(即,通过从下拉菜单中选择应用程序或双击桌面图标),与您的 X 服务器关联的任何几个程序都可以像 shell 一样运行并启动程序。我将在这里忽略细节,因为它们既可变又不重要。关键点是,与普通 shell 不同,X 服务器在客户端程序运行时不会进入休眠状态——相反,它位于您和客户端之间,将您的鼠标单击和按键传递给它,并满足其在显示器上指向像素的请求。
您的键盘是一个非常简单的输入设备;之所以简单,是因为它生成的少量数据非常慢(按照计算机的标准)。当您按下或释放一个键时,该事件会通过键盘电缆发出信号,以引发硬件中断。
操作系统的任务是监视此类中断。对于每种可能的中断类型,都会有一个中断处理程序,它是操作系统的一部分,它会存储与中断关联的任何数据(例如您的按键/释放键值),直到可以对其进行处理。
您的键盘的中断处理程序实际执行的操作是将键值发布到内存底部附近的系统区域中。在那里,当操作系统将控制权传递给当前应该从键盘读取的程序时,它将可用于检查。
更复杂的输入设备(如磁盘或网卡)的工作方式类似。早些时候,我提到磁盘控制器使用总线发出信号,表明磁盘请求已完成。实际发生的情况是磁盘引发中断。然后,磁盘中断处理程序将检索到的数据复制到内存中,以供稍后发出请求的程序使用。
每种中断都具有关联的优先级。较低优先级的中断(如键盘事件)必须等待较高优先级的中断(如时钟滴答声或磁盘事件)。Unix 旨在优先处理需要快速处理的事件类型,以保持机器响应平稳。
在操作系统的启动时消息中,您可能会看到对IRQ编号的引用。您可能知道,配置硬件的常见错误方式之一是让两个不同的设备尝试使用相同的 IRQ,而没有完全理解原因。
答案如下。IRQ 是“中断请求”的缩写。操作系统需要在启动时知道每个硬件设备将使用哪个编号的中断,以便它可以将正确的处理程序与每个中断关联起来。如果两个不同的设备尝试使用相同的 IRQ,中断有时会分派到错误的处理程序。这通常至少会锁定设备,有时会严重混淆操作系统,以至于它会崩溃或崩溃。
实际上,它并没有。计算机一次只能执行一项任务(或进程)。但是计算机可以非常快速地更改任务,并欺骗慢速的人类,让他们认为它同时执行多项任务。这称为分时。
内核的工作之一是管理分时。它有一个名为调度程序的部分,该部分在自身内部保留有关动物园中所有其他(非内核)进程的信息。每 1/60 秒,内核中的计时器都会关闭,从而生成时钟中断。调度程序停止当前正在运行的任何进程,将其挂起在原位,并将控制权交给另一个进程。
1/60 秒听起来可能不多。但在今天的微处理器上,它足以运行数万条机器指令,这些指令可以完成大量工作。因此,即使您有很多进程,每个进程都可以在其每个时间片内完成相当多的工作。
在实践中,程序可能无法获得其整个时间片。如果来自 I/O 设备的中断传入,内核实际上会停止当前任务,运行中断处理程序,然后返回到当前任务。高优先级中断的风暴可能会挤压正常处理;这种不良行为称为抖动,幸运的是,在现代 Unix 系统下很难引起抖动。
事实上,程序的运行速度很少会受到它们可以获得的机器时间量的限制(此规则有一些例外,例如声音或 3D 图形生成)。更常见的情况是,当程序必须等待来自磁盘驱动器或网络连接的数据时,会导致延迟。
可以例行支持许多并发进程的操作系统称为 “多任务处理”。Unix 系列操作系统从一开始就为多任务处理而设计,并且非常擅长多任务处理——比 Windows 或旧版 Mac OS 有效得多,后者都是在事后才将多任务处理功能强加于它们,并且做得相当糟糕。高效、可靠的多任务处理在很大程度上使 Linux 在网络、通信和 Web 服务方面更胜一筹。
内核的调度程序负责在时间上划分进程。您的操作系统还必须在空间上划分它们,以便进程不会干扰彼此的工作内存。即使您假设所有程序都试图合作,您也不希望其中一个程序中的错误能够破坏其他程序。您的操作系统为解决此问题而做的事情称为内存管理。
动物园中的每个进程都需要自己的内存区域,作为运行其代码和保存变量和结果的位置。您可以将此集合视为由只读代码段(包含进程的指令)和可写数据段(包含进程的所有变量存储)组成。数据段对于每个进程都是真正唯一的,但如果两个进程正在运行相同的代码,Unix 会自动安排它们共享单个代码段,作为一种提高效率的措施。
效率很重要,因为内存很昂贵。有时您没有足够的内存来容纳机器正在运行的所有程序的全部内容,尤其是当您使用像 X 服务器这样的大型程序时。为了解决这个问题,Unix 使用了一种称为 虚拟内存的技术。它不会尝试将进程的所有代码和数据都保存在内存中。相反,它只保留相对较小的工作集;进程状态的其余部分保留在硬盘驱动器上的特殊交换空间区域中。
请注意,在过去,上一段中的 “有时” 是 “几乎总是” ——内存大小通常相对于正在运行的程序的大小来说很小,因此交换很频繁。如今,内存的价格便宜得多,即使是低端机器也拥有相当多的内存。在具有 64MB 或更多内存的现代单用户机器上,在最初加载到核心后,可以运行 X 和典型的作业组合,而无需进行任何交换。
实际上,上一节过于简化了事情。是的,程序将您的大部分内存视为一个比物理内存更大的大平面地址库,并且磁盘交换用于维护这种错觉。但是您的硬件实际上至少有五种不同的内存,并且当必须调整程序以获得最大速度时,它们之间的差异可能很重要。要真正了解您的机器中发生了什么,您应该了解所有这些内存的工作原理。
这五种内存是:处理器寄存器、内部(或片上)缓存、外部(或片外)缓存、主内存和磁盘。之所以有这么多类型,原因很简单:速度需要花钱。我已按访问时间递增和成本递减的顺序列出这些内存类型。寄存器内存是最快、最昂贵的,可以每秒随机访问约 10 亿次,而磁盘是最慢、最便宜的,每秒可以进行约 100 次随机访问。
以下是反映 2000 年代初期典型台式机速度的完整列表。虽然速度和容量会提高,价格会下降,但您可以预期这些比率将保持相当稳定——正是这些比率塑造了内存层次结构。
大小:13000MB 访问次数:100KB/秒
大小:256MB 访问次数:100M/秒
大小:512KB 访问次数:250M/秒
大小:32KB 访问次数:500M/秒
大小:28 字节 访问次数:1000M/秒
我们不能用最快的内存类型来构建所有东西。这将非常昂贵——即使不是这样,快速内存也是易失性的。也就是说,断电时它会丢失数据。因此,计算机必须具有硬盘驱动器或其他类型的非易失性存储,以便在断电时保留数据。处理器速度和磁盘速度之间存在巨大差距。内存层次结构的中间三个级别(内部缓存、外部缓存和主内存)基本上是为了弥合这一差距而存在的。
Linux 和其他 Unix 系统具有称为虚拟内存的功能。这意味着操作系统表现得好像它具有比实际更多的内存。您的实际物理主内存的行为就像更大的“虚拟”内存空间上的一组窗口或缓存,其中大部分在任何给定时间实际上都存储在磁盘上的一个特殊区域,称为交换区。在用户程序看不到的情况下,操作系统在内存和磁盘之间移动数据块(称为“页面”)以维护这种错觉。最终结果是,您的虚拟内存比真实内存大得多,但速度也不会慢太多。
虚拟内存比物理内存慢多少取决于操作系统的交换算法与程序使用虚拟内存的方式有多匹配。幸运的是,时间上接近的内存读取和写入也倾向于在内存空间中聚集。这种趋势称为局部性,或更正式的名称为引用局部性——这是一件好事。如果内存引用在虚拟空间中随机跳转,您通常必须为每个新引用执行磁盘读取和写入,并且虚拟内存会像磁盘一样慢。但是,由于程序实际上表现出很强的局部性,因此您的操作系统每次引用可以进行相对较少的交换。
经验表明,对于广泛的内存使用模式,最有效的方法非常简单;它被称为 LRU 或 “最近最少使用” 算法。虚拟内存系统根据需要将磁盘块抓取到其工作集中。当物理内存不足以容纳工作集时,它会转储最近最少使用的块。所有 Unix 系统以及大多数其他虚拟内存操作系统都使用 LRU 的微小变体。
虚拟内存是连接磁盘速度和处理器速度的第一座桥梁。它由操作系统显式管理。但是,物理主内存的速度与处理器访问其寄存器内存的速度之间仍然存在巨大差距。外部和内部缓存解决了这个问题,使用的技术类似于我所描述的虚拟内存。
正如物理主内存的行为类似于磁盘交换区域上的一组窗口或缓存一样,外部缓存充当主内存上的窗口。外部缓存更快(每秒 2.5 亿次访问,而不是 1 亿次)且更小。硬件(特别是您计算机的内存控制器)在从主内存中获取的数据块上执行外部缓存中的 LRU 操作。由于历史原因,缓存交换的单位称为行,而不是页面。
但我们还没有完成。内部缓存通过缓存外部缓存的部分内容,为我们提供了最终的有效速度提升。它更快且更小——事实上,它就位于处理器芯片上。
如果您想让您的程序真正快速,了解这些细节很有用。当您的程序具有更强的局部性时,它们会变得更快,因为这会使缓存工作得更好。因此,使程序快速的最简单方法是使其体积小。如果程序没有因大量的磁盘 I/O 或等待网络事件而减速,它通常会以它所能容纳的最小缓存的速度运行。
如果您无法使整个程序变小,那么努力调整速度关键部分,使其具有更强的局部性可能会有所回报。有关执行此类调整的技术细节超出了本教程的范围;当您需要它们时,您将非常熟悉某些编译器,从而自己弄清楚其中的许多细节。
即使您有足够的物理核心来避免交换,操作系统中称为内存管理器的部分仍然有重要的工作要做。它必须确保程序只能更改自己的数据段——也就是说,防止一个程序中的错误或恶意代码破坏另一个程序中的数据。为此,它维护一个数据和代码段表。每当进程请求更多内存或释放内存时(后者通常在进程退出时发生),都会更新该表。
此表用于将命令传递给称为MMU 或 内存管理单元的底层硬件的专用部分。现代处理器芯片将 MMU 直接构建在芯片上。MMU 具有在内存区域周围设置围栏的特殊能力,因此超出范围的引用将被拒绝,并导致引发特殊中断。
如果您看到 Unix 消息显示 “段错误”、“核心已转储” 或类似内容,这正是发生的情况;正在运行的程序尝试访问其段之外的内存(核心)引发了致命中断。这表明程序代码中存在错误;它留下的核心转储是旨在帮助程序员追踪错误的诊断信息。
除了隔离程序访问的内存外,保护进程免受彼此影响还有另一个方面。您还需要能够控制它们的文件访问,以便有缺陷或恶意的程序不会破坏系统的关键部分。这就是 Unix 具有文件权限的原因,我们将在稍后讨论。
您可能知道,计算机上的所有内容都存储为位串(二进制数字;您可以将它们视为许多小的开关)。在这里,我们将解释这些位如何用于表示计算机正在处理的字母和数字。
在我们深入探讨这一点之前,您需要了解计算机的字长。字长是计算机移动信息单位的首选大小;从技术上讲,它是处理器寄存器的宽度,寄存器是处理器用于进行算术和逻辑计算的保持区域。当人们写到计算机具有位大小时(例如,称其为 “32 位” 或 “64 位” 计算机),这就是他们的意思。
现在大多数计算机的字长为 64 位。在不久的过去(2000 年代初期),许多 PC 具有 32 位字长。1980 年代的旧 286 机器的字长为 16 位。旧式大型机的字长通常为 36 位。
计算机将您的内存视为从零开始编号到某个较大值的字序列,该值取决于您的内存大小。该值受您的字长限制,这就是为什么像 286 这样的旧机器上的程序必须经历痛苦的变形才能寻址大量内存的原因。我不会在此处描述它们;它们仍然给老程序员带来噩梦。
整数表示为字或字对,具体取决于处理器的字长。一个 64 位机器字是最常见的整数表示形式。
整数算术接近但不完全是数学二进制。最低位为 1,下一个为 2,然后为 4,依此类推,就像纯二进制一样。但是有符号数以补码表示法表示。最高位是符号位,它使数量为负,并且每个负数都可以通过反转所有位并加一从相应的正值获得。这就是为什么 64 位机器上的整数的范围为 -263 到 263 - 1。第 64 位用于符号;0 表示正数或零,1 表示负数。
某些计算机语言允许您访问无符号算术,它是纯二进制,仅包含零和正数。
大多数处理器和一些语言可以进行浮点数运算(此功能内置于所有最新的处理器芯片中)。浮点数为您提供了比整数更广泛的值范围,并允许您表示分数。完成此操作的方式各不相同,并且相当复杂,无法在此处详细讨论,但总体思路很像所谓的“科学计数法”,在其中可以写成(例如)1.234 * 1023;数字的编码分为尾数 (1.234) 和指数部分 (23) 用于十的幂乘数(这意味着乘以后的数字将有 20 个零,即 23 减去三个小数位)。
字符通常表示为每 7 位一组的字符串,采用称为 ASCII(美国信息交换标准代码)的编码。在现代机器上,128 个 ASCII 字符中的每一个都是八位字节或 8 位字节的低 7 位;八位字节被打包到内存字中,因此(例如)一个六字符的字符串仅占用一个 64 位内存字。有关 ASCII 代码表,请在 Unix 提示符下键入“man 7 ascii”。
前面的段落在两个方面具有误导性。次要的一点是,术语“八位字节”在形式上是正确的,但实际上很少使用;大多数人将八位字节称为字节,并期望字节为 8 位长。严格来说,术语“字节”更通用;例如,曾经有 36 位机器,字节为 9 位(尽管可能永远不会再有了)。
主要的一点是,并非全世界都使用 ASCII。事实上,世界上的大部分地区都不能——ASCII 虽然对于美国英语来说很好,但缺少其他语言用户需要的许多重音字符和其他特殊字符。即使是英国英语也难以处理缺少英镑货币符号的问题。
已经有几次尝试来解决这个问题。所有尝试都使用了 ASCII 没有的额外高位,使其成为 256 字符集中的低半部分。其中最广泛使用的是所谓的“Latin-1”字符集(更正式的名称为 ISO 8859-1)。这是 Linux、旧版本 HTML 和 X 的默认字符集。Microsoft Windows 使用 Latin-1 的变异版本,该版本在正确的 Latin-1 为历史原因而未分配的位置添加了一堆字符,例如左右双引号(有关这造成麻烦的尖锐描述,请参阅 demoroniser 页面)。
Latin-1 处理西欧语言,包括英语、法语、德语、西班牙语、意大利语、荷兰语、挪威语、瑞典语、丹麦语和冰岛语。然而,这仍然不够好,因此出现了一系列 Latin-2 到 -9 字符集来处理希腊语、阿拉伯语、希伯来语、世界语和塞尔维亚-克罗地亚语等内容。有关详细信息,请参阅 ISO 字母汤 页面。
最终的解决方案是一个名为 Unicode 的庞大标准(及其相同的双胞胎 ISO/IEC 10646-1:1993)。Unicode 在其最低的 256 个槽位中与 Latin-1 相同。在 16 位空间中的这些槽位之上,它包括希腊语、西里尔语、亚美尼亚语、希伯来语、阿拉伯语、梵文、孟加拉语、古木基语、古吉拉特语、奥里亚语、泰米尔语、泰卢固语、卡纳达语、马拉雅拉姆语、泰语、老挝语、格鲁吉亚语、藏语、日文假名、整套现代韩文谚文以及统一的中文/日文/韩文 (CJK) 表意文字。有关详细信息,请参阅 Unicode 主页。XML 和 XHTML 使用此字符集。
最新版本的 Linux 使用 Unicode 的一种编码,称为 UTF-8。在 UTF 中,字符 0-127 是 ASCII。字符 128-255 仅在 2 到 4 个字节的序列中使用,用于标识非 ASCII 字符。
当您在 Unix 下查看硬盘时,您会看到一个名为目录和文件的树。通常,您不需要深入了解,但是如果您遇到磁盘崩溃并需要尝试抢救文件,那么了解底层发生的事情会很有用。不幸的是,没有从文件级别向下描述磁盘组织的好的方法,所以我必须从硬件向上描述它。
您的磁盘表面区域(存储数据的位置)被划分为类似飞镖靶的东西——分成圆形磁道,然后将圆形磁道切成扇区。由于靠近外边缘的磁道比靠近磁盘中心主轴的磁道具有更大的面积,因此外磁道中的扇区切片比内磁道中的扇区切片更多。每个扇区(或磁盘块)具有相同的大小,在现代 Unix 系统下,通常为 1 二进制 K(1024 8 位字节)。每个磁盘块都有唯一的地址或磁盘块号。
Unix 将磁盘划分为磁盘分区。每个分区是连续的块跨度,与其他任何分区分开使用,既可以用作文件系统,也可以用作交换空间。分区的最初原因与在磁盘速度慢得多且更容易出错的世界中进行崩溃恢复有关;它们之间的边界减少了磁盘上可能因磁盘上随机坏点而变得无法访问或损坏的部分。如今,更重要的是,分区可以声明为只读(防止入侵者修改关键系统文件)或通过我们在此不讨论的各种方式在网络上共享。磁盘上编号最低的分区通常被特殊对待,作为引导分区,您可以在其中放置要引导的内核。
每个分区要么是交换空间(用于实现虚拟内存),要么是文件系统(用于保存文件)。交换空间分区仅被视为线性块序列。另一方面,文件系统需要一种将文件名映射到磁盘块序列的方法。由于文件会随着时间的推移而增长、缩小和更改,因此文件的数据块不会是线性序列,而是可能分散在其分区各处(从操作系统在需要时可以找到空闲块的任何位置)。这种分散效应称为碎片。
在每个文件系统中,从名称到块的映射是通过称为i 节点的结构来处理的。在每个文件系统的“底部”(编号最低的块)附近都有这些东西的池(最低的几个用于我们在此不描述的内务处理和标记目的)。每个 i 节点描述一个文件。文件数据块(包括目录)位于 i 节点之上(在编号较高的块中)。
每个 i 节点都包含其描述的文件中的磁盘块号列表。(实际上,这只说对了一半,仅对小文件正确,但其余细节在此处并不重要。)请注意,i 节点不包含文件名。
文件名存在于目录结构中。目录结构只是将名称映射到 i 节点号。这就是为什么在 Unix 中,一个文件可以有多个真实名称(或硬链接);它们只是恰好指向同一 i 节点的多个目录条目。
在最简单的情况下,您的整个 Unix 文件系统仅存在于一个磁盘分区中。虽然您会在一些小型个人 Unix 系统上看到这种安排,但这并不常见。更典型的是,它分布在多个磁盘分区中,可能在不同的物理磁盘上。因此,例如,您的系统可能有一个小分区用于存放内核,一个稍大的分区用于存放操作系统实用程序,以及一个更大的分区用于存放用户主目录。
您在系统启动后立即可以访问的唯一分区是您的根分区,它(几乎总是)是您从中启动的分区。它保存文件系统的根目录,即所有其他内容都挂载在其上的顶层节点。
系统中的其他分区必须附加到此根目录,才能使您的整个多分区文件系统可访问。在启动过程的中途,您的 Unix 将使这些非根分区可访问。它会将每个分区挂载到根分区上的目录。
例如,如果您有一个名为/usr的 Unix 目录,它可能是一个挂载点,指向一个分区,该分区包含许多与您的 Unix 一起安装但初始启动期间不需要的程序。
现在我们可以从上到下查看文件系统。当您打开一个文件时(例如,比如,/home/esr/WWW/ldp/fundamentals.xml),会发生以下情况
您的内核从 Unix 文件系统的根目录(在根分区中)开始。它在那里查找一个名为“home”的目录。通常,“home”是另一个地方的大型用户分区的挂载点,因此它将转到那里。在该用户分区的顶层目录结构中,它将查找名为“esr”的条目并提取 i 节点号。它将转到该 i 节点,注意到其关联的文件数据块是目录结构,并查找“WWW”。提取该 i 节点后,它将转到相应的子目录并查找“ldp”。这将把它带到另一个目录 i 节点。打开该 i 节点后,它将找到“fundamentals.xml”的 i 节点号。该 i 节点不是目录,而是保存与文件关联的磁盘块列表。
为了防止程序意外或恶意地踩踏它们不应该踩踏的数据,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 节点。完全关闭执行权限的目录将毫无用处。
有时您会看到一个目录是世界可执行但不是世界可读的;这意味着随机用户可以访问其下的文件和目录,但只能通过知道它们的准确名称(目录无法列出)。
重要的是要记住,目录的读取、写入或执行权限与目录下的文件和目录的权限无关。特别是,对目录的写入访问权限意味着您可以在那里创建新文件或删除现有文件,但不会自动授予您对现有文件的写入访问权限。
最后,让我们看一下登录程序本身的权限。
snark:~$ ls -l /bin/login -rwsr-xr-x 1 root bin 20164 Apr 17 12:57 /bin/login |
这具有我们对系统命令的期望的权限——除了所有者执行位应该在的位置有一个“s”。这是称为“设置用户 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 约定)。在某些现代 Unix 系统(包括 Linux)上,可以通过在目录上设置 set-group-ID (chmod g+s) 来选择后一种行为。
前面暗示过文件系统可能是脆弱的东西。现在我们知道,要访问文件,您必须跳过可能是任意长的目录和 i 节点引用的链。现在假设您的硬盘驱动器出现坏点?
如果幸运的话,它只会破坏一些文件数据。如果不幸的话,它可能会损坏目录结构或 i 节点号,并使系统的整个子树处于不稳定状态——或者,更糟糕的是,导致损坏的结构,该结构以多种方式指向同一磁盘块或 i 节点。这种损坏可以通过正常的文件操作传播,破坏不在原始坏点中的数据。
幸运的是,随着磁盘硬件变得越来越可靠,这种意外情况已经变得非常不常见。尽管如此,这仍然意味着您的 Unix 系统会希望定期检查文件系统的完整性,以确保没有出现任何问题。现代 Unix 系统会在启动时,在挂载每个分区之前,对每个分区执行快速完整性检查。每隔几次重启,它们就会进行更彻底的检查,这会多花几分钟时间。
如果所有这些听起来都像是 Unix 非常复杂且容易出错,那么令人欣慰的是,这些启动时检查通常会在正常问题变得真正灾难性之前捕获并纠正它们。其他操作系统没有这些功能,这会稍微加快启动速度,但当您尝试手动恢复时,可能会让您陷入更严重的困境(并且假设您首先有 Norton Utilities 或其他类似工具的副本...)。
当前 Unix 设计的趋势之一是日志文件系统。这些文件系统安排到磁盘的流量,以便保证它处于一致的状态,可以在系统恢复运行时恢复。这将大大加快启动时的完整性检查速度。
我们已经讨论了程序如何运行。每个程序最终都必须作为字节流执行,这些字节流是计算机机器语言中的指令。但是人类不太擅长处理机器语言;这样做甚至在黑客中也已成为一种罕见的、黑色艺术。
如今,除了内核中少量直接硬件接口支持之外,几乎所有 Unix 代码都以高级语言编写。(该术语中的“高级”是历史遗迹,旨在将这些语言与“低级”汇编语言区分开来,汇编语言基本上是机器代码的薄包装。)
有几种不同类型的高级语言。为了讨论这些语言,您会发现记住程序的源代码(人类创建的可编辑版本)必须经过某种翻译才能变成机器实际可以运行的机器代码,这将很有用。
最常见的语言类型是编译型语言。编译型语言通过一个名为编译器(名称已经很能说明问题了)的特殊程序,被翻译成可运行的二进制机器代码文件。一旦二进制文件生成,您就可以直接运行它,而无需再次查看源代码。(大多数软件都是以编译后的二进制文件的形式交付的,这些二进制文件来自您看不到的代码。)
编译型语言往往具有出色的性能,并且能够最完整地访问操作系统,但也往往难以编程。
C语言,Unix 本身就是用它编写的,是这些语言中最重要的一种(及其变体 C++)。FORTRAN 是另一种编译型语言,仍然在工程师和科学家中使用,但历史更悠久,也更原始。在 Unix 世界中,没有其他编译型语言被主流使用。在 Unix 世界之外,COBOL 被广泛用于金融和商业软件。
过去曾经有很多其他的编译型语言,但它们中的大多数要么已经灭绝,要么严格来说只是研究工具。如果您是一位使用编译型语言的新 Unix 开发人员,那么您极有可能使用 C 或 C++。
解释型语言依赖于解释器程序,该程序读取源代码并将其动态地翻译成计算和系统调用。每次执行代码时,都必须重新解释源代码(并且解释器必须存在)。
解释型语言往往比编译型语言慢,并且通常对底层操作系统和硬件的访问权限有限。另一方面,与编译型语言相比,它们往往更容易编程,并且对编码错误更宽容。
许多 Unix 实用程序,包括 shell 和 bc(1) 以及 sed(1) 和 awk(1),实际上都是小型解释型语言。BASIC 通常是解释型的。Tcl 也是如此。从历史上看,最重要的解释型语言是 LISP(相对于其大多数后继者来说是一项重大改进)。今天,Unix shell 和 Emacs 编辑器内部的 Lisp 可能是最重要的纯解释型语言。
为了帮助您理解互联网是如何工作的,我们将看看当您执行典型的互联网操作时会发生什么——将浏览器指向本文档在 Web 上的主页,该主页位于 Linux 文档项目上。本文档是
http://www.tldp.org/HOWTO/Unix-and-Internet-Fundamentals-HOWTO/index.html |
这意味着它位于主机 www.tldp.org 的 World Wide Web 导出目录下的文件 HOWTO/Unix-and-Internet-Fundamentals-HOWTO/index.html 中。
您的浏览器必须做的第一件事是建立与文档所在机器的网络连接。为此,它首先必须找到 主机 www.tldp.org 的网络位置(“主机”是“主机机器”或“网络主机”的缩写;www.tldp.org 是一个典型的主机名)。相应的地址实际上是一个称为 IP 地址的数字(我们稍后将解释术语“IP”部分)。
为此,您的浏览器查询一个名为域名服务器的程序。域名服务器可能位于您的机器上,但更可能在您的机器与之通信的服务机器上运行。当您注册 ISP 时,您的设置过程几乎肯定会涉及告诉您的互联网软件 ISP 网络上域名服务器的 IP 地址。
不同机器上的域名服务器相互通信,交换和保持更新解析主机名(将主机名映射到 IP 地址)所需的所有信息。您的域名服务器可能在解析 www.tldp.org 的过程中跨网络查询三到四个不同的站点,但这通常发生得非常快(不到一秒)。我们将在下一节详细了解域名服务器。
域名服务器将告诉您的浏览器 www.tldp.org 的 IP 地址是 152.19.254.81;知道了这一点,您的机器将能够直接与 www.tldp.org 交换比特。
将主机名翻译成 IP 地址的程序和数据库的整个网络被称为“DNS”(域名系统)。当您看到对“DNS 服务器”的引用时,这意味着我们刚刚称之为域名服务器的东西。现在我将解释整个系统是如何工作的。
互联网主机名由点分隔的部分组成。域是共享通用名称后缀的机器的集合。域可以存在于其他域内部。例如,机器 www.tldp.org 位于 .org 域的 .tldp.org 子域中。
每个域都由一个权威域名服务器定义,该服务器知道域中其他机器的 IP 地址。权威(或“主”)域名服务器可能具有备份,以防其宕机;如果您看到对 辅助域名服务器 或(“辅助 DNS”)的引用,则指的是其中之一。这些辅助服务器通常每隔几个小时从其主服务器刷新信息,因此对主服务器上的主机名到 IP 映射所做的更改将自动传播。
现在这是重要的部分。域的域名服务器不必知道其他域(包括其自己的子域)中所有机器的位置;它们只需要知道域名服务器的位置。在我们的示例中,.org 域的权威域名服务器知道 .tldp.org 的域名服务器的 IP 地址,但不知道 .tldp.org 中所有其他机器的地址。
DNS 系统中的域像一棵大的倒置树一样排列。顶部是根服务器。每个人都知道根服务器的 IP 地址;它们被硬编码到您的 DNS 软件中。根服务器知道顶级域(如 .com 和 .org)的域名服务器的 IP 地址,但不知道这些域内机器的地址。每个顶级域服务器都知道其直接下方的域的域名服务器在哪里,依此类推。
DNS 经过精心设计,因此每台机器都可以尽可能少地了解树的形状,并且对子树的本地更改可以通过简单地更改一个权威服务器的名称到 IP 地址映射数据库来完成。
当您查询 www.tldp.org 的 IP 地址时,实际发生的情况是这样的:首先,您的域名服务器询问根服务器,告诉它在哪里可以找到 .org 的域名服务器。一旦知道这一点,它就会询问 .org 服务器,告诉它 .tldp.org 域名服务器的 IP 地址。一旦有了这个地址,它就会询问 .tldp.org 域名服务器,告诉它主机 www.tldp.org 的地址。
大多数时候,您的域名服务器实际上不必那么努力工作。域名服务器做了很多缓存;当您的域名服务器解析主机名时,它会将该主机名与结果 IP 地址的关联在内存中保留一段时间。这就是为什么当您浏览到一个新网站时,您通常只会看到浏览器发出的关于“查找”您获取的第一个页面的主机的消息。最终,名称到地址的映射会过期,您的 DNS 必须重新查询——这很重要,这样当主机名更改地址时,您就不会有无效的信息永远保留在那里。如果主机无法访问,您站点的缓存 IP 地址也会被丢弃。
浏览器想要做的是向 www.tldp.org 上的 Web 服务器发送一个如下所示的命令
GET /LDP/HOWTO/Fundamentals.html HTTP/1.0 |
以下是它的工作原理。该命令被制成一个数据包,这是一个比特块,就像电报一样,它被封装了三个重要的东西:源地址(您的机器的 IP 地址)、目标地址(152.19.254.81)和一个服务号或 端口号(在本例中为 80),指示它是一个万维网请求。
然后,您的机器将数据包沿着线路(您与 ISP 或本地网络的连接)发送,直到到达一个称为路由器的专用机器。路由器在其内存中有一个互联网地图——不一定是完整的地图,但它完全描述了您的网络邻域,并且知道如何到达互联网上其他邻域的路由器。
您的数据包在到达目的地途中可能会经过多个路由器。路由器很智能。它们会监视其他路由器确认收到数据包所花费的时间。它们还使用该信息来引导流量通过快速链路。它们使用它来注意何时另一个路由器(或电缆)已从网络中掉线,并在可能的情况下通过找到另一条路由来补偿。
有一个都市传说,说互联网的设计是为了在核战争中幸存下来。这不是真的,但互联网的设计在从不稳定的硬件在不确定的世界中获得可靠的性能方面非常出色。这直接归因于其智能分布在数千个路由器中,而不是集中在少数几个庞大且脆弱的交换机中(如电话网络)。这意味着故障往往是局部化的,并且网络可以绕过它们进行路由。
一旦您的数据包到达目标机器,该机器就会使用服务号将数据包馈送到 Web 服务器。Web 服务器可以通过查看命令数据包的源 IP 地址来判断回复位置。当 Web 服务器返回此文档时,它将被分解成多个数据包。数据包的大小将根据网络中的传输介质和服务类型而变化。
要理解如何处理多数据包传输,您需要知道互联网实际上使用了两个协议,一个堆叠在另一个之上。
较低级别的IP(互联网协议)负责标记单个数据包,其中包含在网络上交换信息的两台计算机的源地址和目标地址。例如,当您访问 http://www.tldp.org 时,您发送的数据包将包含您计算机的 IP 地址,例如 192.168.1.101,以及 www.tldp.org 计算机的 IP 地址 152.2.210.81。这些地址的工作方式与某人给您寄信时您的家庭住址的工作方式非常相似。邮局可以读取地址并确定您在哪里以及如何最好地将信件发送给您,这与路由器对互联网流量的作用非常相似。
较高级别的TCP(传输控制协议)为您提供可靠性。当两台机器协商 TCP 连接时(它们使用 IP 进行协商),接收方知道要将它看到的数据包的确认发送回发送方。如果发送方在某个超时时间内没有看到数据包的确认,它会重新发送该数据包。此外,发送方为每个 TCP 数据包提供一个序列号,接收方可以使用该序列号重新组装数据包,以防它们乱序到达。(如果网络链路在连接期间出现故障,这很容易发生。)
TCP/IP 数据包还包含校验和,以实现对不良链路损坏的数据的检测。(校验和是从数据包的其余部分计算出来的,这样,如果数据包的其余部分或校验和被损坏,重新计算并比较很可能表明存在错误。)因此,从任何使用 TCP/IP 和域名服务器的人的角度来看,这看起来像是在主机名/服务号对之间传递字节流的可靠方式。编写网络协议的人几乎从不需要考虑所有数据包化、数据包重组、错误检查、校验和计算以及在该级别以下进行的重传。
现在让我们回到我们的例子。Web 浏览器和服务器使用应用协议进行通信,该协议运行在 TCP/IP 之上,仅将其用作来回传递字节字符串的方式。此协议称为 HTTP(超文本传输协议),我们已经看到了其中的一个命令——上面显示的 GET。
当 GET 命令发送到服务号为 80 的 www.tldp.org 的 Web 服务器时,它将被分派到侦听端口 80 的服务器守护进程。大多数互联网服务都是由服务器守护进程实现的,这些守护进程除了在端口上等待、监视和执行传入命令之外什么都不做。
如果互联网的设计有一个总体规则,那就是所有部分都应尽可能简单且易于人为访问。HTTP 及其相关协议(如简单邮件传输协议,SMTP,用于在主机之间移动电子邮件)倾向于使用简单的可打印文本命令,这些命令以回车符/换行符结尾。
这在边际上效率低下;在某些情况下,您可以通过使用紧密编码的二进制协议来获得更高的速度。但经验表明,拥有易于人类描述和理解的命令的好处,超过了您可能通过使事情变得棘手和不透明而获得的任何边际效率提升。
因此,服务器守护进程通过 TCP/IP 发送回给您的也是文本。响应的开头看起来像这样(一些标头已被抑制)
HTTP/1.1 200 OK Date: Sat, 10 Oct 1998 18:43:35 GMT Server: Apache/1.2.6 Red Hat Last-Modified: Thu, 27 Aug 1998 17:55:15 GMT Content-Length: 2982 Content-Type: text/html |
这些标头之后将是一个空行和网页文本(之后连接断开)。您的浏览器只是显示该页面。标头告诉浏览器如何显示(特别是,Content-Type 标头告诉浏览器返回的数据实际上是 HTML)。