Linuxファイル、ファイル記述子、dup()およびdup2()



Linux Files File Descriptors



A.Linuxファイル

通常ファイル、カタログファイル、リンクファイル、デバイスファイルの4種類に分類できます。




1、普通文書


これは、テキストファイル、シェルスクリプト、バイナリ実行可能ファイル、さまざまな種類のデータなど、ユーザーが最もよく使用するファイルです。




Ls -lhでファイルのプロパティを表示すると、同様の-rw-r--r--があることがわかります。最初の記号は-であることに注意してください。このようなファイルは、Linuxでは通常のファイルです。これらのファイルは通常、画像ツール、ドキュメントツール、アーカイブツール...、cpツールなどの関連アプリケーションで作成されます。このようなファイルを削除する方法は、rmコマンドを使用することです。


2、カタログファイル


Linuxでは、ディレクトリもファイルであり、ファイル名とサブディレクトリへのポインタであり、それらのファイルとサブディレクトリへのポインタです。




ディレクトリで実行してdrwxr-xr-xのようなものを見ると、そのようなファイルはディレクトリであり、そのディレクトリはLinuxの特別なファイルです。最初の文字はdであることに注意してください。ディレクトリを作成するコマンドは、mkdirコマンドまたはcpコマンドを使用して、ディレクトリを別のディレクトリにコピーできます。 rmまたはrmdirを使用してコマンドを削除します。


3、リンクファイル


リンクされたファイルは、Windowsの「ショートカット」に似ています。


ln-sソースファイル名新しいファイル名で作成されます。


4、機器ファイル


2つのブロックデバイスファイルが含まれ、もう1つはキャラクターデバイスファイルです


ブロックデバイスファイルとは、データの読み取りと書き込みを指します。これらは、ハードディスクドライブなどのブロックユニット内のデバイスです。


文字デバイスとは、主にネットワークカードなどのシリアルポートインターフェイスデバイスを指します。

次に、ファイル記述子

1、ファイル記述子とその役割


カーネルはファイル記述子を使用してファイルにアクセスします。ファイル記述子は負でない整数です。既存のファイルを開くとき、または新しいファイルを作成するとき、カーネルはファイル記述子を返します。ファイルの読み取りと書き込みでは、ファイル記述子を使用して、読み取りと書き込みを行うファイルを指定する必要もあります。Linuxの場合、デバイスとファイルに対するすべての操作は、ファイル記述子を使用して行われます。ファイル記述子は、インデックス値であり、カーネル内の各プロセスがファイルを開くレコードテーブルを指す非負の整数です。既存のファイルを開くとき、または新しいファイルを作成するとき、カーネルはファイルの読み取りと書き込みが必要になったときにファイル記述子をプロセスに返します。


また、ファイル記述子をパラメーターとして対応する関数に渡す必要があります。


通常、プロセスが開始されると、標準入力、標準出力、および標準エラー処理の3つのファイルが開かれます。これらの3つのファイルは、ファイル記述子0、1、および2に対応します。つまり、マクロはSTDIN_FILENO、STDOUT_FILENO、およびSTDERR_FILENOに置き換わるため、代わりにこれらのマクロを使用することをお勧めします。)



LINUXのデフォルトのファイル記述子で合計1024を確認します。これは、ほとんどの状況で十分です。

#ulimit -n


プロセスIDを表示

#ps to


プロセスファイル記述子を取得する

cd / proc / [pid] / fd
[pid]は、対応するプロセスのpidです。


#cd / proc / 1473 / fd



#sysctl -a | grep fs.file

Nrはすでに使用されています

参照:百科事典 http://baike.baidu.com/view/1303430.htm


三。Dupとdup2

Dupとdup2も2つの非常に便利な呼び出しであり、これらはすべてファイルの記述子をコピーするために使用されます。
これらは、プロセスのstdin、stdout、およびstderrをリダイレクトするためによく使用されます。
これら2つの関数のプロトタイプは次のとおりです。
#include
int dup(int oldfd)
int dup2(int oldfd、int targetfd)
関数dupを使用して、記述子をコピーできます。関数に既存の記述子を渡すと、新しい記述子が返されます。
この新しい記述子は、渡された記述子のコピーです。これは、2つの記述子が同じデータ構造を共有することを意味します。例えば、
ファイル記述子に対してlseekを実行すると、最初に取得されたファイルの位置は2番目のファイルと同じになります。
以下は、dup関数の使用方法を説明するスニペットです。
int fd1、fd2
..。
fd2 = dup(fd1)


