外行解释“一切都是文件”——与Windows有什么不同?

Moh*_*med 43 windows architecture files unix-philosophy

我知道“一切都是文件”意味着即使设备在 Unix 和类 Unix 系统中也有它们的文件名和路径,并且这允许在各种资源上使用通用工具,而不管它们的性质如何。但我无法将其与 Windows 进行对比,这是我使用过的唯一其他操作系统。我读过一些关于这个概念的文章,但我认为对于非开发人员来说,它们有些难以掌握。外行的解释是人们需要的!

例如,当我想将文件复制到连接到读卡器的 CF 卡时,我将使用类似

zcat name_of_file > /dev/sdb
Run Code Online (Sandbox Code Playgroud)

在 Windows 中,我认为读卡器将作为驱动程序出现,我认为我们将做类似的事情。那么,“一切都是文件”的哲学在这里有何不同?

War*_*ung 116

“一切都是文件”有点油嘴滑舌。“一切都出现在文件系统中的某个地方”更接近标记,即使如此,它也更像是一种理想而不是系统设计法则。

例如,Unix 域套接字不是文件,但它们确实出现在文件系统中。您可以ls -l使用域套接字来显示其属性,通过 修改其访问控制chmod,并且在某些 Unix 类型系统(例如 macOS,但不是 Linux)上,您甚至可以cat向/从一个系统发送数据。

但是,即使使用与Unix 域套接字相同的BSD 套接字系统调用创建和操作常规TCP/IP 网络套接字,TCP/IP 套接字也不会出现在文件系统中,¹ 即使没有特别好的理由应该这样做是真实的。

另一个出现在文件系统中的非文件对象的例子是Linux 的/proc文件系统。此功能向用户空间公开了大量有关内核运行时操作的详细信息,主要是作为虚拟纯文本文件。许多/proc条目是只读的,但许多条目/proc也是可写的,因此您可以使用任何可以修改文件的程序来更改系统的运行方式。唉,这里又出现了一个非理想情况:BSD Unixes默认运行没有/proc,而 System V Unixes 暴露的 via/proc比 Linux少得多。

我无法将其与 MS Windows 进行对比

首先,您可以在网上和有关 Unix 的书籍中找到的许多关于文件 I/O 和 Windows 在这方面“损坏”的观点已经过时了。Windows NT修复了很多问题。

现代版本的 Windows 有一个统一的 I/O 系统,就像 Unix,所以你可以通过 TCP/IP 套接字读取网络数据,ReadFile()而不是 Windows 套接字特定的 API WSARecv(),如果你愿意的话。这与Unix 方式完全相同,您可以使用通用read(2)Unix 系统调用或特定于套接字的调用从网络套接字读取数据recv(2)。²

尽管如此,即使在 2021 年,Windows 仍然无法将这个概念提升到与 Unix 相同的水平。 Windows 架构中有许多区域无法通过文件系统访问,或者不能被视为类文件。一些例子:

  1. 驱动程序。

    Windows 的驱动程序子系统与 Unix 的驱动程序子系统一样丰富和强大,但是要编写程序来操作驱动程序,您通常必须使用Windows Driver Kit,这意味着编写 C 或 .NET 代码。

    在 Unix 类型的操作系统上,您可以从命令行对驱动程序执行很多操作。您几乎可以肯定已经这样做了,只要将不需要的输出重定向到/dev/null

  2. 程序间通信。

    Windows 程序不像 Unix 命令行程序那样通过文本流和管道轻松地相互通信。Unix GUI 通常要么构建在命令行程序之上,要么导出文本命令界面,因此同样简单的基于文本的通信机制也适用于 GUI 程序。

  3. 注册表。

    Unix 没有直接等效的 Windows 注册表。相同的信息分散在文件系统中,主要在/etc/proc和 中/sys

如果您没有看到驱动程序、管道和 Unix 对 Windows 注册表的回答与“一切都是文件”有任何关系,请继续阅读。

“一切都是文件”的哲学在这里有何不同?

我将通过扩展上述三点来详细解释这一点。

长答案,第 1 部分:驱动器与设备文件

假设您的 CF 卡读卡器E:在 Windows 和/dev/sdcLinux下显示为。它有什么实际区别?

