シェル/init はどのようにして stdio ストリームを作成しますか?

シェル/init はどのようにして stdio ストリームを作成しますか?

私はMITのソースを読んでいますxv6 OSこのスニペットは次の の先頭にありますsh.c:

// Ensure that three file descriptors are open.
while((fd = open("console", O_RDWR)) >= 0){
    if(fd >= 3){
      close(fd);
      break;
    }
}

これは、少なくとも新しく割り当てられたファイル記述子が 3 より大きい (または同じ) かどうかをチェックすることにより、3 つのファイル記述子 (おそらく stdin、stdout、stderr 用) が開かれます。

open1)同じプロセスから同じデバイスを複数回使用して、異なるファイル記述子を期待することはどのように可能ですか?

2) これを理解するために、ホスト マシン (x86_64 Linux 4.6.0.1) で同様のスニペットを実行しました。テスト プログラムは、open異なる fd が期待できるかどうかを確認するために、ループ内でテキスト ファイルを繰り返し ed しましたが、常に同じファイル記述子が生成されました。このことから、open実際のファイルとデバイス (など/dev/console) を - すると、xv6 のスニペットが明らかに機能するため (Qemu でテスト済み)、何らかの違いがあるという結論に達しました。違いは正確には何ですか?

#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>

int main(void)
{
    int fd;
    int cnt = 0;

    while ((fd = open("sample.txt", O_RDWR) > 0)) {
        if (cnt != 10) {
            cnt++;
            printf("File descriptor opened: %d\n", fd);
        } else {
            break;
        }
    }

    return 0;
}

実行した場合の出力は次のとおりです。

$ ./a.out
File descriptor opened: 1
File descriptor opened: 1
[snip]
File descriptor opened: 1
File descriptor opened: 1

編集回答の1つに基づいてstrace実行ファイルを実行すると、open 確かに複数のファイル記述子を返しますが、何らかの理由でそのすべてが印刷されません。それはなぜでしょうか?

3) 多少関係ありませんが、fds 0-2 で stdio ストリームを使用するという慣例は、まさに慣例ではないでしょうか。たとえば、初期化シーケンスで入出力ファイル記述子を他のものに割り当てた場合、その子が I/O を実行する方法に何らかの影響があるでしょうか。

答え1

実際には 3 つの質問があります。プログラムが正しくないため、2 番目の質問はすぐに破棄してください。

    while ((fd = open("sample.txt", O_RDWR) > 0)) {

おそらくあなたが言いたいのは

    while ((fd = open("sample.txt", O_RDWR)) > 0) {

括弧の配置が不適切だと、がfd0 より大きいかどうかのみをテストすることになります (ファイル記述子 0、1、2 が開いているので、これはおそらく適切な仮定です)。

1位:open呼び出し (成功した場合) は、個別のファイル記述子を返すように定義されています。デバイスを再度開くことができない場合は、openが返されます-1

3番目:確かに、それは大会、また、POSIX標準他のシステムでは、プログラムごとに 4 番目のオープン ストリームを用意するなど、他の規則が使用されています。

参考文献:Aegis 環境の使用 (1988 年 7 月)
6-9ページを参照してください。Apollo Domain/OSにエラーが発生したと書かれています。入力そして**出力*

答え2

いいえ、コードはチェック記述子は、実際には開きます。まだ記述子は与えられていません。開くたびに、新しいファイル記述子 (つまり、0、1、2、3) が与えられます。0 から 2 が開いたまま、fd 3 に到達すると、コードは中断します。

各ファイル記述子は、単にファイル内の特定の場所へのポインタです。したがって、同じファイルに対して複数の記述子があっても問題ありません。

テスト プログラムが、異なるオープン呼び出しに対して同じ fd を返す場合、バグがあります。コードを示してください。

はい、fd 0 から 2 には強い規則があります。一部のコードが stdout に印刷する場合、実際には fd 1 に印刷されます。stdout を他のものに「マップ」する方法はありません。

答え3

1) システムが複数のプロセスから同時に同じファイルを開くことをサポートしている場合、1 つのプロセスが複数回開くことも許可しないのはなぜでしょうか。ファイル記述子は継承されるため、同じプロセスで同じファイルが 2 回使用される可能性があります (つまり、1 回継承され、プロセス自体によって 1 回開かれた場合)。プロセスが開いているファイルを確認し、以前のファイルへの参照を返すのは余分な作業になります。

また、プロセスの異なる部分が同時に同じファイルを使用する場合の問題もあります。ライブラリがメインプログラムと同時に何らかの設定ファイルを使用するとします。ファイル記述子はアクセスモードとファイルポインタの位置に関連付けられています。それらのコピーが 1 つしかない場合、奇妙なことが起きます。また、ファイルが 2 回開かれた場合 (同じ fd に)、閉じたときに何が起こるでしょうか。いつ閉じるかを決定するために、参照カウントの別のレイヤーが存在する可能性があります。本当にファイルを閉じますが、他の問題には役立ちません。


2) コードによって異なります。スマートな言語 (つまり C ではない言語) では、開いているファイルを保持している変数を参照カウントし、ファイルを再度開く直前にその変数を閉じる場合があります。コードを見ないと何とも言えません。

しかし、ちょっとしたテストとして、Perl で同じ変数に対して同じファイルを 2 回開くと、同じ FD 番号が生成されます。

perl -e 'open F, "test.txt"; printf "%d ", fileno(F); open F, "test.txt"; printf "%d\n", fileno(F)'
3 3

実行すると、straceファイルが再度開かれる直前に閉じられていることがわかります。

2 つの異なる変数を使用すると、2 つの FD 番号が得られます。

perl -e 'open F, "test.txt"; printf "%d ", fileno(F); open G, "test.txt"; printf "%d\n", fileno(G)'
3 4

3) 技術的には、標準ファイル番号は慣例であると言えます。POSIX と ISO C 標準:

プログラムの起動時に、標準入力 (従来の入力の読み取り用)、標準出力 (従来の出力の書き込み用)、および標準エラー (診断出力の書き込み用) の 3 つのストリームが事前に定義され、明示的に開く必要はありません。

しかし、カーネルを気にせずにプログラムを実行できるという点で、いずれにせよ慣例です。あるいは、そうでないかもしれません。呼び出しの仕様を読むとexec実装によって何かを開くことができるようになりました:

exec ファミリの関数のいずれかの呼び出しが成功した後にファイル記述子 0、1、または 2 が閉じられる場合、実装では新しいプロセス イメージ内のファイル記述子に対して未指定のファイルを開くことがあります。

(もちろん、プログラム自体で閉じることもできます。)

起動時にこれらを使用しないように設定した場合、互換性は完全に失われます。

標準ユーティリティまたは準拠アプリケーションが、ファイル記述子 0 が読み取り用に開かれていないか、ファイル記述子 1 または 2 が書き込み用に開かれていない状態で実行される場合、ユーティリティまたはアプリケーションが実行される環境は非準拠とみなされ、その結果、ユーティリティまたはアプリケーションはこの標準で説明されているように動作しない可能性があります。

Linuxのmanページには、実際的なことが書かれている。:

一般的な原則として、特権の有無にかかわらず、移植可能なプログラムは、execve() の実行後もこれらの 3 つのファイル記述子が閉じられたままであると想定することはできません。

関連情報