为什么/dev/null是一个文件?为什么它的功能不以简单的程序来实现呢?

为什么/dev/null是一个文件?为什么它的功能不以简单的程序来实现呢?

我试图理解 Linux 上特殊文件的概念。然而,/dev据我所知,当一个特殊的文件的功能可以通过几行 C 语言来实现时,拥有一个特殊的文件似乎很愚蠢。

此外,您可以以几乎相同的方式使用它,即通过管道进入null而不是重定向到/dev/null.将其作为文件保存有什么具体原因吗?将其作为文件不会导致许多其他问题,例如太多程序访问同一文件吗?

答案1

除了使用字符专用设备的性能优势之外,主要优势是模块化。 /dev/null 几乎可以在任何需要文件的上下文中使用,而不仅仅是在 shell 管道中。考虑接受文件作为命令行参数的程序。

# We don't care about log output.
$ frobify --log-file=/dev/null

# We are not interested in the compiled binary, just seeing if there are errors.
$ gcc foo.c -o /dev/null  || echo "foo.c does not compile!".

# Easy way to force an empty list of exceptions.
$ start_firewall --exception_list=/dev/null

在这些情况下,使用程序作为源或接收器将非常麻烦。即使在 shell 管道情况下,stdout 和 stderr 也可以独立重定向到文件,这对于作为接收器的可执行文件来说很难做到:

# Suppress errors, but print output.
$ grep foo * 2>/dev/null

答案2

公平地说,这不是一个常规文件本身;它是字符特殊装置:

$ file /dev/null
/dev/null: character special (3/2)

它作为设备而不是文件或程序运行意味着重定向输入或输出的操作更简单,因为它可以附加到任何文件描述符,包括标准输入/输出/错误。

答案3

我怀疑为什么与塑造 Unix(以及随后的 Linux)的愿景/设计以及由此产生的优势有很大关系。

毫无疑问,不启动额外的进程会带来不可忽视的性能优势,但我认为还有更多的好处:早期的 Unix 有一个“一切都是文件”的比喻,如果你看一下,它有一个不明显但优雅的优势它是从系统角度,而不是 shell 脚本角度。

假设您有null命令行程序和/dev/null设备节点。从 shell 脚本的角度来看,该foo | null程序实际上是真正的有用方便的,而且foo >/dev/null打字时间要长一点,而且看起来很奇怪。

但这里有两个练习:

  1. 让我们null使用现有的 Unix 工具和/dev/null- easy:来实现该程序cat >/dev/null。完毕。

  2. 你能实施/dev/nullnull

你是完全正确的,只是丢弃输入的 C 代码是微不足道的,所以它可能还不明显为什么有一个可用于该任务的虚拟文件很有用。

考虑:几乎所有编程语言已经需要处理文件、文件描述符和文件路径,因为它们从一开始就是 Unix 的“一切都是文件”范例的一部分。

如果您拥有的只是写入标准输出的程序,那么该程序并不关心您是否将它们重定向到吞下所有写入的虚拟文件,或者将管道重定向到吞下所有写入的程序。

现在,如果您的程序采用文件路径来读取或写入数据(大多数程序都是这样做的),并且您想向这些程序添加“空白输入”或“丢弃此输出”功能,那么/dev/null这是免费的。

请注意,它的优雅之处在于降低了所有相关程序的代码复杂性 - 对于您的系统可以作为具有实际“文件名”的“文件”提供的每个常见但特殊的用例,您的代码可以避免添加自定义命令要处理的行选项和自定义代码路径。

好的软件工程通常依赖于找到好的或“自然”的隐喻来抽象问题的某些元素:变得更容易思考保持灵活,这样你就可以解决基本相同范围的较高级别问题,而不必花费时间和精力不断地重新实现相同较低级别问题的解决方案。

“一切都是文件”似乎是访问资源的一个隐喻:您调用open分层命名空间中的给定路径,获取对该对象的引用(文件描述符),并且您可以在文件描述符上进行read和等。write您的 stdin/stdout/stderr 也是恰好为您预先打开的文件描述符。您的管道只是文件和文件描述符,文件重定向可以让您将所有这些部分粘合在一起。

Unix 之所以成功,部分原因是这些抽象概念协同工作得很好,并且/dev/null最好将其作为整体的一部分来理解。


PS 值得一看的是 Unix 版本的“一切都是文件”以及诸如此类的东西,/dev/null作为对这个隐喻进行更灵活和更强大的概括的第一步,该隐喻已在随后的许多系统中实现。

例如,在 Unix 中,类似特殊文件的对象/dev/null必须在内核本身中实现,但事实证明它足够有用,可以以文件/文件夹形式公开功能,从那时起,已经创建了多个系统,为程序提供了一种方式要做到这一点。

第一个是 Plan 9 操作系统,它是由 Unix 的一些开发者开发的。后来,GNU Hurd 对其“翻译器”做了类似的事情。与此同时,Linux 最终获得了 FUSE(目前它也已经扩展到其他主流系统)。

答案4

除了“一切都是文件”以及因此大多数其他答案所基于的易用性之外,还存在 @user5626466 提到的性能问题。

为了在实践中展示,我们将创建一个名为的简单程序nullread.c

#include <unistd.h>
char buf[1024*1024];
int main() {
        while (read(0, buf, sizeof(buf)) > 0);
}

并编译它gcc -O2 -Wall -W nullread.c -o nullread

(注意:我们不能在管道上使用 lseek(2),因此排空管道的唯一方法是从中读取数据,直到它为空)。

% time dd if=/dev/zero bs=1M count=5000 |  ./nullread
5242880000 bytes (5,2 GB, 4,9 GiB) copied, 9,33127 s, 562 MB/s
dd if=/dev/zero bs=1M count=5000  0,06s user 5,66s system 61% cpu 9,340 total
./nullread  0,02s user 3,90s system 41% cpu 9,337 total

而使用标准/dev/null文件重定向,我们可以获得更好的速度(由于提到的事实:上下文切换更少,内核只是忽略数据而不是复制数据等):

% time dd if=/dev/zero bs=1M count=5000 > /dev/null
5242880000 bytes (5,2 GB, 4,9 GiB) copied, 1,08947 s, 4,8 GB/s
dd if=/dev/zero bs=1M count=5000 > /dev/null  0,01s user 1,08s system 99% cpu 1,094 total

(这应该是一个评论,但太大了,完全不可读)

相关内容