のは、関連する重要な概念の一部見直してみましょう:オペレーティング・システム・カーネル、各ファイル、パイプエンドポイント、ソケットエンドポイント、オープンデバイスノードで
ファイルの説明
をし、このように、ファイルの説明があります。カーネルは、ファイル内の位置、フラグ(読み取り、書き込み、追加、exec on close)、レコードロックなどを追跡するためにこれらを使用します。
ファイルの説明はカーネルの内部であり、特にどのプロセスにも属していません(一般的な実装では)。プロセスの観点から
ファイルディスクリプタ
、ファイル記述子が開いているファイル、パイプ、ソケット、FIFOの、またはデバイスを識別する整数です。
オペレーティングシステムのカーネルは、プロセスごとにディスクリプタのテーブルを保持します。プロセスによって使用されるファイル記述子は、単にこのテーブルのインデックスです。
ファイルディスクリプタテーブルのエントリは、カーネルファイルの説明を参照します。プロセスがファイルディスクリプタを複製するdup()
or dup2()
を使用するたびに、カーネルは唯一のそのプロセスのファイルディスクリプタテーブルのエントリを複製
。自分が保持しているファイル記述を複製しません。
プロセスがforkするとき、子プロセスは独自のファイルディスクリプタテーブルを取得しますが、エントリはまったく同じカーネルファイル記述を指しています。
プロセスがUnixを介して別のプロセスにファイル記述子を送信すると、プロセスがファイル記述子を他のプロセスに送信するときに、ファイル記述子がファイル記述子への参照となります(基本的にshallow copyです。ドメインソケット補助メッセージでは、カーネルは実際には受信者に新しい記述子を割り当て、転送された記述子が参照するファイル記述をコピーします。
「ファイルディスクリプタ」と「ファイルの説明」はとても似ていること。少し混乱していますが、それはすべて、非常にうまく機能
OPが見ている効果とは何が関係していますか?
新しいプロセスが作成されるたびに、ターゲットデバイス、パイプ、またはソケット、およびdup2()
記述子を標準入力、標準出力、および標準エラーにオープンするのが一般的です。これは、同じのファイル記述を参照する3つの標準記述子すべてにつながります。したがって、1つのファイル記述子を使用して有効な操作は、他のファイル記述子を使用しても有効です。
これはコンソールでプログラムを実行する場合に最も一般的です.3つの記述子はすべて同じファイル記述を参照しているためです。そのファイル記述は擬似端末文字デバイスのスレーブ側を記述する。
次のプログラムを考えてみましょう、run.c:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
static void wrerrp(const char *p, const char *q)
{
while (p < q) {
ssize_t n = write(STDERR_FILENO, p, (size_t)(q - p));
if (n > 0)
p += n;
else
return;
}
}
static inline void wrerr(const char *s)
{
if (s)
wrerrp(s, s + strlen(s));
}
int main(int argc, char *argv[])
{
int fd;
if (argc < 3) {
wrerr("\nUsage: ");
wrerr(argv[0]);
wrerr(" FILE-OR-DEVICE COMMAND [ ARGS ... ]\n\n");
return 127;
}
fd = open(argv[1], O_RDWR | O_CREAT, 0666);
if (fd == -1) {
const char *msg = strerror(errno);
wrerr(argv[1]);
wrerr(": Cannot open file: ");
wrerr(msg);
wrerr(".\n");
return 127;
}
if (dup2(fd, STDIN_FILENO) != STDIN_FILENO ||
dup2(fd, STDOUT_FILENO) != STDOUT_FILENO) {
const char *msg = strerror(errno);
wrerr("Cannot duplicate file descriptors: ");
wrerr(msg);
wrerr(".\n");
return 126;
}
if (dup2(fd, STDERR_FILENO) != STDERR_FILENO) {
/* We might not have standard error anymore.. */
return 126;
}
/* Close fd, since it is no longer needed. */
if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO)
close(fd);
/* Execute the command. */
if (strchr(argv[2], '/'))
execv(argv[2], argv + 2); /* Command has /, so it is a path */
else
execvp(argv[2], argv + 2); /* command has no /, so it is a filename */
/* Whoops; failed. But we have no stderr left.. */
return 125;
}
それは、二つ以上のパラメータを取ります。最初のパラメータはファイルまたはデバイスで、2番目のパラメータはコマンドであり、残りのパラメータはコマンドに供給されます。このコマンドは、3つの標準ディスクリプタすべてが、最初のパラメータで指定されたファイルまたはデバイスにリダイレクトされて実行されます。例えばgccで上記をコンパイルすることができます。
gcc -Wall -O2 run.c -o run
report.c、の小さなテスターユーティリティを書いてみましょう:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char *argv[])
{
char buffer[16] = { "\n" };
ssize_t result;
FILE *out;
if (argc != 2) {
fprintf(stderr, "\nUsage: %s FILENAME\n\n", argv[0]);
return EXIT_FAILURE;
}
out = fopen(argv[1], "w");
if (!out)
return EXIT_FAILURE;
result = write(STDIN_FILENO, buffer, 1);
if (result == -1) {
const int err = errno;
fprintf(out, "write(STDIN_FILENO, buffer, 1) = -1, errno = %d (%s).\n", err, strerror(err));
} else {
fprintf(out, "write(STDIN_FILENO, buffer, 1) = %zd%s\n", result, (result == 1) ? ", success" : "");
}
result = read(STDOUT_FILENO, buffer, 1);
if (result == -1) {
const int err = errno;
fprintf(out, "read(STDOUT_FILENO, buffer, 1) = -1, errno = %d (%s).\n", err, strerror(err));
} else {
fprintf(out, "read(STDOUT_FILENO, buffer, 1) = %zd%s\n", result, (result == 1) ? ", success" : "");
}
result = read(STDERR_FILENO, buffer, 1);
if (result == -1) {
const int err = errno;
fprintf(out, "read(STDERR_FILENO, buffer, 1) = -1, errno = %d (%s).\n", err, strerror(err));
} else {
fprintf(out, "read(STDERR_FILENO, buffer, 1) = %zd%s\n", result, (result == 1) ? ", success" : "");
}
if (ferror(out))
return EXIT_FAILURE;
if (fclose(out))
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
それは正確に一つのパラメータ、標準入力に書き込むかどうかを報告するために、書き込み先のファイルまたはデバイスをとり、標準出力からの読み取りとエラー処理。 (実際に端末装置を参照するために、BashシェルとPOSIXシェルでは通常$(tty)
を使用することができるので、端末上でレポートを見ることができます)。
./run /dev/null ./report $(tty)
./run /dev/zero ./report $(tty)
./run /dev/urandom ./report $(tty)
のか、何で私たちは希望:
gcc -Wall -O2 report.c -o report
今、我々はいくつかのデバイスを確認することができます。ファイルディスクリプタは同じ参照として、予想通りである - 私のマシンでは、私はファイルでこれを実行すると、
./run some-file ./report $(tty)
は、標準入力に書き込み、標準出力と標準エラーからのすべての作業を読んで言います、読み書き可能なファイル記述。
結論としては、上記で遊んだ後で、はここでは全く奇妙な動作ではないということです。です。 ファイル記述子がプロセスによって使用されている場合は、オペレーティングシステム内部のファイルの説明への参照であり、標準入力、出力、およびエラー記述子は、それぞれdup
であると見なされます。
なぜ世界では、何が標準出力に書いていると思いますか?あなたの端末に書き込んでいます。あなたのプロセスの標準出力はあなたの端末に関連しているかもしれませんが、同じものではありません。両者を融合させないでください。あなたのケースでは、stdinも端末に関連付けられているので、stdinへの書き込みが端末に表示されることは驚くことではありません。 –