Linux ファイル I/O: 原則と方法
ファイルは、Linux システムで最も基本的で一般的に使用されるデータ保存方法であり、テキスト ファイル、バイナリ ファイル、デバイス ファイル、ディレクトリ ファイルなどがあります。ファイルの読み取りと書き込みは、Linux プログラミングで最も重要な操作の 1 つであり、ファイル記述子、バッファー、システム コール、ライブラリ関数などの概念が含まれます。この記事では、オープン、クローズ、読み取り、書き込み、位置決め、切り捨て、同期などの操作を含む Linux ファイル I/O の基本原理と方法を紹介し、その使用方法と注意事項を例を挙げて説明します。
ファイル記述子
後続のシステム コール (read(2)、write(2)、lseek(2)、fcntl(2) など) で使用する小さな非負の整数 ($man 2 open)。プログラムの実行開始時通常、3 つのオープン ファイル記述子があります:
- 0: STDIN_FIFLENO、標準入力 stdin
- 1: STDOUT_FILENO、標準出力 stdout
- 2: STDERR_FILENO、標準エラー stderror
fd原則
- fd は 0 から開始し、最小の未使用記述子を探し、ファイル テーブル ポインターとファイル テーブル記述子の対応関係を確立します (VS pid は増加し続け、いっぱいになると戻ります)
- ファイル記述子は、開いているファイルを表すために使用される int ですが、ファイルの管理情報をファイル記述子に格納することはできません。open() 関数を使用してファイルを開くと、OS はファイルの関連情報を読み込みます。 file into ただし、ファイルテーブルなどのデータ構造は、セキュリティや効率性などの理由から直接操作には適しておらず、構造に番号を割り当て、その番号を操作に使用することができます。この番号がファイル記述子です。
- OS はプロセスごとに内部的にファイル記述子マスター テーブルを維持します。新しいファイル記述子が必要になると、マスター テーブル内で未使用の最小の記述子を検索して返します。ファイル記述子の型は int ですが、しかし、これは実際には負ではない整数、つまり 0~OPEN_MAX (現在のシステムでは 1024) であり、そのうち 0、1、2 はシステムによって占有されており、それぞれ stdin、stdout、stderror を表します。 close() を使用して fd を閉じると、fd とファイル テーブル構造間の対応関係がテーブル全体から削除されますが、ファイル テーブル構造が必ずしも削除されるわけではありません。ファイル テーブルが他の fd に対応しない場合に限ります。 (つまり、ファイル テーブルは同時に複数の fd に対応できます) ファイル テーブルを削除する前に、close() はファイル記述子自体の整数値を変更しません。ファイル記述子がファイルを表現できなくなるだけです。 .
- 重複 fdVS コピー fd:dup は、int new_fd=old_fd
- ではなく、old_fd に対応するファイル テーブル ポインタを new_fd にコピーします。 UNIX は、開いているファイルを記述するために 3 つのデータ構造を使用します。
- ファイル記述子テーブル は、各プロセスの現在のプロセスによって開かれたファイルを記述するために使用されます。 ファイル状態識別テーブル (現在のファイルを表す)ステータス、ファイル i ノード (インデックス ノード) を見つけるために使用される V ノード テーブル。この Vnode 構造は Linux では使用されず、一般的な i ノード構造ですが、本質的な違いはありません。 i ノードは、ファイルの読み取り時にファイル システムを介してディスクからインポートされたファイルの場所にあります。
ファイル記述子フラグ