forkを呼び出す前に記述子を作成できることに注意してください。これは、dupを呼び出して記述子を作成するのと同じです。
子プロセスは、コピーされた記述子も受け取ります。
dup2関数はdup関数に似ていますが、dup2関数を使用すると、呼び出し元は有効な記述子とターゲット記述子のIDを指定できます。 dup2関数が正常に戻ると、
ターゲット記述子(dup2関数の2番目の引数)は、ソース記述子(dup2関数の最初の引数)のレプリカになります。つまり、
両方のファイル記述子が同じファイルを指すようになり、関数の最初の引数が指すファイルになります。以下では、コードの一部を使用して説明します。
int oldfd
oldfd = open( 'app_log'、(O_RDWR | O_CREATE)、0644)
dup2(oldfd、1)
close(oldfd)
この場合、「app_log」という名前の新しいファイルを開き、fd1という名前のファイル記述子を受け取りました。 dup2関数を呼び出します。
パラメータはoldfdと1です。これにより、1で表されるファイル記述子が新しく開いたファイル記述子に置き換えられます(つまり、標準出力ファイルのIDが1であるためstdout)。
stdoutに書き込まれるものはすべて、「app_log」という名前のファイルに書き込まれるようになります。
dup2関数は、コピーされた後すぐにoldfdを閉じますが、ファイル記述子1がそれを指しているため、新しく開かれたファイル記述子を閉じないことに注意してください。
以下に、より詳細なサンプルコードを紹介します。この記事で前述したコマンドラインパイプラインを思い出してください。ここでは、ls –1コマンドの標準出力を標準入力として使用しています。
wc –lコマンドに接続します。次に、Cプログラムを使用して、このプロセスの実装を説明します。コードは、以下のサンプルコード3に示されています。
サンプルコード3では、最初に9行目にパイプを作成してから、アプリケーションを2つのプロセスに分割します。子プロセス(13〜16行目)
そして、親プロセス(20〜23行目)。次に、最初に子プロセスのstdout記述子(13行目)を閉じてから、ls –1コマンド関数を提供します。
ただし、stdout(13行目)に書き込む代わりに、作成したパイプラインの入力に書き込みます。これは、dup関数によって実行されます。 14行目
dup2関数を使用して、stdoutをパイプ(pfds [1])にリダイレクトします。その後、すぐにパイプの入力をオフにしてください。次に、execlp関数を使用して子プロセスを配置します
イメージはコマンドls–1のプロセスイメージに置き換えられ、コマンドが実行されると、その出力のいずれかがパイプの入力に送信されます。
それでは、パイプラインのレシーバーについて調べてみましょう。コードからわかるように、パイプラインの受信側は親プロセスです。最初にstdin記述子を閉じます(20行目)。
なぜなら、マシンのキーボードなどの標準的なデバイスファイルからデータの入力を受け取るのではなく、他のプログラムの出力からデータを受け取るからです。次に、もう一度dup2関数を使用します(21行目)。
ファイル記述子0(つまり通常のstdin)をpfds [0]と等しくすることにより、stdinをパイプラインの出力とします。パイプの標準出力の端を閉じます(pfds [1])、
ここでは使わないので。最後に、execlp関数を使用して、親プロセスのイメージをコマンドwc-1およびコマンドwc-1のプロセスイメージに置き換え、パイプの内容を入力として取得します(23行目)。
サンプルコード:Cを使用してコマンドのパイプライン操作を実装するコード
#include
#include
#include