这不仅仅是一个小的语法差异。

在 Linux 上,我可以说用零dd if=/dev/zero of=/dev/sdc覆盖 的内容/dev/sdc

想一想这意味着什么。在这里,我有一个普通的用户空间程序 ( dd(1)),我要求它从虚拟设备 ( /dev/zero)读取数据,并/dev/sdc通过统一的 Unix 文件系统将读取的数据写入真实的物理设备 ( )。dd不知道它正在读取和写入特殊设备。它也适用于常规文件,或混合设备和文件,我们将在下面看到。

E:在 Windows 上没有简单的方法将驱动器归零,因为 Windows 区分文件和驱动器,因此您不能使用相同的命令来操作它们。你能得到的最接近的方法是在没有快速格式化选项的情况下进行磁盘格式化,它将大部分驱动器内容归零,然后在其上写入一个新的文件系统。如果我不想要一个新的文件系统怎么办?如果我真的希望磁盘只填充零怎么办?

让我们大方一点,接受这个要求,在E:. 要在 Windows 上的程序中执行此操作,我必须调用特殊的格式化 API。?在Linux上,你并不需要编写一个程序来访问操作系统的“格式化磁盘”功能:你只要运行适当的用户空间程序要创建的文件系统类型,不管是mkfs.ext4mkfs.xfs有你,或者是什么。这些程序会将文件系统写入/dev您传递的任何文件或节点。

因为mkfsUnixy 系统上的类型程序不会人为地区分设备和普通文件,所以我可以在我的 Linux 机器上的普通文件中创建一个ext4 文件系统

$ dd if=/dev/zero of=myfs bs=1k count=1k
$ mkfs.ext4 -F myfs
Run Code Online (Sandbox Code Playgroud)

这将创建一个myfs在当前目录中调用的 1 MiB 磁盘映像。然后我可以像挂载任何其他外部文件系统一样挂载它:

$ mkdir mountpoint
$ sudo mount -o loop myfs mountpoint
$ grep $USER /etc/passwd > mountpoint/my-passwd-entry
$ sudo umount mountpoint
Run Code Online (Sandbox Code Playgroud)

现在我有一个 ext4 磁盘映像,其中调用了一个文件my-passwd-entry,其中包含我的用户/etc/passwd条目。

如果我愿意,我可以将该图像炸到我的 CF 卡上:

$ sudo dd if=myfs of=/dev/sdc1
Run Code Online (Sandbox Code Playgroud)

或者,我可以将该磁盘映像打包,邮寄给您,然后让您将其写入选择的介质,例如 USB 记忆棒:

$ gzip myfs
$ echo "Here's the disk image I promised to send you." | 
  mutt -a myfs.gz -s "Password file disk image" \
      you@example.com
Run Code Online (Sandbox Code Playgroud)

这一切在 Linux 上都能实现吗?因为文件、文件系统和设备之间没有人为的区别。Unix 系统上的许多东西要么文件,要么是通过文件系统访问的,因此它们看起来像文件,或者以其他方式看起来足够文件,可以这样对待它们。

Windows 的文件系统概念是一个大杂烩;它区分目录、驱动器和网络资源。共有三种不同的语法,它们都在 Windows 中混合在一起:类 Unix..\FOO\BAR路径系统、驱动器字母如C:和 UNC 路径如\\SERVER\PATH\FILE.TXT. 这是因为它是 Unix、CP/MMS-DOSLAN Manager思想的积累,而不是单一的连贯设计。这就是为什么Windows 文件名中这么多非法字符的原因。

Unix 有一个统一的文件系统,所有的东西都可以通过一个通用的方案访问。对于在 Linux 机器上运行的程序/etc/passwd/media/CF_CARD/etc/passwd、 和之间没有功能差异/mnt/server/etc/passwd。本地文件、外部媒体和网络共享都以相同的方式处理。?

Windows 可以达到与我上面的磁盘映像示例类似的目的,但是您必须使用由非常有才华的程序员编写的特殊程序。这就是为什么Windows 上这么多“虚拟 DVD”类型的程序。核心操作系统功能的缺乏为程序创造了一个人为的市场来填补空白,这意味着你有一群人竞争创建最好的虚拟 DVD 类型的程序。我们在 *ix 系统上不需要这样的程序,因为我们可以使用循环设备挂载 ISO 磁盘映像。