close-on-exec が 1 つだけあり、これは単なるフラグです。プロセスが子プロセスをフォークするとき、子プロセスで exec 関数が呼び出されるときに使用されます。プロセスのサイン。意味は、exec を実行する前にこのファイル記述子を閉じるかどうかです。
-
通常、別のプログラムを実行するには exec を呼び出しますが、このとき子プロセスのテキスト、データ、ヒープ、スタックは新しいプログラムに置き換えられます。このとき、当然のことながら、ファイル記述子を保持する変数は存在しなくなり、不要なファイル記述子を閉じることはできません。したがって、通常は子プロセスをフォークし、子プロセス内で直接 close を実行して不要なファイル記述子をオフにしてから、exec を実行します。ただし、複雑なシステムでは、子プロセスをフォークしたときにファイル記述子 (ソケット ハンドルなどを含む) がいくつ開かれているかが分からなくなる場合があり、このとき 1 つずつクリーンアップするのは非常に困難です。私たちが期待しているのは、子プロセスをフォークする前にファイル ハンドルを開くときにそれを指定することです: 子プロセスをフォークした後に exec を実行するときにこのハンドルを閉じます。
すべてのファイル記述子には、close-on-exec フラグがあります。システムのデフォルトでは、このフラグの最後のビットは 0 に設定されます。このフラグはオフになります。その後、子プロセスが exec 関数を呼び出しても、子プロセスはファイル記述子を閉じません。このとき、親プロセスと子プロセスはファイルを共有し、同じファイル テーブル エントリと同じファイル オフセットを持ちます。
-
fcntl()- の
FD_CLOEXEC
と open()
の O_CLOEXEC
は、ファイルの close-on- を設定するために使用されます。 exec
、close-on-exec フラグが 1 に設定されている場合、このフラグはオンになります。このとき、子プロセスが exec 関数を呼び出す前に、システムはすでに子プロセスにファイル記述子を閉じるように要求しています。 。
注: 新しいバージョンは開くときに CLOEXEC の設定をサポートしていますが、コンパイル中にエラーが表示されます – エラー: 'O_CLOEXEC' undeclared (この関数で最初に使用)。この機能はマクロ(_GNU_SOURCE)を設定して有効にする必要があります。
リーリー
ファイルステータスフラグFD_CLOEXEC
と open()
の O_CLOEXEC
は、ファイルの close-on- を設定するために使用されます。 exec
、close-on-exec フラグが 1 に設定されている場合、このフラグはオンになります。このとき、子プロセスが exec 関数を呼び出す前に、システムはすでに子プロセスにファイル記述子を閉じるように要求しています。 。
ファイル ステータス フラグは、開いているファイルの属性を表すために使用されます。ファイル ステータス フラグは、ファイル記述子を複製することで、同じ開いているファイルのステータスを共有できますが、ファイル記述子フラグは共有できません。
- Access Modes:
- ファイルのアクセス モードを指定します: 読み取り専用、書き込み専用、読み取り/書き込み。 open() によって設定され、fcntl() によって返されますが、変更できません Open-time Flags:
- open() 実行時の動作を示します。このフラグは、open() 実行後は保存されません。 動作モード:
- 読み取りおよび書き込み操作に影響し、open() を通じて設定されますが、fcntl() で読み取りまたは変更できます #########開ける()###### リーリー FQ
FA: 次のモデルを推測してください。1 つのビットが 1 で、残りがすべて 0 である文字列の文字列を使用して、オプションに対して「ビットごとの AND」を実行すると、フラグ全体のステータスを表す 0/1 の文字列を取得できます。 注: 下位 3 ビットはアクセス モードを表します
creat()
フラグ O_WRONLY |O_TRUNC|O_CREAT を指定して open() を呼び出すのと同等です
#include int creat(const char *pathname, mode_t mode);
dup()、dup2()、dup3()
//复制一个文件描述符的指向,新的文件描述符的flags和原来的一样,成功返回new_file_descriptor, 失败返 回-1并设errno #include int dup(int oldfd); //使用未被占用的最小的文件描述符编号作为新的文件描述符 int dup2(int oldfd, int newfd); #include #include int dup3(int oldfd, int newfd, int flags); #include #include int res=dup2(fd,fd2); if(-1==res){ perror("dup2"),exit(-1);

