[译] 在 UNIX 中,一切皆文件

为了有计划的发展架构设计、界面、文化和开发路线,UNIX 系统明确了一系列统一的概念和创想。这几点里面最重要的一点莫过于一句咒语:「一切皆文件」,被广泛认为是 UNIX 的定义之一。

最主要的设计原则是提供一个访问大范围输入/输出资源(包括文件、文件夹、硬盘、CD-ROM、调制解调器、键盘、打印机、显示器、终端机甚至跨进程和网络通讯)的统一的范例。窍门是提供一个所有这些资源的抽象对象,UNIX 之父把这个对象叫做「文件」。因为每个「文件」都由同一个 API[1]暴露,所以你可以用同一套命令来读写/操作磁盘、键盘、文件或网络设备。

这个基本概念有两种含义:

  • 在 UNIX 中,一切都是字节流
  • 在 UNIX 中,文件系统被用作通用的命名空间

在 UNIX 中,一切都是字节流

在 UNIX 中,文件是由什么组成的呢?文件其实和一系列可读写的字节没什么区别。如果你有一个文件的索引(我们称之为「文件描述符[2]」),那么 UNIX 的 I/O 通道就已经准备好了,他们有着同样的一套操作和 API —— 无论设备的类型如何、底层硬件是什么。

纵观历史,UNIX 是第一个把 I/O 抽象成一个统一的概念和一系列原语的系统。那时,大部分操作系统为每一种或一类设备提供不同的 API。一些早期的微型计算机操作系统甚至需要你使用多个命令去拷贝文件 —— 因为每个命令对应指定的软盘大小!

对于大多数程序员和用户来说,UNIX 向他们暴露了:

  • 硬盘中的文件

  • 文件夹

  • 链接

  • 大容量存储设备(例如:硬盘、CD-ROM、磁带、USB 设备)

  • 跨进程通信(例如:管线、共享内存、UNIX 套接字)

  • 网络通信

  • 可交互终端

  • 几乎其他所有设备(例如:打印机、显卡)

对于字节流这种形式你可以:

  • read(读)
  • write(写)
  • lseek(指针移动)
  • close(关闭)

统一的 API 特性对于 UNIX 程序来说是基础也是非常有效的:在 UNIX 中你可以很很轻松地编写一个处理文件的程序,因为不需要关心文件是存储在本地磁盘中、储存在远程网络驱动器上、在互联网中传播、通过用户互动输入,还是通过其他程序在内存中生成。这显著降低了程序的复杂性、减缓了开发者的学习曲线。并且,UNIX 架构的这个基础特性也让程序组合到一起非常简单(你只需要传输两个特殊的文件:标准输入和标准输出)。

最后请注意,当所有的文件提供一致的 API 时,一些特殊类型的设备可能会不支持某些操作。举个很明显的例子,你不可以在鼠标设备上使用 lseek 命令,或在 CD-ROM 设备上使用 write 命令(假设你的 CD 是只读的)。

文件系统有通用命名空间

在 UNIX 里,文件不仅仅是有一致 API 的字节流,而且可以被统一的方式索引:文件系统有着通用命名空间。

全局命名空间和挂载机制

UNIX 的文件系统路径为标签资源提供了一致的全局方案,从而可以忽略他们的物理地址。举几个例子,你可以使用 /usr/local 命令访问一个本地文件夹、/home/joe/memo.pdf 命令访问一个文件、/mnt/cdrom 命令访问 CD-ROM、/usr 命令访问网络驱动器上的一个文件夹、/dev/sda1 命令访问硬盘分区、/tmp/mysql.sock 命令访问 UNIX 域名套接字、/dev/tty0 命令访问终端,甚至使用 /dev/mouse 命令来访问鼠标。这些通用命名空间通常看起来像一个文件层级或文件夹,其实就像前面举的例子,这些只是一个方便的抽象概念,一个文件路径可以引用一切东西:一个文件系统、一个设备、一个网络共享或信道。

命名空间是分层的,所有的资源都可以从根文件夹(/)访问到。你可以使用同样的命名空间来访问多个文件系统:你只是在命名空间的指定的位置(比如 /backups)「连接」了一个设备或文件系统(比如外置硬盘)。用 UNIX 术语来说,这个操作叫做 挂载mounting)一个文件系统,你连接文件系统的命名空间位置叫做 挂载点mount point)。你可以通过给挂载的文件系统中的所有资源添加以挂载点命名的前缀,就像访问通用命名空间的一部分一样,来访问它的所有资源(比如 /backups/myproject-Oct07.zip 这个文件)。

当不同的资源会被明显覆盖的情况下,我刚刚描述的挂载机制在建立一个统一的、明确的命名空间时就至关重要。对比一下这种命名空间和微软操作系统中的文件系统命名空间 —— MS-DOS 和 Windows 把设备视为文件但是 不会把文件系统放在通用命名空间中,它的命名空间是分区的并且每个物理存储地址被视为独特的实体[3]C:\ 是第一个硬盘,E:\ 是 CD-ROM 设备等等。

伪文件系统

早期,UNIX 因为提供全局 API 以及将设备挂载到统一的文件系统命名空间的特性,大幅提升了输入/输出资源的集成度。这个方法是如此成功,以至于从那时开始有一种将更多资源和系统服务暴露为文件系统全局命名空间的趋势。Plan 9 是这种做法的先驱,而现在所有新的 UNIX 系统都这么做了。

这种方法导致产生了许多 伪文件系统,这些系统看起来和一般的文件系统一样,但是可以存取没有直接关联传统文件系统的资源。比如你可以使用伪文件系统来查询控制进程、存取内核内部或建立 TCP 连接。这些伪文件系统具有文件系统语义,可以展示分层信息,并为大部分对象提供了统一存取的方式。伪文件系统有时也被称为虚拟文件系统,特点是没有物理设备也没有备份存储器,只依靠内存来工作。