int main()
{{
int pfds [2]

If(pipe(pfds)== 0){//パイプを作成する

If(fork()== 0){//子プロセス

Close(1)// stdout記述子を閉じます
Dup2(pfds [1]、1)// stdoutをパイプにリダイレクトします(pfds [1])
Close(pfds [0])//パイプの入力をオフにします
Execlp( 'ls'、 'ls'、 '-1'、NULL)//子プロセスのイメージをコマンドls –1のプロセスイメージに置き換えます

} else {//親プロセス

Close(0)// stdin記述子を閉じます
Dup2(pfds [0]、0)// stdinをパイプの出力にします
Close(pfds [1])//パイプの標準出力の端を閉じます(pfds [1])
Execlp( 'wc'、 'wc'、 '-l'、NULL)//親プロセスのイメージをコマンドwc-1のプロセスイメージに置き換えます

}

}

0を返す
}


このプログラムで特に懸念されるのは、子プロセスがその出力をパイプの入力にリダイレクトし、次に親プロセスがその入力をパイプの出力にリダイレクトすることです。
これは、実際のアプリケーション開発で非常に役立つ手法です。
1.カーネル内のファイル記述子データ構造
dup / dup2について具体的に説明する前に、まずカーネル内のファイル記述子の形状を理解する必要があると思います。
プロセスの存在中に、いくつかのファイルが開かれ、シェルからいくつかのファイル記述子が返されます。
プロセスを実行すると、デフォルトで3つのファイル記述子(0、1、2)があり、0はプロセスの標準入力に関連付けられています。
1はプロセスの標準出力に関連付けられ、2はプロセスの標準エラー出力に関連付けられ、どちらが現在プロセスによって開かれています。
ファイル記述子は、/ proc / process ID / fdディレクトリで表示できます。次の図は、問題を明確に示しています。
プロセステーブルエントリ


————————————————
Fdフラグファイルポインタ
_____________________


Fd 0:| ________ | ____________ | ------------>ファイルテーブル


fd 1:| ________ | ____________ |


fd 2:| ________ | ____________ |


fd 3:| ________ | ____________ |


| ....... |


| _____________________ |


図1
ファイルテーブルには、ファイルステータスフラグ、現在のファイルオフセット、vノードポインタが含まれていますが、これらについてはこの記事では説明していません。
重要なのは、開いている各ファイル記述子(fdフラグ)がプロセステーブルに独自のファイルテーブルを持っていることだけを知っておく必要があるということです。
ファイルポインタが指すアイテム。
2. dup / dup2関数
APUEとmanの両方のドキュメントは、これら2つの機能の役割を1つの文で簡潔に述べています。既存のファイル記述子をコピーします。
#include
int dup(int oldfd)
int dup2(int oldfd、int newfd)
図1からこのプロセスを分析します。dup関数が呼び出されると、カーネルはプロセス内に新しいファイル記述子を作成します。
記述子は、現在使用可能なファイル記述子の最小値です。このファイル記述子は、oldfdが所有するファイルエントリを指します。
プロセステーブルエントリ


————————————————


Fdフラグファイルポインタ


_____________________


fd 0:| ________ | ____________ | ______


fd 1:| ________ | ____________ | ----------------> | |


Fd 2:| ________ | ____________ | |ドキュメントテーブル|


fd 3:| ________ | ____________ | ----------------> | ______ |


| ....... |


| _____________________ |


図2:dupを呼び出した後の回路図
図2に示すように、oldfdの値が1で、現在のファイル記述子の最小値が3の場合、新しい記述子3は
記述子1が所有するファイルエントリ。
dup2とdupの違いは、newfdパラメーターを使用して新しい記述子の値を指定できることです。 newfdがすでに開いている場合は、
最初にオフにします。 newfdがoldfdと等しい場合、dup2はnewfdを閉じずに返します。 dup2関数によって返される新しい
ファイル記述子も、パラメータoldfdと同じファイルエントリを共有します。
APUEは、この問題を別の方法で示しています。
実際、dup(oldfd)を呼び出します
同等
fcntl(oldfd、F_DUPFD、0)
そして、dup2(oldfd、newfd)を呼び出します
同等
close(oldfd);
fcntl(oldfd、F_DUPFD、newfd);
3.CGIのDup2
CGIプログラムを作成したことのある人なら誰でも、ブラウザがpostメソッドを使用してフォームデータを送信すると、CGIの読み取りデータが標準のものであることを知っています。
stdinと入力すると、書き込みデータは標準出力stdout(printf関数を使用したc言語)に書き込まれます。私たちの通常の理論によると
解決策、printf出力をターミナルに表示する必要があります。元のCGIプログラムはdup2関数を使用してSTDOUT_FINLENOを実行します(これは
マクロはunitstd.hで定義されています。これは1)このファイル記述子は接続ソケットにリダイレクトされます。
Dup2(connfd、STDOUT_FILENO)/ *実際の状況にはパイプラインも含まれ、この記事の焦点では​​ありません* /
最初のセクションで説明したように、プロセスのデフォルトのファイル記述子1(STDOUT_FILENO)は、標準出力stdoutと比較されます。
関連して、カーネルの場合、開いているすべてのファイルはファイル記述子によって参照され、カーネルはストリームを認識しません
存在する(stdin、stdoutなど)ので、stdoutデータに出力されるprintf関数はファイルの説明に書き込まれます
シンボル1の内部。標準入力、標準出力、標準エラー出力に関連付けられたファイル記述子0、1、2については、これ
これは単なるシェルであり、多くのアプリケーション規則であり、カーネルとは関係ありません。
次のフロー図を使用して、問題を説明します。(ps:フローグラフの関係ではありませんが、理解しておくと役立ちます)
Printf-> stdout-> STDOUT_FILENO(1)->ターミナル(tty)
printfの最終出力は端末デバイスに向けられ、ファイル記述子1は現在の端末を指します。
STDOUT_FILENO = open( '/ dev / tty'、O_RDWR)
dup2を使用した後、STDOUT_FILENOは端末デバイスを指しませんが、connfdを指すため、printf
出力は最終的にconnfdに書き込まれます。とても綺麗ですか? :)
4.CGIプログラムのフォークサブプロセスでSTDOUT_FILENOを復元する方法
ここでそれを見ることができれば、あなたの忍耐に感謝します、私は多くの人々が実際に少し複雑に感じるかもしれないことを知っています
複雑な問題は、小さな問題の集まりです。したがって、第3四半期に、あらゆる小さな問題を把握することは問題ありません。
つまり、STDOUT_FILENOはconnfdソケットにリダイレクトされたため、CGIプログラムに参加したい場合があります。
バックグラウンドスクリプト実行を呼び出すと、必然的にこれらのスクリプトに何らかの入力と出力があります。フォークの後、
子プロセスは親プロセスのすべてのファイル記述子を継承するため、これらのスクリプトの入力と出力は希望どおりになりません。
ターミナルデバイスへの出力ですが、connfdに関連付けられているため、これによりWebページの出力が明らかに中断されます。だからどのように
STDOUT_FILENOと端末の関連付けを復元しますか?
方法1:dup2の前に元のファイル記述子を保存してから、復元します。
コードは次のように実装されています。
Savefd = dup(STDOUT_FILENO)/ * savefdはこの時点で端末を指します* /
Dup2(connfd、STDOUT_FILENO)/ * STDOUT_FILENO(1)はconnfdにリダイレクトされます* /
..... / *いくつかのことを処理します* /
Dup2(savefd、STDOUT_FILENO)/ * STDOUT_FILENO(1)ポイントをsavefdに復元します* /
残念ながら、dup2はCGIプログラムでは実行されないため、CGIプログラムではこのメソッドを使用できません。
Webサーバーに実装されているため、Webサーバーを変更することはお勧めできません。
方法2:ソースをトレースし、現在の端末を開いてSTDOUT_FILENOを復元します。
3番目のセクションのフローチャートを分析します。STDOUT_FILENOは端末にどのように関連付けられていますか?二度とできません。
コードは次のように実装されています。
ttyfd = open( '/ dev / tty'、O_RDWR)
dup2(ttyfd、STDOUT_FILENO)
close(ttyfd)
/ dev / ttyは、プログラムが実行されている端末です。これは、メソッドによって取得する必要があります。実践はこの方法を証明しました
それは実行可能ですが、私はいつも何かが間違っていると感じています。理由はわかりませんが、潜在的な問題がまだ発生していない可能性があります。