Ank*_*r S 116 executable devices files null
我试图了解 Linux 上特殊文件的概念。然而,/dev根据我的知识,当它的功能可以通过 C 中的几行代码实现时,在其中包含一个特殊文件似乎很愚蠢。
此外,您可以以几乎相同的方式使用它,即通过管道进入null而不是重定向到/dev/null. 将其作为文件是否有特定原因?使它成为一个文件不会导致许多其他问题,例如太多程序访问同一文件吗?
小智 156
除了使用字符专用设备的性能优势外,主要优势是模块化。/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
Run Code Online (Sandbox Code Playgroud)
在这些情况下,使用程序作为源或接收器会非常麻烦。即使在 shell 管道的情况下,stdout 和 stderr 也可以独立地重定向到文件,这对于将可执行文件作为接收器来说是很难做到的:
# Suppress errors, but print output.
$ grep foo * 2>/dev/null
Run Code Online (Sandbox Code Playgroud)
Dop*_*oti 62
公平地说,它本身不是一个常规文件;这是一个字符特殊设备:
$ file /dev/null
/dev/null: character special (3/2)
Run Code Online (Sandbox Code Playgroud)
它作为设备而不是文件或程序运行意味着重定向输入或输出是一种更简单的操作,因为它可以附加到任何文件描述符,包括标准输入/输出/错误。
mtr*_*eur 54
我怀疑原因与塑造 Unix(以及 Linux)的愿景/设计以及由此产生的优势有很大关系。
毫无疑问,不启动额外的进程有一个不可忽视的性能优势,但我认为还有更多:早期的 Unix 有一个“一切都是文件”的比喻,如果你看一下,它有一个不明显但优雅的优势它是从系统的角度,而不是从 shell 脚本的角度。
假设您有null命令行程序和/dev/null设备节点。从 shell 脚本的角度来看,该foo | null程序实际上是真正有用和方便的,并且foo >/dev/null需要更长的时间来输入并且看起来很奇怪。
但这里有两个练习:
让我们null使用现有的 Unix 工具来实现该程序/dev/null- 简单:cat >/dev/null。完毕。
你可以实现/dev/null的方面null?
您完全正确的是,仅丢弃输入的 C 代码是微不足道的,因此可能尚不清楚为什么为任务提供可用的虚拟文件很有用。
想一想:几乎每一种编程语言都已经需要处理文件、文件描述符和文件路径,因为它们从一开始就是 Unix 的“一切都是文件”范式的一部分。
如果您拥有的只是写入标准输出的程序,那么该程序并不关心您是将它们重定向到吞下所有写入的虚拟文件,还是将管道重定向到吞下所有写入的程序中。
现在,如果您的程序采用文件路径来读取或写入数据(大多数程序都这样做) - 并且您想向这些程序添加“空白输入”或“丢弃此输出”功能 - 好吧,/dev/null这是免费的。
请注意,它的优雅之处在于降低了所有相关程序的代码复杂性 - 对于您的系统可以作为具有实际“文件名”的“文件”提供的每个常见但特殊的用例,您的代码可以避免添加自定义命令-line 选项和自定义代码路径来处理。
好的软件工程通常依赖于找到好的或“自然”的隐喻,以一种更容易思考但保持灵活的方式抽象问题的某些元素,这样您就可以解决基本相同范围的更高级别问题,而不必不断地将时间和精力花在重新实施相同较低级别问题的解决方案上。
“一切都是文件”似乎是访问资源的一个这样的比喻:您调用open分层命名空间中的给定路径,获取对对象的引用(文件描述符),并且您可以对文件描述符进行read和write等。您的 stdin/stdout/stderr 也是刚好为您预先打开的文件描述符。您的管道只是文件和文件描述符,而文件重定向可让您将所有这些部分粘合在一起。
Unix 成功的部分原因在于这些抽象如何很好地协同工作,并且/dev/null最好将其理解为整体的一部分。
PS 值得一看 Unix 版本的“一切都是文件”以及诸如此类/dev/null的东西,这是迈向更灵活和强大的隐喻概括的第一步,该隐喻已在随后的许多系统中实现。
例如,在 Unix 中,类似文件的特殊对象/dev/null必须在内核本身中实现,但事实证明,以文件/文件夹形式公开功能已经足够有用了,从那时起,已经有多个系统为程序提供了一种方式要做到这一点。
第一个是 Plan 9 操作系统,它是由一些制造 Unix 的人制造的。后来,GNU Hurd 对它的“翻译器”做了类似的事情。与此同时,Linux 最终得到了 FUSE(现在它也已经传播到其他主流系统)。
小智 14
出于性能原因,我认为/dev/null是字符设备(其行为类似于普通文件)而不是程序。
如果是程序,则需要加载、启动、调度、运行,然后停止和卸载程序。您所描述的简单 C 程序当然不会消耗大量资源,但我认为在考虑大量(例如数百万)重定向/管道操作时会产生显着差异,因为流程管理操作在大规模上成本高昂它们涉及上下文切换。
另一个假设:管道进入程序需要接收程序分配内存(即使之后直接丢弃)。因此,如果您通过管道导入该工具,则会消耗双倍内存,一次是在发送程序上,另一次是在接收程序上。
除了“一切都是一个文件”以及大多数其他答案所基于的任何地方的易用性之外,还有@user5626466 提到的性能问题。
为了在实践中展示,我们将创建一个简单的程序,名为nullread.c:
#include <unistd.h>
char buf[1024*1024];
int main() {
while (read(0, buf, sizeof(buf)) > 0);
}
Run Code Online (Sandbox Code Playgroud)
并编译它 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
Run Code Online (Sandbox Code Playgroud)
而使用标准/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
Run Code Online (Sandbox Code Playgroud)
(这应该是那里的评论,但太大了,完全不可读)
您提出的问题好像通过使用空程序代替文件可以简单地获得某些东西。也许我们可以摆脱“魔法文件”的概念,而只有“普通管道”。
但是考虑一下,管道也是文件。它们通常没有命名,因此只能通过它们的文件描述符进行操作。
考虑这个有点人为的例子:
$ echo -e 'foo\nbar\nbaz' | grep foo
foo
Run Code Online (Sandbox Code Playgroud)
使用 Bash 的进程替换,我们可以通过更迂回的方式完成同样的事情:
$ grep foo <(echo -e 'foo\nbar\nbaz')
foo
Run Code Online (Sandbox Code Playgroud)
替换grepfor echo,我们可以在封面下看到:
$ echo foo <(echo -e 'foo\nbar\nbaz')
foo /dev/fd/63
Run Code Online (Sandbox Code Playgroud)
该<(...)构造只是用文件名替换,并且 grep 认为它正在打开任何旧文件,它恰好被命名为/dev/fd/63. 这/dev/fd是一个神奇的目录,它为访问它的文件所拥有的每个文件描述符创建命名管道。
我们可以通过mkfifo创建一个显示在ls所有内容中的命名管道来减少魔法,就像一个普通文件:
$ mkfifo foofifo
$ ls -l foofifo
prw-rw-r-- 1 indigo indigo 0 Apr 19 22:01 foofifo
$ grep foo foofifo
Run Code Online (Sandbox Code Playgroud)
别处:
$ echo -e 'foo\nbar\nbaz' > foofifo
Run Code Online (Sandbox Code Playgroud)
看哪,grep 将输出foo.
我认为一旦你意识到管道和常规文件以及像 /dev/null 这样的特殊文件都只是文件,很明显,实现一个空程序会更加复杂。内核必须以任何一种方式处理对文件的写入,但在 /dev/null 的情况下,它可以将写入放在地板上,而使用管道,它必须实际将字节传输到另一个程序,然后必须实际上阅读它们。
| 归档时间: |
|
| 查看次数: |
23311 次 |
| 最近记录: |