其他工具(如磁盘擦除程序)也是如此,我们在 Unix 系统上也不需要这些工具。想要您的 CF 卡的内容不可挽回地被加扰而不是仅仅归零?好的,/dev/random用作数据源而不是/dev/zero

$ sudo dd if=/dev/random of=/dev/sdc
Run Code Online (Sandbox Code Playgroud)

在 Linux 上,我们不会不断重新发明这样的轮子,因为核心 OS 功能不仅运行良好,而且运行良好,而且被广泛使用。举一个例子,一个典型的引导 Linux 机器的方案涉及使用我上面展示的技术创建的虚拟磁盘映像。?

我觉得公平地指出,如果 Unix 从一开始就将 TCP/IP I/O 集成到文件系统中,我们就不会出现netcatvs socatvs Ncatvs vs mess,其原因与导致Windows 上的磁盘映像和擦除工具激增:缺乏可接受的操作系统设施。nc

长答案,第 2 部分:管道作为虚拟文件

尽管 Windows 起源于 DOS,但它从来没有丰富的命令行传统。

这并不是说,Windows不会一个命令行,或者说,它缺少很多命令行程序。现在 Windows 甚至有一个非常强大的命令外壳,适当地称为PowerShell

然而,缺乏命令行传统会产生连锁反应。您会获得DISKPART在 Windows 世界中几乎不为人知的工具,因为大多数人通过计算机管理 MMC 管理单元进行磁盘分区等。然后,当您确实需要编写分区创建脚本时,您会发现它DISKPART并不是真正由另一个程序驱动的。是的,您可以将一系列命令写入脚本文件并通过 运行它DISKPART /S scriptfile,但它是全有或全无的。在这种情况下你真正想要的是更像GNU 的parted东西,它会接受像parted /dev/sdb mklabel gpt. 这允许您的脚本逐步进行错误处理。

所有这些与“一切都是文件”有什么关系?简单:管道使命令行程序 I/O 成为某种“文件”。管道是单向,不像常规磁盘文件那样随机访问,但在许多情况下,差异无关紧要。重要的是,您可以附加两个独立开发的程序,并让它们通过简单的文本进行通信。从这个意义上说,任何两个以Unix 方式设计的程序都可以进行通信。

在您确实需要文件的情况下,很容易将程序输出转换为文件:

$ some-program --some --args > myfile
$ vi myfile
Run Code Online (Sandbox Code Playgroud)

但是,当“一切都是文件”的理念为您提供更好的方法时,为什么要将输出写入临时文件呢?如果您只想将该命令的输出读入vi编辑器缓冲区,则vi可以直接为您执行此操作。在vi“正常”模式下,说:

:r !some-program --some --args
Run Code Online (Sandbox Code Playgroud)

这会将程序的输出插入到当前光标位置的活动编辑器缓冲区中。在幕后,vi正在使用管道将程序的输出连接到一些代码,这些代码使用与从文件读取相同的操作系统调用。我不会感到惊讶,如果这两种情况:r-也就是说,有和没有的!-都使用相同的通用数据在所有常见的实现读循环vi。我想不出一个很好的理由不这样做。

这也不是 的最新功能vi;它可以追溯到古老的 ed(1)文本编辑器。

这个强大的想法在 Unix 中反复出现。

对于此的第二个示例,请回想我mutt上面的电子邮件命令。我必须将其编写为两个单独的命令的唯一原因是我希望将临时文件命名为*.gz,以便正确命名电子邮件附件。如果我不关心文件名,我可以使用进程替换来避免创建临时文件:

$ echo "Here's the disk image I promised to send you." | 
  mutt -a <(gzip -c myfs) -s "Password file disk image" \
      you@example.com
Run Code Online (Sandbox Code Playgroud)

通过将 的输出gzip -c转换为 FIFO(类似于文件)或/dev/fd对象(类似于文件)来避免临时性。?