read()
//从fd对应的文件中读count个byte的数据到以buf开头的缓冲区中,成功返回成功读取到的byte的数目,失败返回-1设errno #include ssize_t read(int fd, void *buf, size_t count); #include #include int res=read(fd,buf,6); if(-1==fd) perror("read"),exit(-1);
write()
//从buf指向的缓冲区中读取count个byte的数据写入到fd对应的文件中,成功返回成功写入的byte数目,文件的位置指针会向前移动这个数目,失败返回-1设errno #include ssize_t write(int fd, const void *buf, size_t count);//不需要对buf操作, 所以有const, VS read()没有const #include #include int res=write(fd,"hello",sizeof("hello")); if(-1==res) perror("write"),exit(-1);
Note: 上例中即使只有一个字符’A’,也要写”A”,因为”A”才是地址,’A’只是个int
lseek()
l 表示long int, 历史原因
//根据移动基准whence和移动距离offset对文件的位置指针进行重新定位,返回移动后的位置指针与文件开头的距离,失败返回-1设errno #include #include off_t lseek(int fd, off_t offset, int whence); /*whence: SEEK_SET:以文件开头为基准进行偏移,0一般不能向前偏 SEEK_CUR:以当前位置指针的位置为基准进行偏移,1向前向后均可 SEEK_END:以文件的结尾为基准进行偏移,2向前向后均可向后形成”文件空洞” #include #include int len=lseek(fd,-3,SEEK_SET); if(-1==len){ perror("lseek"),exit(-1);
fcntl()
//对fd进行各种操作,成功返回0,失败返回-1设errno #include #include int fcntl(int fd, int cmd, ... ); //...表示可变长参数 /*cmd: Adversory record locking: F_SETLK(struct flock*) //设建议锁 F_SETLKW(struct flock*) //设建议锁,如果文件上有冲突的锁,且在等待的时候捕获了一个信号,则调用被打断并在信号捕获之后立即返回一个错误,如果等待期间没有信号,则一直等待 F_GETLK(struct flock*) //尝试放锁,如果能放锁,则不会放锁,而是返回一个含有F_UNLCK而其他不变的l_type类型,如果不能放锁,那么fcntl()会将新类型的锁加在文件上,并把当前PID留在锁上 Duplicating a file descriptor: F_DUPFD (int) //找到>=arg的最小的可以使用的文件描述符,并把这个文件描述符用作fd的一个副本 F_DUPFD_CLOEXEC(int)//和F_DUPFD一样,除了会在新的文件描述符上设置close-on-exec F_GETFD (void) //读取fd的flag,忽略arg的值 F_SETFD (int) //将fd的flags设置成arg的值. F_GETFL (void) //读取fd的Access Mode和其他的file status flags; 忽略arg F_SETFL (long) //设置file status flags为arg F_GETOWN(void) //返回fd上接受SIGIO和SIGURG的PID或进程组ID F_SETOWN(int) //设置fd上接受SIGIO和SIGURG的PID或进程组ID为arg F_GETOWN_EX(struct f_owner_ex*) //返回当前文件被之前的F_SETOWN_EX操作定义的文件描述符R F_SETOWN_EX(struct f_owner_ex*) //和F_SETOWN类似,允许调用程序将fd的I/O信号处理权限直接交给一个线程,进程或进程组 F_GETSIG(void) //当文件的输入输出可用时返回一个信号 F_SETSIG(int) //当文件的输入输出可用时发送arg指定的信号 */ /*…: 可选参素,是否需要得看cmd,如果是加锁,这里应是struct flock* struct flock { short l_type; //%d Type of lock: F_RDLCK(读锁), F_WRLCK(写锁), F_UNLCK(解锁) short l_whence; //%d How to interpret l_start, 加锁的位置参考标准:SEEK_SET, SEEK_CUR, SEEK_END off_t l_start; //%ld Starting offset for lock, 加锁的起始位置 off_t l_len; //%ld Number of bytes to lock , 锁定的字节数 pid_t l_pid; // PID of process blocking our lock, (F_GETLK only)加锁的进程号,,默认给-1 }; */
建议锁(Adversory Lock)
限制加锁,但不限制读写, 所以只对加锁成功才读写的程序有效,用来解决不同的进程 同时对同一个文件的同一个位置 “写”导致的冲突问题
读锁是一把共享锁(S锁):共享锁+共享锁+共享锁+共享锁+共享锁+共享锁
写锁是一把排他锁(X锁):永远孤苦伶仃
释放锁的方法(逐级提高):
- 将锁的类型改为:F_UNLCK, 再使用fcntl()函数重新设置
- close()关闭fd时, 调用进程在该fd上加的所有锁都会自动释放
- 进程结束时会自动释放所有该进程加过的文件锁
Q:为什么加了写锁还能gedit或vim写???
A:可以写, 锁只可以控制能否加锁成功, 不能控制对文件的读写, 所以叫”建议”锁, 我加了锁就是不想让你写, 你非要写我也没办法. vim/gedit不通过能否加锁成功来决定是否读写, 所以可以直接上
Q: So如何实现文件锁控制文件的读写操作????
A:可以在读操作前尝试加读锁, 写操作前尝试加写锁, 根据能否加锁成功决定能否进行读写操作
int fd=open("./a.txt",O_RDWR); //得到fd if(-1==fd) perror("open"),exit(-1); struct flock lock={F_RDLCK,SEEK_SET,2,5,-1}; //设置锁 //此处从第3个byte开始(包含第三)锁5byte int res=fcntl(fd,F_SETLK,&lock); //给fd加锁 if(-1==res) perror("fcntl"),exit(-1);
ioct1()
这个函数可以实现其他文件操作函数所没有的功能,大多数情况下都用在设备驱动程序里,每个设备驱动程序可以定义自己专用的一组ioctl命令,系统则为不同种类的设备提供通用的ioctl命令
//操作特殊文件的设备参数,成功返回0,失败返回-1设errno #include int ioctl(int d, int request, ...); //d:an open file descriptor. //request: a device-dependent request code
close()
//关闭fd,这样这个fd就可以重新用于连接其他文件,成功返回0,失败返回-1设errno #include int close(int fd); #include #include int res=close(fd); if(-1==res) perror("close"),exit(-1);
通过本文,我们了解了Linux文件I/O的基本原理和方法,它们可以满足我们对文件的各种操作需求。我们应该根据实际需求选择合适的方法,并遵循一些基本原则,如关闭不用的文件描述符,检查错误返回值,使用合适的缓冲区大小等。文件I/O是Linux程序设计中不可或缺的一部分,它可以实现数据的持久化和交换,也可以提升程序的功能和性能。希望本文能够对你有所帮助和启发。
以上がLinux ファイル I/O: 原則と方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