伪文件系统的例子:

  • procfs (/proc):proc 文件系统包含一个特殊文件层,这个文件层可以用来查询或控制运行中的进程,或通过标准文件入口(大部分基于文本)一窥内核内部文件。
  • devfs (/dev or /devices):devfs 将所有系统中的设备以动态文件系统命名空间呈现。devfs 也可以通过内核设备驱动直接管理这些命名空间和接口,以此来提供智能的设备管理 —— 包括设备入口注册/反注册。
  • tmpfs (/tmp):临时文件系统的内容会在重启时消失,tmpfs 是为速度和效率而设计的,具有动态文件系统大小、用以空间清理的显式回退等特性。
  • portalfs (/p):通过 BSD 门户文件系统,你可以将一个服务器进程连接到文件系统通用命名空间上。这样可以提供明确的通过文件系统对网络服务的存取过程。比如一个 App 可以通过打开一个合规的文件 /p/tcp/ph7spot.com/smtp 来和 ph7spot.com 上的 SMTP 服务器进行交互。门户文件系统很神奇,因为它在文件系统中可以提供套接字语义,还可以被 UNIX 系统工具传输和使用(比如:cat, grep, awk 等等)—— 甚至可以通过 shell 来使用!
  • ctfs (/system/contract):协定文件系统作为一个以文件为基础的接口的 Solaris 协定子系统。Solaris 协定为各种各样的事件和失败情况定义了一个进程或进程组的表现形式 —— 比如,进程停止时重启。 Solaris 协定为诸如群集故障转移软件,批处理排队系统和网格计算引擎等环境中的软件管理和监视提供了非常高级的功能。

能够通过文件系统语义进行管理的系统资源究竟涉及多么大的范围,上面的例子可以让你对有一个清楚的认识了。

结论

在现代的 UNIX 操作系统中,所有设备和大部分进程间通信在文件系统层级都以文件或伪文件的形式查看和管理。「一切皆文件」的 UNIX 基础愿景和设计原则,是 UNIX 成功和长久的关键因素。它提供了一个有力、简单的抽象,使得系统、工具和社区可以在其之上建立。更重要的是它用一种专有的方式来解决问题,那就是为链接工具和应用提供了强有力的集成和基础组合机制。

尽管「一切皆文件」这个比喻很成功,但是一些人或多或少怀疑它的普遍性。当每个文件都被视为字节流时,产生的一个后果就是元数据缺少标准支持:为了合适地处理一个文件,每个应用必须想办法计算文件类型、架构和语义。并且,为了保存元数据,每个处理数据流的工具必须保持元数据不变(比如照片中的 XMP 信息)。因此,尽管 UNIX 文件的一大堆字节的形态对于链接文字界面的程序极度高效,同时也严重限制了多媒体和二进制应用的组合。

尽管它有它的限制,但很多人也承认这个比喻的影响力,和它在操作系统一体化上的效果。自从 UNIX 第一次发布以来,研究者们持续推进这一中心思想。比如 Plan 9 操作系统倡导一个将系统资源完全集成的方法:Plan 9 愿景的基础就是这样的目标 —— 不仅仅设备和信道,而是将 所有系统接口通过文件系统代表。比如 Plan 9 的设计人员注意到在 UNIX 中,网络设备不能 完全地被视为合格的文件:它们通过套接字存取,而套接字有特有的打开语义并且属于一个不同的命名空间(因特网套接字的主机和端口)。Plan 9 实现并且证明了,你可以在一个全局命名空间里成功的统一所有本地和远程设备。这个想法最终以 portalfs 的形式在 UNIX 中实现。

其他来源于 Plan 9 的创新的概念也是基于「UNIX 中,一切皆文件」原则创建的。比如 Plan 9 在统一命名空间设计之上提供了另一个抽象层:文件系统命名空间可以被每个用户、每个进程自定义,甚至动态调整[4]。最后,Plan 9 证明了「UNIX 中,一切皆文件」这个比喻,可以被在更大的层面上实现。事实上,这个基础概念在现代 UNIX 操作系统中正被继续发扬光大[5]

参考文献

  • 一本了不起的书 —— 《The Art of UNIX Programming》,Eric S. Raymond 著。
  • 《The Elements of Operating-System Style》和《Problems in the Design of UNIX》两章对本文有很大帮助。
  • 《10 Things I Hate About (U)NIX》,David Chisnall 著。
  • 「Linux 情报项目」中的挂载定义。
  • Wikipedia 上的 《UNIX File Types》。
  • 《Understanding UNIX Concepts》,USAIL (UNIX System Administration Independent Learning) 著。
  • 《文件系统层级标准》。
  • 《proc 文件系统》,Redhat 出品。
  • 《BSD 系统下的模块化用户模式文件系统》。
  • 《Self-Healing in Modern Operating Systems》,Michael W 著。帮你更深入地理解 Solaris 协议子系统。

  1. 想知道更多关于 UNIX 操作系统的背景故事,请阅读维基百科入口(译者注:需科学上网)。
  2. 文件描述符只是存取一个文件的抽象键,文件描述符通常是整型值并且关联到一个打开的文件。
  3. 了解详情请查看维基百科(译者注:原文没有地址,自行搜索吧)
  4. 这个概念最终以 unionfs 的形式在 UNIX 中实现
  5. 需要这个领域当前活动的一些例子,请查看 unionfs、portalfs 和 objfs 获取实例。

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

猜你喜欢

转载自juejin.im/post/5b652d346fb9a04fc03129e6
今日推荐