对于这个强大的想法出现在 Unix 中的第三种方式,请考虑gdb在 Linux 系统上。这是用于任何用 C 和 C++ 编写的软件的调试器。从其他系统来到 Unix 的程序员看到gdb并几乎总是抱怨它,“哎呀,它太原始了!” 然后他们去寻找一个 GUI 调试器,找到存在的几个调试器中的一个,并愉快地继续他们的工作……通常从未意识到 GUI 只是在gdb下面运行,在它上面提供了一个漂亮的外壳。大多数 Unix 系统上没有竞争的低级调试器,因为程序不需要在该级别上竞争。我们所需要的只是一个良好的低级工具,如果该低级工具通过管道轻松通信,我们都可以将其作为高级工具的基础。

这意味着我们现在有一个记录在案的调试器接口,它允许直接替换gdb. 不幸的是,主要竞争对手gdb 没有走这条低摩擦的道路,但撇开那些狡辩,lldb就像gdb.

要在 Windows 机器上实现相同的功能,可替换工具的创建者必须定义某种正式的插件或自动化 API。这意味着除了非常流行的程序之外不会发生这种情况,因为构建普通的命令行用户界面和完整的编程 API 需要做很多工作。

这种魔法是通过普遍的基于文本的IPC的优雅而发生的。

尽管 Windows 的内核具有 Unix 风格的匿名管道,但很少看到普通用户程序在命令外壳之外将它们用于IPC,因为 Windows 缺乏先在命令行版本中创建所有核心服务,然后在其上构建 GUI 的传统。分别放在上面。这导致在没有 GUI 的情况下无法做一些事情,这就是与 Linux 相比,Windows有如此多的远程桌面系统的原因之一。这无疑是 Linux 成为云操作系统的部分原因,在那里一切都由远程管理完成。命令行界面比 GUI 更容易自动化,这在很大程度上是因为“一切都是文件”。

考虑SSH。你可能会问,它是如何工作的?SSH 将网络套接字(类似于文件)连接到伪 tty at /dev/pty*(类似于文件)。现在,您的远程系统通过一种与 Unix 方式无缝匹配的连接连接到本地系统,如果需要,您可以通过 SSH 连接传输数据

您是否知道这个概念现在有多强大?

从程序的角度来看,管道文本流与文件没有区别,只是它是单向的。程序从管道中读取数据的方式与从文件中读取数据的方式相同:通过文件描述符。FD 绝对是 Unix 的核心;文件和管道在两者上使用相同的 I/O 抽象这一事实应该告诉你一些事情。?

Windows 世界缺乏这种简单文本通信的传统,只能通过COM.NET使用重量级的OOP接口。如果您需要自动化这样的程序,您还必须编写一个 COM 或 .NET 程序。这比在 Unix 机器上设置管道要困难得多。

缺少这些复杂编程 API 的 Windows 程序只能通过诸如剪贴板或文件/保存后跟文件/打开之类的贫乏接口进行通信。

长答案,第 3 部分:注册表与配置文件

Windows 注册表和 Unix 系统配置方式之间的实际差异也说明了“一切都是文件”理念的好处。

在 Unix 类型的系统上,我可以仅通过检查文件从命令行查看系统配置信息。我可以通过修改这些相同的文件来改变系统行为。大多数情况下,这些配置文件只是纯文本文件,这意味着我可以使用 Unix 上的任何工具来操作它们,这些工具可以处理纯文本文件。

在 Windows 上编写注册表脚本并不是那么容易。

最简单的方法是通过一台机器上的注册表编辑器 GUI 进行更改regedit*.reg,然后通过文件将这些更改盲目地应用到其他机器。这并不是真正的“脚本编写”,因为它不允许您有条件地做任何事情:要么全有,要么全无。

如果您的注册表更改需要任何数量的逻辑,那么下一个最简单的选择是学习PowerShell,这相当于学习 .NET 系统编程。就像 Unix 只有 Perl,而您必须通过它完成所有临时系统管理。现在,我是 Perl 的粉丝,但不是每个人都是。Unix 允许您使用任何您碰巧喜欢的工具,只要它可以操作纯文本文件。


