目次
ファイル記述子の導入
前の章では、システム インターフェイスについて説明し、open() 関数の戻り値が整数であることを学びましたが、この整数とは何でしょうか? 次のコードを使用してこの現象を確認できます。
int main()
{
umask(0);
int fd1 = open("log1.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
printf("open success, fd: %d\n",fd1);
int fd2 = open("log2.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
printf("open success, fd: %d\n",fd2);
int fd3 = open("log3.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
printf("open success, fd: %d\n",fd3);
int fd4 = open("log4.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
printf("open success, fd: %d\n",fd4);
close(fd1);
close(fd2);
close(fd3);
close(fd4);
}
4 つのファイルを別々に開き、make を終了してコンパイルして実行します。
これらのfd は連続しており、 fd は3 から始まること がわかりました。では、0、1、2 はどこに行ったのでしょうか?
前の章で説明したように、プログラムが実行されると、デフォルトで 3 つの入出力ストリーム、つまり stdin、stdout、stderr が開きます。
これら 3 つはそれぞれ 0、1、2 の位置を占めます。
例を使用して、それらの間の関係を確認できます。
int main()
{
umask(0);
//向stdout(显示器)中输出
fprintf(stdout,"hello,stdout\n");
//向fd为1的文件写入
const char* s = "hello,1\n";
write(1,s,strlen(s));
}
これら 2 つのステートメントがディスプレイに正常に出力されていることがわかります。
これは、fd が 1 のファイルが表示に対応していることを意味します。
前の章で fopen() インターフェースについて説明しました。もう一度見てみましょう。
他には何も言わずに、その戻り値がFILE*であることを見てみましょう。これがポインタであることがわかります。
しかし、このファイルは何でしょうか? FILE は、内部に多数のファイル関連のメンバーを含む構造体であり、 C 標準ライブラリによって提供されます。
システム インターフェイスは C 言語ライブラリ関数内で呼び出す必要があります。システム側から見ると、システムは fd のみを認識し、 FILE などの他の変数はシステムによって認識されないため、fd はこの FILE 構造にカプセル化する必要があります。
そうは言っても、stdin、stdout、stderr の型も FILE* であり、それらの内部も fd をカプセル化する必要があります。出力を出力してそれを証明できます。
int main()
{
printf("stdin: %d\n",stdin->_fileno);
printf("stdout: %d\n",stdout->_fileno);
printf("stderr: %d\n",stderr->_fileno);
return 0;
}
走行結果グラフ:
結果はまさに我々の考えたとおりであり、今述べたことを正しく検証することができました。
したがって、私たちの最終的な結論は次のとおりです。
1. C言語のファイル呼び出しインターフェースとシステムインターフェースとの間には一定の関係があります。
2. ユーザー レベルでは、stdin、stdout、および stderr ですが、システム レベルでは、数値 0、1、および 2 でのみ表すことができます。これらの番号はファイル記述子です。
ファイル記述子とは何ですか
上記では、fd が整数であることを知った上で fd について話しましたが、それは正確には何でしょうか?
ファイルにアクセスするには、プロセスはまずファイルを開く必要があります。
プロセスは複数のファイルを開くことができるため、一般的にはプロセス:ファイル = 1:n となります。
ファイルにアクセスするには、直接アクセスする前にファイルをメモリにロードする必要があります。
プロセスは複数のファイルを開くことができるため、複数のプロセスが独自のファイルを開いた場合はどうなるでしょうか?
このとき、システム内には多数のファイルが開かれるため、OS は非常に多くのファイルを管理する必要があります。
管理方法: 最初に記述し、次に整理します。
カーネルでは、開いているファイルはどのように表示されますか? OSは、オープンした各ファイルを管理するために、構造体構造に抽象化してから記述する必要がありますが、この構造体を構造体ファイルと呼び、その一般的な内容は次のとおりです。
このようなファイルは整理して管理する必要があるため、二重リンクリストを使用して管理します。
しかし、プロセスは私が開いたファイルをどのようにして知るのでしょうか。したがって、ここで対処する必要があるのは、プロセスとファイルの間の対応だけです。
事前に一言しておきます。
オペレーティング システムは、これらのプロセス ファイル操作リクエストの処理、ファイル記述子の管理、およびファイル ステータスの維持を担当しますが、ファイルの特定のコンテンツと処理はプロセスによって制御および管理されます。
したがって、しばらくはオペレーティング システム管理とプロセス管理を混同しないでください。
ファイル記述子 fd が 0 から増加することがわかります。学習したコンテナでは、ベクトル コンテナの添字も 0 から逆方向に増加します。
したがって、この対応関係は実際には本質的に配列であり、この配列の各添え字は対応する file_struct を指し、この配列内の各添え字はファイル記述子 (fd) と呼ばれます。この配列はファイル記述子テーブルと呼ばれます。
これは、プロセス task_struct 構造体のメンバーとして含まれます。
task_struct 構造 体では、カーネル ソース コードを確認できます。
内部には、 files_structの構造体 を指す構造体ポインターがあり、この構造体の内容を見てみましょう。
最後の構造体fd_arrayに注目してください。その型はポインター配列型で、ファイル記述子テーブルです。各要素はファイル構造を指すポインターです。次に、このファイルの内容を見てみましょう。
これには、属性やコンテンツ関連を含む、ファイルのすべてのコンテンツが含まれています。すべてのファイルが fd_array を通じて見つかることがわかりました。
上記の関係を要約すると、次のようになります。
プロセス task_struct 構造体には、files_struct を指す構造体ポインタがあり、file_struct には別の構造体配列 fd_array があり、fd_array 配列の各添字はファイル記述子であり、添字に対応する内容は添字として fd です。ファイルの。
次に、それらの間の関係を全体として整理します。そしてそれがどのように段階的に導き出されたのか。
ファイル記述子は0 から始まる小さな整数です。ファイルを開くと、オペレーティング システムは、ターゲット ファイルを記述するためにメモリ内に対応するデータ構造を作成します。したがって、ファイル構造があります。開いているファイル オブジェクトを表します。
プロセスは open システム コールを実行するため、プロセスはファイルに関連付けられている必要があります。各プロセスには、テーブルfiles_structを指すポインタ*files があります。テーブルの最も重要な部分は、ポインタの配列を含むことです。各要素は、開いているファイルへのポインタです。したがって、本質的に、ファイル記述子は配列のインデックスです。したがって、ファイル記述子を保持している限り、対応するファイルを見つけることができます。
ファイル記述子の割り当てルール
まず新しいファイルを作成してから、それに含まれるファイル記述子の数を確認してみましょう。コードは次のとおりです。
int main()
{
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC);
if(fd < 0)
{
perror("open");
return 1;
}
printf("fd: %d\n",fd);
close(fd);
return 0;
}
次に、vim を終了し、コンパイルして実行します。
fd が 3 になっていることがわかりますが、デフォルトでは 0、1、2 が stdin、stdout、stderr によってすでに占有されているため、これは問題ありません。
このとき、fdが0のファイルを閉じたら、再度そのファイルのfdを見てください。
close(0);
すると今回実行した結果はfdが0になっていることがわかります。
同様に、fd 2 でファイルを閉じるとします。
close(2);
この時点でファイルfdは2となっています。
おそらく、新しく開かれたファイルの fd は、デフォルトで最初の空いている fd になることがわかっています。
したがって、ここでの fd の割り当てルールは次のとおりです。最小の空いているファイル記述子を新しいファイルに割り当てます。
リダイレクト
出力リダイレクト
0と2のfdを閉じると言っただけですが、1のfdでファイルを閉じるとどうなるでしょうか?
次のコードを入力します
int main()
{
close(1);
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
if(fd < 0)
{
perror("open");
return 1;
}
printf("fd: %d\n",fd); //stdout->FILE[fileno=1]->log.txt
printf("fd: %d\n",fd);
printf("fd: %d\n",fd);
//close(fd); //后面解释
return 0;
}
次に、このコードを再度実行します。
何も出力されていないことがわかりましたが、これは真実ではなく、論理的に言えば 3 つの 1 が出力されるはずです。
ただし、log.txt の内容を出力すると、次のようになります。
ディスプレイに出力すべき内容がlog.txtファイルに出力されていることが分かりました。
fd=1 でファイルを閉じない場合は、コンパイルして再度実行します。
この時点で、ディスプレイに正常に出力されることがわかりました。
上記の理由は次のとおりです。 printf はデフォルトで stdout (標準出力)、つまり fd が 1 であるファイルに出力します。ただしこのとき、fd=1のファイルは閉じられ、その後新たに作成されたファイルが占有しますので、このとき再度printfを出力するとこのファイルに直接出力されます。
これは、最初はディスプレイに出力されますが、最終的には他のファイルに書き込まれる(表示される)この動作は、出力リダイレクトと呼ばれます。
以下はリダイレクトの概略図です。
fd=1 が指す元のファイルはディスプレイですが、myfile を指すように変更されるため、前に書き込まれてディスプレイに出力されたすべての内容がファイル myfile に出力されます。
したがって、リダイレクトの本質は、OS 内で、fd の対応するコンテンツのポインティングを変更することです。
入力リダイレクト
リダイレクトの原理はわかっていますし、入力リダイレクトも理解しやすく、別のファイルから読み取るのではなくキーから読み取るだけです。
まずこのコードを見てみましょう。
int main()
{
int fd = open("log.txt",O_RDONLY,0666);
if(fd < 0)
{
perror("open");
return 0;
}
printf("打开的文件fd为:%d\n",fd);
char buffer[64];
fgets(buffer,sizeof(buffer),stdin);
printf("%s\n",buffer);
}
まずファイルを開いて、その fd を出力し、次に fgets を書きました。3 番目のパラメータは stdin で、キーボードからデータを読み取り、読み取ったデータをバッファに出力し、バッファを出力します。
私の入力が完全に出力されていることがわかります。
ただし、この時点で、 close(0)をfront に追加すると、明らかな効果が得られ、次の内容が log.txt ファイルに書き込まれます。
次にコードを実行します
この時点でキーボード入力を待機するのではなく、ファイルから直接読み取ることがわかりました。
これは、fd=0 が指すキーボード ファイルをlog.txt ファイルに変更することと同じです。
このプロセスは入力リダイレクトと呼ばれます。
リダイレクトを追加
これは出力リダイレクトに似ていますが、open 関数の O_TRUNC が O_APPEND に置き換えられる点が異なります。
コードは以下のように表示されます。
int main()
{
close(1);
int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
if(fd < 0)
{
perror("open");
return 0;
}
fprintf(stdout,"you can see me\n");
}
このプログラムは当初、毎回「you can see me」をディスプレイに出力し、その後リダイレクトされ、最終的に log.txt に追加されました。
これが追加リダイレクトです。
dup2()
上記のコードはリダイレクトの原理を説明するためのものであり、実際にリダイレクトを使用する場合はそのようには使用せず、使用するたびにファイルを閉じてから開く必要があります。このように書かれていることは高くありません。
したがって、この問題を解決するには、dup2 システム コールを使用します。
まず dup2 の使用法を見てみましょう。
次に、もう一度説明を見てください。
これはnewfd が oldfd のコピーである、つまり oldfd が newfd にコピーされることを意味し、最終的には oldfd と一致する必要があります。
oldfd が有効なファイル記述子でない場合、呼び出しは失敗し、newfd は閉じられません。
oldfd が有効なファイル記述子の場合、oldfd の値が newfd にコピーされます。
冒頭で説明した出力リダイレクトに戻ります。
最後に、fd=1 が指す内容を変更しました。つまり、fd=3 の元のファイル ポインタを fd=1 のファイル ポインタにコピーしました。
最終的な結果は fd=3 と一致します。上記の文で「最終的には oldfd と一致する必要がある」と書かれているように、fd=3 は oldfd、 fd=1 は newfd となり、関係が明確になります。
これを知った上で、早速 dup2 を使ってみましょう。
int main(int argc, char* argv[])
{
if(argc != 2)
{
return 2;
}
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
if(fd < 0)
{
perror("open");
return 1;
}
dup2(fd,1);//将fd(3)的内容拷贝到fd=1当中
fprintf(stdout,"%s\n",argv[1]);
}
argc と argv はコマンド ライン パラメーターです。詳細については、以前の記事「Portal 」で詳しく説明していますので、ご参照ください。
次に、この時点でプログラムを実行します。
これにより、ディスプレイに出力されるべきコンテンツがファイルに出力されます。
以上でdup2の一般的な使い方は完了です。
この記事はここまでです。質問や間違いがある場合は、コメント欄またはプライベート メッセージで修正してください。