DeepSeekは、Webバージョンと公式Webサイトの2つのアクセス方法を提供する強力なインテリジェント検索および分析ツールです。 Webバージョンは便利で効率的であり、公式ウェブサイトは包括的な製品情報、ダウンロードリソース、サポートサービスを提供できます。個人であろうと企業ユーザーであろうと、DeepSeekを通じて大規模なデータを簡単に取得および分析して、仕事の効率を向上させ、意思決定を支援し、イノベーションを促進することができます。

DeepSeekをインストールするには、Dockerコンテナ(最も便利な場合は、互換性について心配する必要はありません)を使用して、事前コンパイルパッケージ(Windowsユーザー向け)を使用してソースからコンパイル(経験豊富な開発者向け)を含む多くの方法があります。公式文書は慎重に文書化され、不必要なトラブルを避けるために完全に準備します。

Bitgetは、スポット取引、契約取引、デリバティブなど、さまざまな取引サービスを提供する暗号通貨交換です。 2018年に設立されたこのExchangeは、シンガポールに本社を置き、安全で信頼性の高い取引プラットフォームをユーザーに提供することに取り組んでいます。 Bitgetは、BTC/USDT、ETH/USDT、XRP/USDTなど、さまざまな取引ペアを提供しています。さらに、この取引所はセキュリティと流動性について評判があり、プレミアム注文タイプ、レバレッジド取引、24時間年中無休のカスタマーサポートなど、さまざまな機能を提供します。

世界をリードするデジタル資産交換であるOuyi Okxは、安全で便利な取引体験を提供するために、公式のインストールパッケージを開始しました。 OUYIのOKXインストールパッケージは、ブラウザに直接インストールでき、ユーザー向けの安定した効率的な取引プラットフォームを作成できます。インストールプロセスは、簡単で理解しやすいです。

Gate.ioは、インストールパッケージをダウンロードしてデバイスにインストールすることで使用できる人気のある暗号通貨交換です。インストールパッケージを取得する手順は次のとおりです。Gate.ioの公式Webサイトにアクセスし、「ダウンロード」をクリックし、対応するオペレーティングシステム(Windows、Mac、またはLinux)を選択し、インストールパッケージをコンピューターにダウンロードします。スムーズなインストールを確保するために、インストール中に一時的にウイルス対策ソフトウェアまたはファイアウォールを一時的に無効にすることをお勧めします。完了後、ユーザーはGATE.IOアカウントを作成して使用を開始する必要があります。

OKXとしても知られるOUYIは、世界をリードする暗号通貨取引プラットフォームです。この記事では、OUYIの公式インストールパッケージのダウンロードポータルを提供します。これにより、ユーザーはさまざまなデバイスにOUYIクライアントをインストールすることが容易になります。このインストールパッケージは、Windows、Mac、Android、およびiOSシステムをサポートします。インストールが完了した後、ユーザーはOUYIアカウントに登録またはログインし、暗号通貨の取引を開始し、プラットフォームが提供するその他のサービスを楽しむことができます。

Gate.ioは、広範なトークン選択、低い取引手数料、ユーザーフレンドリーなインターフェイスで知られる高く評価されている暗号通貨取引プラットフォームです。高度なセキュリティ機能と優れたカスタマーサービスにより、Gate.ioは、信頼できる便利な暗号通貨取引環境をトレーダーに提供します。 gate.ioに参加する場合は、リンクをクリックして公式登録インストールパッケージをダウンロードして、暗号通貨取引の旅を開始してください。

このチュートリアルは、既存のApacheサーバーに沿って、潜在的にubuntuシステムにnginxとphpmyAdminをインストールおよび構成することをガイドします。 Nginxのセットアップをカバーし、Apacheとの潜在的なポートの競合を解決し、Mariadbをインストールします(
