/dev/nullがファイルであるのはなぜですか?単純なプログラムでは、なぜその機能が実装されないのですか?

/dev/nullがファイルであるのはなぜですか?単純なプログラムでは、なぜその機能が実装されないのですか?

Linuxでは特殊ファイルの概念を理解しようとしています。しかし、/dev私が知っている限り、Cは数行で機能を実装できますが、特別なファイルを持つことは愚かなようです。

また、ほぼ同じ方法で使用できます。つまり、nullリダイレクトする代わりにパイプ/dev/nullとしてファイルとして保存する特別な理由はありますか?ファイルとして保存すると、同じファイルにアクセスするプログラムが多すぎるなど、他の多くの問題は発生しませんか?

答え1

キャラクター別装置を使用することで得られる性能上の利点に加えて、最大の利点はモジュラー。 /dev/null は、シェルパイプだけでなく、ファイルが予想されるほとんどすべてのコンテキストで使用できます。ファイルをコマンドライン引数として受け入れるプログラムを考えてみましょう。

# 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

このような場合、プログラムをソースまたはシンクとして使用するのは非常に面倒です。シェルパイプの場合でも、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には「すべてがファイルです」というたとえ話がありました。エレガントな利点は、シェルスクリプトの観点ではなく、システムの観点から来ています。

nullコマンドラインプログラムと/dev/nullデバイスノードがあるとします。シェルスクリプトの観点からは、foo | nullプログラムは実際に効果があるそして便利foo >/dev/null入力するのに少し時間がかかり、奇妙に見えます。

しかし、ここには2つの練習があります。

  1. null既存のUnixツールと-easy:を使用して/dev/nullこのプログラムを実装しましょうcat >/dev/null。完璧。

  2. /dev/nullそれを実装できますかnull

あなたは正しいです。入力Cコードを捨てるだけでは十分ではないので、まだ明確ではないかもしれません。なぜこれに使用できるダミーファイルがあると便利です。

考慮事項:ほぼすべてのプログラミング言語すでに必要ファイル、ファイル記述子、およびファイルパスは、最初からUnixの「すべてがファイルです」パラダイムの一部でしたので作業します。

あなたが持っているものが標準出力に書き込むプログラムだけなら、そのプログラムはすべての書き込みを飲み込む仮想ファイルにリダイレクトするのか、それともすべての書き込みを飲み込むプログラムにパイプをリダイレクトするのかは関係ありません。

これで、プログラムがデータを読み書きするためにファイルパスを使用し(ほとんどのプログラムが実行する)、そのプログラムに「空の入力」または「この出力の削除」機能を追加したい場合は、/dev/nullこの機能は必要ありません。

その優雅さは、すべての関連プログラムのコードの複雑さを減らすことです。システムが実際の「ファイル名」を持つ「ファイル」として提供できるすべての一般的ですが、特別なユースケースでは、コードは行オプションとカスタムコードパスの追加を防ぎます。処理するカスタムコマンドです。

良いソフトウェアエンジニアリングは、問題の特定の要素を抽象化するために良いまたは「自然な」比喩を見つけることに依存することがよくあります。考えやすくなるしかし、柔軟性を維持する、同じ低レベルの問題に対する解決策の継続的な再実装に時間と労力を費やすことなく、基本的に同じ範囲の上位レベルの問題を解決できます。

「すべてがファイルです」というリソースアクセスのたとえ話です。open階層的な名前空間で指定されたパスを呼び出し、オブジェクト(ファイル記述子)への参照を取得し、ファイル記述子readでなどの操作を実行できます。writestdin/stdout/stderr も事前に開いているファイル記述子です。パイプはファイルとファイル記述子にすぎず、ファイルリダイレクトを使用すると、これらすべてのフラグメントを1つにまとめることができます。

Unixが成功する理由の1つは、これらの抽象的な概念が一緒にうまく機能し、/dev/null全体の一部として最もよく理解されるためです。


/dev/nullPS多くの後続のシステムで実装されているこのたとえ話のより柔軟で強力な一般化に向けた最初のステップとして、「すべてがファイルです」というUnixバージョンを見る価値があります。

たとえば、Unixは特別なファイルに似たオブジェクトを/dev/nullカーネル自体で実装する必要がありましたが、ファイル/フォルダの形で機能を公開するのに役立つことが証明されており、それ以降、プログラムに次の機能を提供するためにいくつかのシステムが作成されました。 。これを行う方法です。

1つ目は、一部のUnix開発者が開発したPlan 9オペレーティングシステムです。その後、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

(これはコメントでなければなりませんが、大きすぎて全く読めません。)

関連情報