脚注:

  1. 计划9固定这个设计失误,经由暴露网络I / O/net虚拟文件系统

    Bash 有一个特性叫做/dev/tcp允许通过常规文件系统函数进行网络 I/O。由于它是 Bash 功能,而不是内核功能,因此它在 Bash 之外或根本不使用 Bash 的系统上是不可见的。这通过反例说明了为什么通过文件系统使所有数据资源可见是一个好主意。

  2. “现代 Windows”是指 Windows NT 及其所有直系后代,包括 Windows 2000、所有版本的 Windows Server 以及从 XP 开始的所有面向桌面的 Windows 版本。我使用该术语来排除基于 DOS 的 Windows 版本,即 Windows 95 及其直接后代、Windows 98 和 Windows ME,以及它们的 16 位前辈。

    您可以通过在后面的操作系统中缺少统一的 I/O 系统来看出区别。你不能ReadFile()在 Windows 95 上传递 TCP/IP 套接字;您只能将套接字传递给 Windows 套接字 API。请参阅 Andrew Schulman 的开创性文章Windows 95: What It's Not深入了解该主题。

  3. 毫无疑问,它/dev/null是 Unix 类型系统上的真正内核设备,而不仅仅是一个特殊的文件名,就像NULWindows 中表面上的等价物。

    尽管Windows试图阻止您创建一个NUL文件,就可以绕过这种保护与纯粹的挂羊头卖狗肉,愚弄Windows的文件名解析逻辑。如果您尝试使用cmd.exe或 资源管理器访问该文件,Windows 将拒绝打开它,但您可以通过 Cygwin 写入文件,因为它使用与示例程序类似的方法打开文件,您可以通过类似的技巧删除它。

    相比之下,Unix 会很乐意让你rm /dev/null,只要你有写访问权限/dev,并让你在它的位置重新创建一个新文件,这一切都没有技巧,因为那个dev 节点只是另一个文件。虽然缺少那个开发节点,但内核的空设备仍然存在;在您通过mknod.

    您甚至可以在其他地方创建额外的空设备 dev 节点:您是否调用它都没有关系/home/grandma/Recycle Bin,只要它是空设备的 dev 节点,它的工作方式与/dev/null.

  4. Windows中实际上有两个高级“格式化磁盘”API:SHFormatDrive()Win32_Volume.Format().

    有两个非常......嗯...... Windows类的原因。第一个要求 Windows 资源管理器显示其正常的“格式化磁盘”对话框,这意味着它可以在任何现代版本的 Windows 上运行,但仅当用户交互式登录时。另一个您可以在没有用户输入的情况下在后台调用,但它直到 Windows Server 2003 才被添加到 Windows。没错,核心操作系统行为一直隐藏在 GUI 之后,直到 2003 年,在一个 Unixmkfs 从第 1 天发布的世界中。

    /etc/mkfs在我的Unix V5的副本,从1974年是4136字节静态链接的PDP-11可执行文件。(Unix直到 1980 年代后期才获得动态链接,所以它不像其他地方有一个大型库来完成所有实际工作。)它的源代码 - 包含在 V5 系统映像中/usr/source/s2/mkfs.c- 是一个完全独立的 457- C行程序。连#include声明都没有!

    这意味着您不仅可以mkfs在高层次上检查什么,还可以使用创建 Unix 的相同工具集对其进行试验,就像40 年前的Ken Thompson一样。用 Windows 试试。今天最接近的是下载DOS 源代码,该源代码2014 年首次发布,您会发现它只是一堆汇编源代码。它只能使用您手头可能没有的过时工具进行构建,最终您将获得自己的 DOS 2.0 副本,该操作系统远不如 1974 年的Unix V5强大,尽管它是在近十年后发布的。

    (为什么要说 Unix V5?因为它是最早的完整的 Unix 系统仍然可用。早期的版本显然已经失去了时间。有一个项目拼凑了一个 V1/V2 时代的 Unix,但它似乎丢失了mkfs,尽管存在上面链接的 V1 手册页证明它一定存在于某个地方,某个时间。要么那些将这个项目放在一起的人找不到mkfs要包含的现存副本,要么我很难找到没有 的文件find(1),该文件也不存在于该系统中. :))

    现在,您可能会想,“我不能直接调用format.com吗?在 Windows 上调用与mkfs在 Unix上调用不一样吗?” 唉,不,它不一样,原因有很多: