【Linux】-ファイルオペレーティングシステムコール

1.ファイル入出力機能

呼び出し可能なファイルI / O関数には、ファイルを開く、ファイルを読み取る、ファイルを書き込むなどがあります。UNIXシステムのほとんどのファイルI / Oには、開く、読み取り、書き込み、lseek、閉じるという5つの機能が必要です。カーネルの場合、開いているすべてのファイルはファイル記述子を通じて参照されます。既存のファイルを開くか、新しいファイルを作成すると、カーネルはファイル記述子をプロセスに返します。ファイルの読み取りまたは書き込みを行うときは、openまたはcreateを使用してファイル記述子を返し、ファイルを識別して、読み取りまたは書き込みのパラメーターとして渡します。次に、これらの5つの関数のいくつかをよく見てみましょう。

1. Open
はopen関数を呼び出してファイルを開くか作成します

#include<fcntl.h>
int open(const char* filename,int flag,.../*mode_t mode */);

(1)filename:開くまたは作成するファイルの名前。注意!ファイル名だけを指定すると、現在のパスの下で検索されるため、指定されたファイルパスと名前にする必要があります。
(2)フラグ:ファイルを開く方法を示します。以下の1つ以上の定数を使用してOR演算を実行し、フラグパラメーターを形成できます
。次の図の3つの定数は、1つだけを指定する必要があります
ここに画像の説明を挿入。次の図の定数は、オプションの
O_APPENDです。書き込まれるたびにファイルに追加されます。年末
O_CREAT:このファイルが存在しない場合は、それを作成します。ただし、現時点で
は、ファイルのアクセス許可ビットO_EXCL を指定するために使用される3番目のパラメーターモードを使用する必要があります。O_CREATも指定され、ファイルが存在する場合、エラーが発生します。このメソッドを使用して、ファイルが存在するかどうかをテストできます。ファイルが存在しない場合は、ファイルが作成されます。これにより、アトミック操作の作成とテストの両方が行われます。
O_TRUNC:このファイルが存在し、書き込み専用または読み取り/書き込み用に開かれている場合、長さは0に切り捨てられ
ます。同期の入出力オプションの
一部O_DSYNC:物理I / O操作が完了するまで各書き込みを待機させますが、書き込み操作の場合書き込まれたばかりのデータの読み取りには影響せず、ファイル属性が更新されるのを待ちません
O_RSYNC:パラメーターの読み取り操作が各ファイル記述子を作成し、ファイルの同じ部分への保留中の書き込み操作が完了するまで待機
O_SYNC:すべてを作成します2回目の書き込みは、物理I / O操作が完了するまで待機します。これには、書き込み操作によるファイル属性の更新に必要なI / Oが含まれます。

2、close
はclose関数を呼び出して、開いているファイルを閉じます

#include<fcntl.h>
int close(int filename);

ファイルを閉じると、プロセスがファイルに追加したすべてのレコードロックも解放されます。注意!プロセスが終了すると、カーネルは開いているすべてのファイルを自動的に閉じます。多くのプログラムは、開いているファイルをcloseで明示的に閉じることなく、この機能を利用します。

3. lseek
lseekを呼び出して、開いているファイルのオフセットを明示的に設定できます。開いている各ファイルには、「現在のファイルオフセット」が関連付けられています。一般に、読み取りおよび書き込み操作は現在のファイルオフセットから始まり、読み取りおよび書き込みバイト数だけオフセットを増やします。システムデフォルトによると、ファイルを開くとき、O_APPENDオプションが指定されていない限り、オフセットは0に設定されます。

#include<fcntl.h>
int lseek(int fd,int size,int flag);

(1)フラグ:移動マークであり、移動の開始位置です。SEEK_SETは、ファイルオフセットをファイルの先頭からsizeバイトに設定します。SEEK_CURは、ファイルオフセットを現在の値にサイズを加えた値に設定するためのもので、サイズは正または負にできます。SEEK_ENDは、ファイルのオフセットにファイルの長さを加えたサイズに設定するためのものであり、サイズは正または負にできます。
(2)戻り値:lseekが正常に実行されると、新しいファイルオフセットが返されます。注意!通常のファイルの場合、オフセットは負でない必要がありますが、デバイスによっては負のオフセットも許可される場合があります。したがって、lseekの戻り値を比較するときは、0未満かどうかをテストせず、-1と等しいかどうかをテストしてください。

lseekは現在のファイルオフセットをカーネルに記録するだけで、I / O操作は発生しません。このオフセットは、次の読み取りまたは書き込み操作で使用されます。ファイルオフセットは、ファイルの現在の長さよりも大きい場合があります。この場合、ファイルへの次の書き込みにより、ファイルが長くなり、実際に許可される穴がファイルに形成されます。ファイル内にあるが書き込まれていないバイトは0として読み取られます。また、新しく書き込まれたデータの場合、ディスクブロックを割り当てる必要がありますが、前述の空の領域の場合、ディスクブロックは必要ありません。

4、読み取り
は開いているファイルからデータを読み取るために読み取り関数を呼び出します

#include<fcntl.h>
int read(int fd,void *buf,size_t size);

(1)fd:openの戻り値で指定されたファイルの読み取り
(2)戻り値:成功した場合、読み取られたバイト数を返します。ファイルの終わりに達した場合、0を返します。次の状況では、実際に読み取られるバイト数が、読み取るのに必要なバイト数より少なくなる可能性があります。
a。ファイルを読み取るときに、必要なバイト数を読み取る前にファイルの終わりに達した
b。端末デバイスから読み取る場合、通常は一度に最大で1行を
読み取るc。ネットワークから読み取る場合、ネットワークのバッファーメカニズムが戻り値を引き起こす可能性がある読み取りに必要なバイト数より少ない
d。パイプまたはFIFOから読み取るときに、パイプに必要なバイト数より少ない場合、読み取りは実際に利用可能なバイト数のみを返します
e。特定の側面から記録する場合デバイス(テープなど)は、一度に最大で1つのレコードを読み取ります
。f。信号が割り込みを引き起こす場合
(3)void *は、一般的なポインターを示すために使用されます。

5、書き込み
は、開いているファイルにデータを書き込むために書き込み関数を呼び出します

#include<fcntl.h>
int write(int fd,void *buf,size_t size);

彼の戻り値はパラメーターサイズの値と同じですが、それ以外の場合はエラーを意味します。エラーの一般的な原因は、ディスクがいっぱいであるか、特定のプロセスのファイル長の制限を超えていることです。
通常のファイルの場合、書き込み操作はファイルの現在のオフセットから始まります。ファイルを開くときにO_APPENDオプションを指定すると、各書き込み操作の前に、ファイルオフセットがファイルの現在の終わりに設定されます。書き込みが成功すると、ファイルオフセットにより、実際に書き込まれるバイト数が増加します。

6、dupおよびdup2関数
これら2つの関数は、既存のファイル記述子をコピーするために使用されます。

#include<unistd.h>
int dup(int fileds);
int dup2(int fileds,int fileds2);

dupによって返される新しいファイル記述子は、現在使用可能なファイル記述子の中で最小の値でなければなりません。dup2では、filedes2パラメータを使用して、新しい記述子の値を調整できます。filedes2がすでに開いている場合は、まず閉じます。filedsがfileds2と等しい場合、dup2はfiledes2を閉じずに返します。

7、stat、fstat、lstat関数

#include<sys/stat.h>
int stat(const char *restrict pathname,struct stat *restrict buf);
int fstat(int fileds,struct stat *buf);
int lstat(const char *restrict pathname,struct stat *restrict buf);

戻り値:3つの関数はすべて、成功した場合は0を返し、エラーの場合は-1を返します。statは、指定されたファイルに関連する情報構造を返します。fstat関数は、ファイル記述子filedsで開かれたファイルに関する情報を取得します。lstack関数はstatに似ていますが、指定されたファイルがシンボリックリンクの場合、lstatはシンボリックリンクに関する情報を返します。情報。

二、原子操作

アトミック操作とは、複数のステップで構成される操作のことです。アトミックに実行される場合、すべてのステップが実行されるか、1つのステップが実行されないかのどちらかです。すべてのステップのサブセットのみを実行することはできません。
(1)ファイルへの追加
前述のopen操作にはO_APPENDオプションはありません。単一のプロセスには影響はありませんが、マルチプロセスがこのメソッドを使用して同じファイルに同時にデータを追加すると、問題が発生します。データ構造間の関係は共有されているため、同じファイルを追加する2つの独立したプロセスAとBがあると想定されます。各プロセスはファイルを開いていますが、それぞれO_APPENDフラグを使用していません。プロセスには独自のファイルエントリがありますが、vノードエントリを共有しているため、2つのプロセスの書き込み操作により、ファイル内のデータが上書きされます。
論理演算は「ファイルの末尾に移動して書き込み」を行い、2つの個別の関数呼び出しを使用します。この問題の解決策は、これら2つの操作が他のプロセスのアトミック操作になることです。
(2)preadおよびpwrite関数
関数プロトタイプは次のとおりです。

#include<unistd.h>
ssize_t pread(int flags,void *buf,size_t nbytes,off_t offset);
ssize_t pwrite(int flags,void *buf,size_t nbytes,off_t offset);

戻り値:Preadはバイト数を読み取り、ファイルの終わりに達した場合は0を返し、失敗した場合は-1を返します。pwriteは、成功した場合は書き込まれたバイト数を返し、エラーが発生した場合は-1を返します。predの
呼び出しは、順次呼び出しと同じです。lseekとread、pwriteの呼び出しは、lseekとwriteの順次呼び出しと同等

3.例

演習1:
I / O操作を実行するための上記の基本機能を使用して、演習を行い、ユーザーがインターフェイスで入力したデータをa.txtに保存し、a.txtのすべての内容を端末に表示します上へ

int main()
{
	int fd = open("a.txt", O_RDWR | O_CREAT, 0664);//权限设置值
	assert(-1 != fd);
	
	while(1)
	{
		printf("input: ");
		char buff[128] = {0};
		fgets(buff,128,stdin);//从用户获取数据,stdin标准输入,会把最后的回车符也放在buff中
		
		if(strncmp(buff,"end",3) == 0)
		{
			break;
		}
		
		int n =write(fd,buff,strlen(buff));
		if(n<=0)
		{
			perror("write error:");//和printf很像,但是他主要是打的出错信息
			exit(0);
		}
	}
	
	printf(****************************a.txt:*************************\n);
	lseek(fd,0,SEEK_SET);//将文件读写游标移动到开始位置
	
	while(1)
	{
		char buff[128] = {0};//从文件里面读取数据往buff中写
		int n = read(fd,buff,127);
		if( n == 0 )
		{
			printf("END\n");
			break;
		}
		else if(n<0)
		{
			perror("read error: ");
			exit(0);
		}
		else
		{
			printf("%s",buff);
		}
	}
	close(fd);
}

演習2:
コードを介して親プロセスと子プロセスをテストし、フォークの前に開かれたファイル記述子を共有する

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>

int main()
{
	int fd = open("a.txt", O_RDWR | O_CREAT, 0664);//权限设置值
	assert(-1 != fd);

	pid_t n = fork();
	assert(-1 != n);

	if(0 == n)
	{
		while(1)
		{
			char c = 0;
			int len = read(fd, &c, 1);
			if(n <= 0)
			{
				break;
			}
			printf("child:: %c\n", c);
			sleep(1);
		}
	}
	else
	{
		while(1)
		{
			char c = 0;
			int len = read(fd, &c, 1);
			if(n <= 0)
			{
				break;
			}
			printf("father:: %c\n", c);
			sleep(1);
		}
	}

	close(fd);

	exit(0);
}

2つの異なる実行結果は次のとおりです。
ここに画像の説明を挿入
上記の実行結果から、親プロセスと子プロセスの両方がフォークにアクセスできるようになる前にファイル記述子が開かれ、ファイルの読み取りと書き込みのオフセットが共有されることがわかります。キャラクターは共有されません内部実装プロセスは次のとおりです。
ここに画像の説明を挿入

第4に、ライブラリ関数とシステムコール関数の違い

(1)概念
まず、ライブラリ関数とシステムコール関数とを明確にする必要があります。前に学習したfopen、fread、fwrite、fclose、およびfseekはすべて、参照するライブラリ関数であり、この記事の冒頭に記載した読み取り、書き込み、閉じるなどはシステムコール関数です。たとえば、私たちはよくこのような質問に遭遇します。freadとreadのどちらがより効率的ですか?実際には、高効率の絶対読み取りではありません。ファイルの読み取りが少ない場合、freadはユーザーモードからカーネルモードへの呼び出し消費があるためですが、大量のデータを読み取る場合、fread操作はすべてです。ユーザーアクセス領域に、ユーザーがどれだけの量を取得するために使用するかを示しますが、読み取り操作は、どのくらいの量のデータを読み取るかを示します。
これは、ライブラリ関数とシステムコール関数の概念につながります。システムコール関数は、システムカーネルがユーザー空間で呼び出すために実行するインターフェイスであり、システムコール関数はユーザーモードによって呼び出され、カーネルモードで実行されます。これに対応するのがライブラリ関数です。ライブラリ関数は関数ライブラリファイルに実装されており、実行中はユーザーモードでのみ実行する必要があります。

(2)違い
実際には、概念的には両者の違いを明確に知ることができ、ライブラリ関数は関数ライブラリファイルにあり、システムコール関数はシステムカーネルに実装されています。次に、openを列としたシステムコール関数の実装原理を注意深く説明します。
私たちのシステムでは、それらの間の関係は次の図に示すとおりです
ここに画像の説明を挿入
。1.最初に関数に対応するシステムコール番号を見つけ、それをexaレジスター
2に保存します。システムコール関数は0x80割り込みをトリガーし、次にカーネルに入り、カーネルは割り込みの実行を開始します。ハンドラ。0x80割り込みの重要な命令は次のとおりです

call [_sys_call_table+eax*4]

この命令は主に、eaxレジスターに格納されているシステムコール番号がカーネルシステムコールテーブル内のカーネル関数メソッドを見つけて実行するためのものです。
3.関数が呼び出された後、戻り値fdと整数値があるので、整数値をeaxレジスターに入れてユーザーモードに切り替え、mov命令でeaxレジスターの値をfdが指すアドレスに移動します。これは、関数の戻り値を保存することと同じです。
具体的なプロセスは次のとおりです。
ここに画像の説明を挿入

公開された98元の記事 ウォンの賞賛9 ビュー3641

おすすめ

転載: blog.csdn.net/qq_43412060/article/details/105460239