web-dev-qa-db-ja.com

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

Linux上の特殊ファイルの概念を理解しようとしています。ただし、/devに特別なファイルがあることは、私の知る限り、Cの数行でその機能を実装できる場合、ばかげているように見えます。

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

114
Ankur S

キャラクタースペシャルデバイスを使用することによるパフォーマンス上の利点に加えて、主な利点はmodularityです。/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
154
ioctl

公平に言えば、それは通常のファイル自体ではありません。 文字型特殊デバイス

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

ファイルやプログラムとしてではなくデバイスとして機能するということは、標準の入力/出力/エラーを含む任意のファイル記述子にアタッチできるため、入力または出力をリダイレクトするのがより簡単な操作であることを意味します。

62
DopeGhoti

whyは、Unix(およびその結果としてLinux)を形作ったビジョン/デザインと、それに由来する利点に大きく関係していると思います。

余分なプロセスを起動しないことには無視できないパフォーマンス上の利点があることは間違いありませんが、私にはそれ以上のものがあると思います:初期のUnixには「すべてがファイルである」というメタファーがありました。シェルスクリプトの観点からではなく、システムの観点から。

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

しかし、ここでは2つの演習があります。

  1. 既存のUnixツールと/dev/nullを使用してプログラムnullを実装しましょう-簡単:cat >/dev/null。できました。

  2. nullに関して/dev/nullを実装できますか?

入力を破棄するだけのCコードは取るに足らないものであることは完全に正しいので、まだ明らかではないかもしれませんwhyタスクで使用できる仮想ファイルがあると便利です。

考慮してください:ほとんどすべてのプログラミング言語すでに必要ですファイル、ファイル記述子、およびファイルパスを操作します。なぜなら、それらは最初からUnixの「すべてがファイル」パラダイムの一部だったからです。

すべてがstdoutに書き込むプログラムである場合でも、すべての書き込みを取り込む仮想ファイルにリダイレクトするか、すべての書き込みを取り込むプログラムにパイプをリダイレクトするかはプログラムに関係ありません。

ここで、データの読み取りまたは書き込みにファイルパスを使用するプログラム(ほとんどのプログラムが行う)があり、それらのプログラムに「空白入力」または「この出力を破棄」機能を追加したい場合は、/dev/nullそれは無料です。

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

多くの場合、優れたソフトウェアエンジニアリングは、問題の一部の要素を考えるのが容易になるだが柔軟なままのように抽象化するために、優れたまたは「自然な」メタファーを見つけることに依存しています。は、基本的に同じレベルの高いレベルの問題を解決できます。同じ低いレベルの問題の解決策を再実装するために時間と精神的なエネルギーを費やす必要はありません。

「すべてがファイルです」は、リソースにアクセスするためのそのようなメタファの1つであるようです。階層的な名前空間で特定のパスのopenを呼び出し、オブジェクトへの参照(ファイル記述子)を取得すると、readwriteなど。 stdin/stdout/stderrも、たまたま事前に開かれているファイル記述子です。パイプは単なるファイルとファイル記述子であり、ファイルリダイレクトを使用すると、これらすべての要素を結合できます。

Unixは、これらの抽象化がいかにうまく機能したかという理由で、部分的に成功しました。/dev/nullは、全体の一部として最もよく理解されています。


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

たとえば、Unixでは、/dev/nullのような特殊なファイルのようなオブジェクトをカーネル自体に実装する必要がありましたが、それ以降、複数のシステムで作成されたファイル/フォルダー形式で機能を公開するのに十分便利であることがわかりましたプログラムがそれを行う方法を提供します。

最初の1つは、Unixを作ったのと同じ人々によって作られたPlan 9オペレーティングシステムでした。その後、GNU Hurdは「トランスレータ」と同様のことを行いました。その間、LinuxはFuseを取得しました(これは他の主流システムにも普及しています)。

54
mtraceur

おもう /dev/nullパフォーマンス上の理由のプログラムではなく、キャラクターデバイス(通常のファイルのように動作します)です。

プログラムの場合は、プログラムのロード、開始、スケジューリング、実行、およびその後のプログラムの停止とアンロードが必要です。あなたが説明している単純なCプログラムはもちろん多くのリソースを消費しませんが、リダイレクト(/数百万)の多数のアクションを考慮すると、大きな違いが生じると思いますプロセス管理操作はコストがかかりますコンテキストスイッチを伴うため、大規模です。

別の仮定:プログラムへのパイピングには、受信プログラムによって割り当てられるメモリが必要です(後で直接破棄された場合でも)。したがって、ツールにパイプすると、送信プログラムと受信プログラムの両方で、メモリの消費量が2倍になります。

14
user5626466

「すべてがファイルである」ため、他のほとんどの回答が基づいているあらゆる場所での使いやすさの他に、@ 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

(これはコメントのはずですが、そのためには大きすぎて完全に判読できません)

7
Matija Nalis

あなたの質問は、ファイルの代わりにnullプログラムを使用することによって、何かがおそらく単純に得られるかのように提起されます。おそらく、「マジックファイル」の概念を取り除き、代わりに単に「通常のパイプ」を使用することができます。

しかし、考慮してくださいパイプもファイルです。通常は名前が付けられていないため、ファイル記述子を介してのみ操作できます。

このやや不自然な例を考えてみましょう。

_$ echo -e 'foo\nbar\nbaz' | grep foo
foo
_

Bashのプロセス置換を使用すると、より遠回りの方法で同じことを達成できます。

_$ grep foo <(echo -e 'foo\nbar\nbaz')
foo
_

grepechoに置き換えれば、カバーの下に表示されます。

_$ echo foo <(echo -e 'foo\nbar\nbaz')
foo /dev/fd/63
_

<(...)構文はファイル名に置き換えられ、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
_

その他:

_$ echo -e 'foo\nbar\nbaz' > foofifo
_

そして見よ、grepはfooを出力します。

パイプと通常のファイル、および/ dev/nullのような特別なファイルがすべて単なるファイルであることがわかったら、nullプログラムの実装は明らかにmore複雑です。カーネルはいずれかの方法でファイルへの書き込みを処理する必要がありますが、/ dev/nullの場合は書き込みをフロアにドロップするだけですが、パイプを使用すると、実際にバイトを別のプログラムに転送する必要があります。実際に読んでください。

6
Phil Frost

歴史的なパラダイムとパフォーマンスを超えたセキュリティの問題があると私は主張します。特権実行資格情報を持つプログラムの数を制限することは、どんなに単純であっても、システムセキュリティの基本的な原則です。置換/dev/nullは、システムサービスが使用するため、このような特権を持っている必要があります。最新のセキュリティフレームワークは、エクスプロイトを防止する優れた機能を備えていますが、完全なものではありません。ファイルとしてアクセスされるカーネル駆動のデバイスは、悪用するのがはるかに困難です。

0
NickW

他の人がすでに指摘したように、/dev/nullis数行のコードで構成されるプログラム。これらのコード行がカーネルの一部であるだけです。

より明確にするために、これがLinuxの実装です。キャラクターデバイスは、読み取りまたは書き込み時に関数を呼び出します。 /dev/nullへの書き込みは write_null を呼び出し、読み取り中は read_null 、登録済み ここ を呼び出します。

文字通り数行のコード:これらの関数は何もしません。読み取りと書き込み以外の関数をカウントする場合にのみ、手の指よりも多くのコード行が必要になります。

0
Matthieu Moy