[Linux]ファイル記述子
記事ディレクトリ
ファイルシステムインターフェース
ファイル記述子について学ぶ前に、まず Linux システムで一般的に使用されるファイル システム インターフェイスを理解する必要があります。
オープン関数
//open函数所在的头文件和函数声明
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
- open 関数には 2 つのインターフェイスがあり、3 パラメーター インターフェイスは、2 パラメーターのインターフェイスにファイル作成の権限を制御する機能を追加し、ファイルの書き込み時に使用するのに適しています。
- pathname パラメータ – 開かれるファイルのパス
- flags パラメータ – ファイルを開く方法
- mode パラメータ – ファイルを作成する場合は、ファイルのアクセス許可
- 関数は新しいファイル記述子を正常に返し、失敗した場合は -1 を返し、エラー コードが設定されます。
flags パラメータは、ビットマップ構造を使用して、操作対象のファイルのオープン モードを受け取ります。ここでのビットマップ構造の役割は、1 つのパラメータだけを使用して複数のフラグ情報を渡すことです。ビットマップ構造の具体的な形式は、フラグを変更することです。 32 ビット整数変数の各ビットを個別とみなし、各ビットがフラグ情報を表すビットマップ構造の模式図は次のとおりです。
flags パラメータの最後のビットがファイルをクリアするかどうかを示すものとします。このビットが 1 の場合はファイルをクリアする必要があることを示し、0 の場合はファイルをクリアする必要がないことを示します。
open 関数をテストするには、次のコードを作成します。
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG "log.txt"
int main()
{
int fd = open(LOG, O_WRONLY);
if (fd == -1)
{
printf("fd: %d, errno: %d, errstring: %s\n", fd, errno, strerror(errno));
exit(1);
}
return 0;
}
コードをコンパイルして実行し、結果を確認します。
log.txt ファイルが現在のパスに存在せず、O_WRONLY
オープン モードが読み取りモードでファイルを開くことになっているため、open 関数の使用は失敗します。エラー メッセージから、失敗の理由もわかります。ファイルが存在しないということです。
次に、次のコードを記述して、open 関数をテストします。
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG "log.txt"
int main()
{
int fd = open(LOG, O_WRONLY | O_CREAT);
if (fd == -1)
{
printf("fd: %d, errno: %d, errstring: %s\n", fd, errno, strerror(errno));
exit(1);
}
return 0;
コードをコンパイルして実行し、結果を確認します。
プログラムを実行すると、ファイルのアクセス許可に問題があることがわかり、ファイル名が強調表示されます。今回使用したモードオープンモードは、ファイルが存在しない場合にファイルを作成することですが、アクセス許可O_WRONLY | O_CREAT
がO_CREAT
設定されていない場合、ファイルのアクセス許可に問題があります。
次に、次のコードを記述して、open 関数をテストします。
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG "log.txt"
int main()
{
umask(0);
int fd = open(LOG, O_WRONLY | O_CREAT, 0666);
if (fd == -1)
{
printf("fd: %d, errno: %d, errstring: %s\n", fd, errno, strerror(errno));
exit(1);
}
else printf("fd: %d, errno: %d, errstring: %s\n", fd, errno, strerror(errno));
return 0;
}
コードをコンパイルして実行し、結果を確認します。
3 つのパラメーターのオープン関数インターフェイスの 3 番目のインターフェイスは、ファイルの作成時にファイルのアクセス許可を設定する機能を提供します。さらに、このコードでは関数が使用され、プロセスで使用される umask 値を設定して、作成されたファイルのアクセス許可が確実に有効であることを確認しますumask
。あなたが望むもの。
//umask函数所在的头文件和函数声明
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
閉じる関数
//close函数所在的头文件和函数声明
#include <unistd.h>
int close(int fd);
- close 関数は、ファイルの内容に関する問題を回避するためにファイルを閉じます。
- 対応するファイルを閉じるには、open 関数を使用してファイルを開くときに、close 関数で戻り値 (ファイル記述子) を渡す必要があります。
- 成功した場合は 0、失敗した場合は -1 を返し、エラー コードを設定します。
次のコードを作成して、close 関数をテストします。
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG "log.txt"
int main()
{
umask(0);
int fd = open(LOG, O_WRONLY | O_CREAT, 0666);
if (fd == -1)
{
printf("fd: %d, errno: %d, errstring: %s\n", fd, errno, strerror(errno));
exit(1);
}
else printf("fd: %d, errno: %d, errstring: %s\n", fd, errno, strerror(errno));
close(fd);
return 0;
}
コードをコンパイルして実行し、結果を確認します。
書き込み関数
//write函数所在的头文件和函数声明
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
- write 関数は、対応するファイルにデータを書き込むことができます
- fd パラメータ – open 関数でファイルを開くときの戻り値 (ファイル記述子)
- buf パラメータ – ファイルに書き込まれるデータ
- count パラメータ – ファイルに書き込まれるデータのバイト数
- 正常に書き込まれたデータのバイト数を返し、失敗した場合は -1 を返し、エラー コードを設定します。
次のコードを作成して、書き込み関数をテストします。
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG "log.txt"
int main()
{
umask(0);
int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd == -1)
{
printf("fd: %d, errno: %d, errstring: %s\n", fd, errno, strerror(errno));
exit(1);
}
else printf("fd: %d, errno: %d, errstring: %s\n", fd, errno, strerror(errno));
const char* msg = "hello world\n";
write(fd, msg, strlen(msg));
close(fd);
return 0;
}
コードをコンパイルして実行し、結果を確認します。
例証します:
O_TRUNC
ファイルを開くときに最初にファイルをクリアするモードです。- ファイルにデータを書き込む場合は、記述する必要はありません
'\0'
。言語の文字列の終わりを示す役割があり、ファイル内では必須ではありません。
次に、次のコードを記述して書き込み関数をテストします。
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG "log.txt"
int main()
{
umask(0);
int fd = open(LOG, O_WRONLY | O_CREAT | O_APPEND, 0666);
if (fd == -1)
{
printf("fd: %d, errno: %d, errstring: %s\n", fd, errno, strerror(errno));
exit(1);
}
else printf("fd: %d, errno: %d, errstring: %s\n", fd, errno, strerror(errno));
const char* msg = "hello world i love you\n";
write(fd, msg, strlen(msg));
close(fd);
return 0;
}
コードをコンパイルして実行し、結果を確認します。
注: O_APPEND
このモードは追加メソッドを使用しており、O_WRONLY
追加書き込みを実現するにはモードと組み合わせて使用する必要があります。
読み取り機能
//read函数所在的头文件和函数声明
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
- read関数はファイル内のデータを読み取ることができます
- fd パラメータ – 読み取るファイルのファイル記述子
- buf パラメータ – ファイル内のデータを受け取る文字列変数のアドレス
- count パラメーター – buf が受信できるデータの最大量 (バイト単位)
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG "log.txt"
int main()
{
int fd = open(LOG, O_RDONLY);
if (fd == -1)
{
printf("fd: %d, errno: %d, errstring: %s\n", fd, errno, strerror(errno));
exit(1);
}
else printf("fd: %d, errno: %d, errstring: %s\n", fd, errno, strerror(errno));
char buffer[256];
ssize_t n = read(fd, buffer, sizeof(buffer)-1);
if (n > 0)
{
buffer[n] = '\0';
}
printf("buffer: %s", buffer);
close(fd);
return 0;
}
コードをコンパイルして実行し、結果を確認します。
例証します:
O_RDONLY
モードは読み取り用にファイルを開くことです- read 関数によって読み取られたデータを受け取るために使用される文字列は、最後にストレージ用の要素を予約する必要があります。
'\0'
システムインターフェースとプログラミング言語ライブラリ関数の関係
コンピュータ アーキテクチャにおいて、オペレーティング システムはユーザーにサービスを提供し、ハードウェアを管理する部分であり、ファイル操作にはハードウェアが使用されるため、プログラミング言語ライブラリ関数のファイル操作はシステム上で実行する必要があります。どのプログラミング言語であってもインターフェースの仕組みは同じなので、ファイル操作の本質はどのプログラミング言語であっても同じです。コンピュータのアーキテクチャ図は次のとおりです。
ファイル記述子
ファイルディスクリプタの概念
ファイル記述子について学ぶ前に、Linux オペレーティング システムのファイル システム インターフェイスを観察すると、ファイル操作がファイル記述子と密接に関連していることがわかることを知っておく必要があります。open 関数の戻り値、close 関数のパラメータ、書き込み関数、および読み取り関数はすべてファイルの説明です。
まず、ファイル操作を行う際、オペレーティング システムはファイルをディスクからメモリにロードするのではなく、メモリ上にファイルを記述する構造体 (以下に示す構造体ファイル) を作成し、この構造体でバッファを管理します。メモリとディスク。ファイル データの交換はこのバッファを介して行われます。概略図は次のとおりです。
次に、ファイル操作はシステム インターフェイスを呼び出すプロセスによって完了するため、プロセス制御ブロック (以下に示す struct task_struct) はこれらの記述ファイルの構造を記録する必要があるため、プロセス制御ブロックは構造を作成するブロックを適用します。 (下図に示す struct files_struct) 説明 プロセスが操作するファイルを、プロセス制御ブロック内の変数 (下図の struct files_struct*) を使用して、プロセスが操作するファイルのアドレスを記録します。概略図は次のとおりです。
最後に、プロセスが操作できるファイルの数が非常に多いため、配列 (以下に示す struct file* fd_array[]) を使用して、プロセスによって操作されるファイルの構造 (以下に示す struct files_struct) を記録します。プロセスによって操作されるすべてのファイル 記述構造 (以下に示す構造ファイル) のアドレス、およびこの配列の添え字はファイル記述子です。概略図は次のとおりです。
注:ファイル記述子の存在により、プロセス管理とファイル管理は軽く結合された状態になり、この 2 つはファイル記述構造へのポインターを使用してのみリンクされます。
ファイルデータ交換の原理
-
プロセスがシステム インターフェイスを呼び出し
write
た後、オペレーティング システムはディスク ファイルに転送されるデータをファイル記述構造で管理されているバッファにコピーし、オペレーティング システムは適切なタイミングでデータをディスク ファイルにリフレッシュします。独自のリフレッシュ戦略を採用しています。 -
プロセスがシステム インターフェイスを呼び出し
read
た後、オペレーティング システムはディスク ファイル内のデータをファイル記述構造で管理されるバッファにコピーし、バッファの内容をプロセスによって指定された場所にコピーします。
「すべてはファイルである」を理解する
Linux オペレーティング システムでは、すべての周辺機器がファイルとしてみなされるため、「Linux の下ではすべてがファイルである」と言われます。「すべてがファイルである」という理解は次のとおりです。
まず、オペレーティング システムがハードウェアを管理する場合、ハードウェア デバイスを直接管理するわけではありません。両者の間のやり取りは、中間ソフトウェアであるドライバーを介して完了する必要があります。周辺機器とコンピュータの間のやり取りは、データの書き込みと読み取りであるため、各ハードウェアに対応するドライバーには、そのハードウェアの読み取りおよび書き込みメソッドがあります。概略図は次のとおりです。
ドライバーはハードウェアに対応した読み取りおよび書き込みメソッド宣言を使用して設計されていますが、すべてが具体的に実装されているわけではありません (たとえば、キーボードには読み取りメソッドのみがあり、キーボードへの書き込みはできません)。
次に、Linux オペレーティング システムは、記述ファイル構造 (以下に示す構造体ファイル) を使用して各ペリフェラルを記述します。この構造は、関数ポインタの形式を使用して、ドライバーによって提供される読み取りおよび書き込みメソッドを記録し、その後、オペレーティング システムの動作を記録します。ペリフェラル データを書き込む場合、ファイルに対応するバッファにデータを書き込み、関数ポインタを使用して対応する関数メソッドを呼び出してペリフェラルにデータを書き込むだけで済みます。オペレーティング システムがペリフェラル データを読み取るとき必要なのは、関数ポインターを呼び出して対応するメソッドを呼び出し、ファイルに対応するバッファーにデータを書き込み、バッファーからデータを読み取ることだけです。概略図は次のとおりです。
すべてのペリフェラルは記述ファイルの構造を使用して統一的に記述され、プロセスはバッファを使用してデータを交換し、対応する読み取りおよび書き込みメソッドを呼び出して読み書きするため、操作方法はファイルの操作方法と同じです。プロセス コンピュータの観点から見ると、周辺機器とディスク ファイルは同じであるため、「Linux ではすべてがファイルである」と言われます。
プロセスのデフォルトのファイル記述子
プロセスの実行中、デフォルトでは、標準入力、標準出力、標準エラーの 3 つのファイルが開かれます。これを確認するには、次のコードを記述します。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG "log.txt"
int main()
{
int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);
printf("fd:%d\n", fd);
close(fd);
return 0;
}
コードをコンパイルして実行し、結果を確認します。
ファイル記述子は、プロセスが操作するファイルの構造を記録する配列の添字を表します。ファイル記述子の使用規則は、配列の開始位置から開始して、最初の未使用の位置を見つけることです。標準入力はオープンされます。デフォルトでは、プロセスの実行時に(ファイル記述子が 0 に対応)、標準出力(ファイル記述子が 1 に対応)、標準エラー(ファイル記述子が 2 に対応)なので、開かれるファイルのファイル記述子が開始されます。 3から。したがって、上記のコードでファイルを開くことによって取得されるファイル記述子は 3 であることがわかります。
ファイル記述子とプログラミング言語の関係
記述ファイルの情報は、 C 言語の標準ライブラリで使用されますstruct FILE
。標準入力はstdin
、標準出力はstdout
、標準エラーは ですstderr
。Linux オペレーティング システムが提供するファイル操作インターフェイスでは、ファイル記述子を使用する必要があるため、struct FILE
ファイルの説明を含める必要があります。文字フィールド。C言語はプロセスファイル操作とstruct FILE
強力な相関を実行します.C言語ライブラリ関数で提供されるファイル操作関数は、struct FILE
記録されたファイルディスクリプタを使用してLinuxシステムインターフェースを呼び出して完了します。Linux システムの C 言語では、ファイル記述子を記録するstruct FILE
変数があります。_fileno
これを確認するには、次のコードを記述します。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG "log.txt"
int main()
{
printf("stdin:%d\n", stdin->_fileno);
printf("stdout:%d\n", stdout->_fileno);
printf("stderr:%d\n", stderr->_fileno);
FILE* fp = fopen("LOG", "w");
printf("fp:%d\n", fp->_fileno);
fclose(fp);
return 0;
}
コードをコンパイルして実行し、結果を確認します。
Linux オペレーティング システムでは、どのようなプログラミング言語であっても、ファイル操作を使用する限り、Linux オペレーティング システムでファイル操作を完了するには、ファイル記述子をある程度カプセル化する必要があります。
リダイレクト
出力リダイレクト
出力リダイレクトは、プロセスがモニターに出力するデータをファイルに出力することです。
出力リダイレクトの実現原理:ファイル記述子 1 をパラメータとしてシステムインターフェース関数を呼び出すことで出力関数が実装され、ファイル記述子テーブルの位置 1 が指すファイルを変更することで出力リダイレクトが完了します。出力関数のカプセル化ではファイル記述子 1 のみが使用され、出力場所の変更を知る方法はありません。C 言語で実装され、データをprintf
出力しますstdout
が、stdout
ファイル記述子 1 をカプセル化するだけです。ファイル記述子テーブルで指定されたファイルが変更されたとしても、printf
それを知る方法はありません。
パターンの出力リダイレクトを実装するには、次のコードを作成します。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG "log.txt"
int main()
{
int fd = close(1);
open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);
printf("hello world\n");
printf("hello world\n");
printf("hello world\n");
close(fd);
return 0;
}
コードをコンパイルして実行し、結果を確認します。
入力リダイレクト
入力リダイレクトにより、本来キーボードからデータを取得する必要があるプロセスが、ファイルからデータを取得するように変更されます。
入力リダイレクトの実装原理:入力関数は、ファイル記述子 0 をパラメータとしてシステム インターフェイス関数を呼び出すことで実装され、ファイル記述子テーブルの位置 0 が指すファイルを変更することで入力リダイレクトが完了します。入力関数のカプセル化ではファイル記述子 0 のみが使用され、入力位置の変更を知る方法はありません。C言語で実装すると、からデータscanf
を取得しますが、カプセル化されるのはファイルディスクリプタ0のみであり、ファイルディスクリプタテーブルで指しているファイルが変更されても分かりません。stdin
stdin
scanf
パターンの入力リダイレクトを実装するには、次のコードを作成します。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG "log.txt"
int main()
{
close(0);
int fd = open(LOG, O_RDONLY);
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("%d %d\n", a, b);
close(fd);
return 0;
}
コードをコンパイルして実行し、結果を確認します。
リダイレクトを追加
追加リダイレクトは、プロセスがモニターに追加するデータをファイルに追加するように変更します。
追加リダイレクトの実装原理:出力関数で使用されるファイル記述子 1 を、指定されたファイルを指すように変更し、ファイルを開くときに append メソッドを使用します。
パターンに追加リダイレクトを実装するには、次のコードを記述します。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG "log.txt"
int main()
{
close(1);
int fd = open(LOG, O_WRONLY | O_CREAT | O_APPEND, 0666);
printf("hello world\n");
printf("hello world\n");
printf("hello world\n");
close(fd);
return 0;
}
コードをコンパイルして実行し、結果を確認します。
注:リダイレクトを実装する場合、次の入力または出力メソッドを選択することもできます。
fscanf(stdin, ...); //等效于scanf
fprintf(stdout, ...); //等效于printf
コマンドを使用してリダイレクトを完了します
Linux オペレーティング システムでは、指示を使用してリダイレクトを完了できます。確認するには、次のコードを作成します。
#include <stdio.h>
int main()
{
printf("hello->printf\n");
fprintf(stdout, "hello->fprintf stdout\n");
fprintf(stderr, "hello->fprintf stderr\n");
return 0;
}
コードをコンパイルして実行し、結果を確認します。
./myfile > log.txt
./myfile の出力を log.txt ファイルにリダイレクトします。つまり、log.txt のファイル情報をプロセスのファイル記述子 1 に書き込みます。つまり、プロセスの2>&1
ファイル記述子 1 のファイル情報を書き込みます。ファイル記述子 2 に。
システムコールをリダイレクトする
Linux オペレーティング システムには、リダイレクトできるシステム コールが用意されていますdup2
。
//dup2函数所在的头文件和声明
#include <unistd.h>
int dup2(int oldfd, int newfd);
- dup2 関数は、ファイル記述子 newfd に出力されたデータを、ファイル記述子 oldfd に対応するファイルに変更します。
- dup2 関数は、ファイル記述子 oldfd のファイル情報をファイル記述子 newfd のファイル情報に書き込みます。
確認するには、dup2
次のコードを記述します。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LOG "log.txt"
int main()
{
int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC);
dup2(fd, 1);
printf("this is printf\n");
fprintf(stdout, "this is fprintf->stdout\n");
close(fd);
return 0;
}
コードをコンパイルして実行し、結果を確認します。
C言語ファイルバッファ
C言語標準ライブラリが提供するstruct FILE
バッファフィールドを搭載しています C言語ライブラリ関数のファイル操作を使用してファイルにデータを書き込みます C言語標準ライブラリは対応するファイル内のバッファにデータを書き込むだけで、struct FILE
その後特定のリフレッシュ戦略と組み合わせると、バッファ内の内容が Linux オペレーティング システムのファイル バッファにリフレッシュされます。概略図は次のとおりです。
バッファリフレッシュ戦略:
-
バッファなし - データはバッファを経由せずにオペレーティング システムに直接書き込まれます。
-
行バッファリング - データがバッファに書き込まれた後
'\n'
、'\n'
前のデータがオペレーティング システムに書き込まれます。 -
フルバッファリング - バッファがいっぱいになるとデータがオペレーティングシステムに書き込まれます。
-
モニターが採用するリフレッシュ戦略: ラインバッファリング
-
通常のファイルに使用されるリフレッシュ戦略: フルバッファリング
バッファ内のデータをオペレーティング システムにフラッシュするにはシステム コールを使用する必要がありますが、システム コールの使用には時間がかかるため、バッファ フラッシュ戦略を使用すると効率が向上します。
struct FILE
C 言語でバッファを検証するには、次のコードを記述します。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define LOG "log.txt"
int main()
{
fprintf(stdout, "hello fprint->stdout\n");
const char* msg = "hello write\n";
write(1, msg, strlen(msg));
fork();
return 0;
}
コードをコンパイルして実行し、結果を確認します。
リダイレクトが実行されない場合、印刷データの場所はモニターであり、ラインバッファリング戦略が採用されているため、データは子プロセスが作成される前にオペレーティングシステムにフラッシュされます。リダイレクト後、印刷データの場所はdata は通常のファイルであり、完全にバッファリングされたストラテジですが、データがバッファを満たすのに十分ではないため、プロセスの説明後、つまり子プロセスの作成後にデータが更新されます。親プロセスと子プロセスのどちらが最初に更新しても、コピーオンライトが発生し、最終データがオペレーティング システムに 2 回更新されます。システム コールの呼び出しはwrire
オペレーティング システムに直接書き込まれるため、影響を受けません。