在Unix里 一切都是文件


UNIX操作系统的设计、用户界面、文化和演变都是建立在它的一套统一的想法和概念上。其中最重要的一点可能是“一切皆文件”,而这个概念被认为是UNIX的灵魂之一。

这一关键设计原则提供了一个统一的范式,用于访问各种输入输出资源:文档、目录、磁盘驱动器、CD-ROM、调制解调器、键盘、打印机、显示器、终端,甚至是一些进程间通信和网络通信。所有这些资源拥有一个通用的抽象,UNIX之父将其称为“文件”。因为每个“文件”都通过相同的API暴露出来,所以你可以使用同一组基本命令来读取和写入磁盘、键盘、文档或网络设备。

这个基本概念实际上有双重含义:

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

在UNIX中,一切都是字节流

那么UNIX中的文件是由什么组成的?文件只不过是可以读取和写入的普通字节的集合。如果你持有一个文件引用(也就是文件描述符),就可以使用相同的一组API进行IO操作,无论设备的类型和底层硬件是什么。

纵观历史,UNIX是第一个对所有IO操作进行统一抽象并提供一小组操作原语的操作系统。当时,大多数操作系统为每种设备或设备家族提供单独的API。一些早期的微型计算机操作系统甚至要求你使用多个用户命令来复制文件——不同大小的软盘需要使用不同的命令!

从程序员和用户的角度来看,UNIX通过字节流的形式暴露了如下的资源:

存储在磁盘上的文档、目录、链接、大容量存储设备(如磁盘驱动器、CD-ROM、磁带、USB)、进程间通信(例如管道、共享内存、UNIX套接字)、网络连接、交互式终端、其他设备(如打印机、图形卡)。用户可以对这些字节流进行读(read)、写(write)、随机访问(lseek)和关闭(close)操作。

统一的API对于UNIX程序来说非常重要:你可以尽情地开发一个处理文件的程序,却不需要知道文件内容究竟是来自哪里,它们可能保存在本地磁盘或网络某处的远程驱动器上,或者是通过网络流入、由用户输入,甚至是由另一个程序在内存中生成。这大大降低了程序的复杂性,并简化了开发人员的学习曲线。这一基本特性让程序开发变得轻而易举(你只需与两个特殊文件打交道:标准输入和标准输出)。

不过需要注意的是,虽然所有文件都提供了统一的API,但某些特定类型的设备可能不支持某些操作。例如,你无法对鼠标设备进行lseek操作,也无法在CD-ROM设备上进行write操作(假设你的CD是只读的)。

在UNIX中,文件系统是统一的命名空间

在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术语中,这个操作叫作挂载(mount)文件系统,而挂载文件系统的位置称为挂载点(mount point)。你可以将挂载文件系统里的所有资源视为全局命名空间的一部分,只要在访问这些资源时使用挂载点作为前缀(例如/backups/myproject-Oct07.zip)。

挂载机制对于建立统一而连贯的命名空间来说至关重要,在这种命名空间中,异构资源可以重叠。而在微软的操作系统中,MS-DOS和Windows也将设备视为文件,但没有将文件系统视为统一的命名空间。它们的命名空间是分区的,每个物理存储位置都被视为一个不同的实体:C:\是第一个磁盘驱动器,E:\是CD-ROM设备,等等。

伪文件系统

在早期,UNIX通过提供全局API并将设备放入统一的文件系统命名空间,极大促进了输入输出资源的集成。这种方法非常成功,以至于越来越多的资源和系统服务被作为文件系统全局命名空间的一部分。Plan 9操作系统开创了这一壮举,现在已经出现在了所有的现代UNIX系统中。

这种方式导致出现了大量的伪文件系统,它们的行为类似于普通文件系统,但可用于访问与传统文件系统不直接相关的资源。例如,你可以使用伪文件系统来查询和控制进程、访问内核或建立TCP连接。这些伪文件系统提供了文件系统语义,用于表示结构化的信息,并为各种对象提供统一的访问方式。伪文件系统(有时也称为虚拟文件系统)通常没有实际的物理存储,它们是基于内存的。

伪文件系统示例:

  • procfs(/proc):proc文件系统包含了一个特殊文件的层次结构,可用于查询或控制正在运行的进程,或通过标准文件条目(主要是基于文本的)来查看内核。
  • devfs(/dev或/devices):devfs将系统上的所有设备显示为一个动态的文件系统命名空间。devfs还负责管理这个命名空间,并直接与内核设备驱动程序对接,实现智能设备管理——包括注册设备和取消注册。
  • tmpfs(/tmp):临时文件系统,在重新启动后内容就会消失。tmpfs旨在提高速度和效率,它的文件系统大小是动态的,并使用了交换空间。
  • portalfs(/p):使用BSD的Portal文件系统,你可以将服务器进程附加到文件系统全局命名空间中,从而通过文件系统来访问网络服务。例如,只需要打开常规文件/p/tcp/ph7spot.com/smtp,应用程序就可以与托管在ph7spot.com上的SMTP服务器发生交互。Portal文件系统有一些神奇之处,因为它在文件系统中提供了套接字语义,可以在标准的UNIX工具(如cat、grep、awk等)中使用——甚至可以直接用在Shell中!
  • ctfs(/system/contract):合约文件系统充当了与Solaris合约子系统的接口。Solaris合约定义了进程或进程组在发生各种类型的事件和故障时的行为——例如如果进程死了,就重启它。Solaris合约为软件管理和各种环境的监控提供了高级功能,如集群故障恢复系统、批处理队列系统和网格计算引擎。

结论

在现代UNIX操作系统中,所有的设备和大多数进程间通信都被视为文件系统层次结构中的文件或伪文件。这种UNIX愿景和设计原则被称为“一切皆文件”,是UNIX保持成功和长青的关键因素之一。它提供了一个强大而简单的抽象,作为系统、工具和社区的构建基础。更重要的是,它提供了强大的集成和基本的组合机制,通过连接工具和应用程序来解决眼前的问题。

尽管“一切皆文件”的理念取得了成功,但有些人对它的普遍适用性持怀疑态度。如果说文件被视为字节流,那么就会缺少对元数据的支持:为了能够正确地处理文件,应用程序必须使用自己的方法来确定文件的类型、结构和语义。此外,为了保留元数据,处理文件的工具必须保持元数据不变(例如图片的XMP信息)。因此,尽管将UNIX文件视为字节流对于基于文本接口的程序交互来说非常有用,但在处理多媒体和二进制文件方面却带来了严重的局限性。

尽管存在局限性,但大多数人仍然承认这个理念所具备的强大威力以及它对操作系统集成产生的巨大影响。自UNIX首次发布以来,研究人员一直在努力推广这一核心理念。例如,Plan 9操作系统率先对系统资源采用了完全整合的方法:不仅是设备和通信渠道,所有的系统接口都使用文件系统来表示。例如,Plan 9的设计者指出,在UNIX系统中,网络设备不能完全被视为常规文件:用于访问它们的套接字具有不同的开放语义,并且位于不同的命名空间中(套接字的主机和端口)。Plan 9的实现证明了我们可以成功地将所有本地和远程设备统一到全局命名空间中。这个概念最终以portalfs的形式回到了UNIX上。

Plan 9的其他创新概念建立在“一切皆文件”的原则之上。例如,Plan 9在统一命名空间的基础上提供了另一层抽象:可以为每个用户或每个进程定制文件系统命名空间,甚至可以动态调整。最后,Plan 9证明了“一切皆文件”这一理念可以在更大的范围内实现。事实上,这一基本概念在现代UNIX操作系统中得到了进一步的扩展。

本文文字及图片出自 InfoQ

余下全文(1/3)
分享这篇文章:

请关注我们:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注