コース内容#
ファイル操作#
- 【導入】以前学んだ cp、mv、cat コマンドは、すべてファイルの読み書きに関係しています
- cp:読→書
- mv:読→書→削除
- cat:読→書
- これらのステップはどのように実現されているのでしょうか?
【低レベル操作、ファイルディスクリプタに基づく】
open#
ファイルを開くまたは作成する [別名:openat、create]
- man 2 open【関数のプロトタイプと説明に注意】
- プロトタイプ
- 戻り値 int:ファイルディスクリプタ または -1
- 一般的なファイルディスクリプタ:0-stdin、1-stdout、2-stderr
- -1:エラーが発生し、errno が設定されます [perror で使用可能、コードデモを参照]
- flags:ファイルのオープン方法
- [PS] ヘッダーファイルを特に記憶する必要はありません
- 説明
- システムコール [system call]:あなたが権限を持たないことを手伝います
- open のファイルが存在しない場合、ファイルを新規作成する可能性があります [flags に O_CREAT が定義されている場合]
- O_CREAT
- open 関数のフラグ
- C 言語体系では、全て大文字はマクロ定義を表します
- 低レベルでは int 型データで、ビットマスクと呼ばれます
- 32 ビットで、32 種類の状態を表現可能で、各ビットが 1 つの状態を表します
- 状態間は AND、OR、XOR の方法で変換可能です
- O_CREAT
- ファイルディスクリプタ [file descriptor]
- 小さく、非負で、後続のシステムで呼び出し可能 [read、write...]
- 戻り値は常に現在のプロセスで取得可能な最小の数字です
- ファイル数を判断するために使用できます [もし 1000 を返す場合、現在のファイル数は必ず 1000 を超えています]
- ファイルを開いた後、ファイルポインタはデフォルトでファイルの先頭にあります
- ファイルディスクリプション [file description]
- open を呼び出すたびに新しい open ファイルディスクリプションが作成され、これはシステムのグローバルファイルテーブルの 1 つの情報です
- ファイルオフセットとファイルの状態を記録します
- [PS] ファイルディスクリプタは open ファイルディスクリプションの参照であり、pathname の変更によって影響を受けません
- open を呼び出すたびに新しい open ファイルディスクリプションが作成され、これはシステムのグローバルファイルテーブルの 1 つの情報です
- ⭐flags
- 必ず O_RDONLY、O_WRONLY、O_RDWR のいずれかを含む必要があります
- フラグ間はビット OR で組み合わせます
- O_CREAT 作成
- O_TRUNC 切り捨て
- O_DIRECT 直接 IO
- 直接 IO—— 同期書き込み、ファイルは直接書き込まれ、バッファを経由しません
- バッファ IO
- バッファの終了条件:① 一定量のデータが集まる;② 固定時間待つ
- ディスクに文字 a を書き込むと、すぐにはディスクに書き込まれず、コストを削減できます
- しかし、停電時にデータが失われやすいです
- [PS]
- ディスクの最小単位はブロックで、各ブロックは 4K です
- したがって、ディスクはブロックデバイスとも呼ばれます
- printf が stdout に出力される条件に似ています [行バッファ]
- 改行 / プログラム終了時に、システムは自動的にバッファをフラッシュします
- バッファが満杯になると、自動的にフラッシュします
- fflush 関数で手動でフラッシュします
- ディスクの最小単位はブロックで、各ブロックは 4K です
- バッファの終了条件:① 一定量のデータが集まる;② 固定時間待つ
- O_NONBLOCK 非ブロッキング IO
- ブロッキング
- 例:scanf のとき、標準入力ストリームに入力があるまで次の操作を待つ必要があります
- 欠点:リソースを浪費します
- 非ブロッキング
- 待たない
- 欠点
- 頻繁に戻って確認する必要があり、リソースを浪費します
- 何らかのメカニズムで監視する必要があり、技術的コストがかかります
- ブロッキング
- O_TMPFILE 一時ファイルを作成
- プロセスが終了するとファイルは削除され、トランザクションが終了するときも同様です
- システムの一時フォルダ /tmp に似ています
- ❗ 低レベルでファイルを読み書きする前に、open 関数を呼び出してファイルディスクリプタを取得する必要があります
read#
ファイルディスクリプタを通じてデータを読み取ります
- man read
- プロトタイプ
- 戻り値 ssize_t:読み取ったバイト数 または -1
- _t で終わる場合、一般的にはユーザー定義型です
- 推測:基本型の 1 つで、long long である可能性があり、int である可能性もあります
- ctags を使って具体的な型を段階的に見つける:ctrl + ] 、ctrl + o
- 答え:int [32 ビットシステムの場合];long int [64 ビットシステムの場合]
- [PS] 原則として、32 ビットシステムでは long int のサイズは int と同じです
- buf、count:毎回最大で count バイトのデータを buf に読み取ります
- 説明 + 戻り値
- 最大 count バイトをバッファに読み取ろうとします
- 読み取ったバイト数が count に達しない場合:中断された [signal];データ自体が count サイズに不足している
- 成功裏に num [≤ count] バイトのデータを読み取ると、ファイルオフセット [ポインタのように] は自動的に num のサイズだけ進みます
- もしファイルオフセットが EOF [読み取るデータがない] に達すると、関数は 0 を返します
- count
- もし 0 に設定すると、エラーが検出される可能性があります。エラーが検出されなければ、0 を返します
- SSIZE_MAX [int /long int の最大値] より大きい場合、返される結果は定義されたものになります [POSIX.1 標準]
- 戻り値
- ≤ count
- エラー時には - 1 を返し、errno が設定されます
- [PS] エラー
- EAGAIN
- ファイルを読み取る [ソケットを含む] とき、ファイルが O_NONBLOCK に設定されていても、read はブロックされます
- EAGAIN
write#
ファイルディスクリプタを通じてデータを書き込みます
- man 2 write
- プロトタイプ
- read と非常に似ています
- 説明 + 戻り値
- read と非常に似ています
- 書き込んだバイト数が count に達しない場合:物理的なスペースが不足;システムリソースの制限;signal によって中断された
- open ファイル時に O_APPEND [追加] が設定されている場合
- ファイルオフセット [offset] はファイルの末尾にあり、書き込み操作は追加されます
- そうでなければ、先頭に置かれ、書き込み操作は上書きされます
close#
ファイルディスクリプタを閉じます
- man close
- 主にファイルディスクリプタを閉じることです
- [PS]
- ロックは解除されます
- 特殊な場合
- close されるのがファイルディスクリプタの最後のものであれば、ファイルディスクリプタに対応するリソースが解放されます
- close されるのがファイルの最後の参照のファイルディスクリプタであれば、ファイルは削除されます
- カーネルが具体的に何をしたかは一時的に気にしないでください
【標準ファイル操作、ファイルポインタに基づく】
<stdio.h>
fopen#
ストリームを通じてファイルを開きます
- man fopen
- プロトタイプ
- 戻り値 FILE *:ファイルポインタ
- 以前はマクロ定義でしたが、ここでは互換性のために大文字です
- mode
- 型は char * で、int ではありません
- 説明
- ストリーム [stream] を関連付けます
- [PS] ネットワーク上でデータを公開する場合、バイトストリーム;ファイルストリーム < 型:FILE *>
- mode
- r /r+:読み取り / 読み書き
- ストリームはファイルの開始にあります
- w /w+:読み取り / 読み書き
- ストリームはファイルの開始にあります
- ファイルが存在する場合、ファイルを切り捨てます [開くと元のデータが消去されます]
- ファイルが存在しない場合、ファイルを作成します
- a /a+:追加 / 読み取りと追加
- 追加時、ストリームは EOF にあります;読み取り時、ストリームはファイルの開始にあります
- ファイルが存在しない場合は作成されます
- +:読み書き両方可能
- [PS]
- b:mode 文字列の最後または 2 つの文字の間に置くことができ、バイナリファイルを処理するために使用されますが、Linux では一般的に効果がありません
- ❓ 作成されたファイルはプロセスの umask 値によって修正されます
- r /r+:読み取り / 読み書き
- 戻り値
- 成功時、ファイルポインタを返します
- エラー時、NULL を返し、errno を設定します
fread、fwrite#
バイナリストリームの IO
- man fread / fwrite
- fread:stream から nmeb 回データを読み取ります [size バイト / 回] ptr に
- fwrite:ptr のデータを nmeb 回データを書き込みます [size バイト / 回] stream に
- 戻り値 size_t:読み取り / 書き込みのアイテム数 [成功]
- [符号なしの ssize_t]
- エラーが発生したり EOF に早く達した場合 👉 0 ≤ 戻り値 < nmeb
- ❗ したがって、戻り値で EOF とエラーを区別することはできず、feof、ferror で確認する必要があります
- [PS] size が 1 の場合、戻り値は転送されたバイト数に等しくなります
fclose#
ストリームをフラッシュしてファイルディスクリプタを閉じます
- man fclose
- ストリームをフラッシュする実際の呼び出しは fflush です
- 戻り値
- 0 [成功]
- -1 (EOF)、errno を設定し [失敗]
- 未定義の動作 [不正なポインタを渡すか、すでに fclose された場合]
- ⭐標準 IO のすべての操作はバッファ IO です
- 本来は書き込み権限がなく、カーネルの制御を待つ必要があります
- ❓ 標準 IO はテキスト [ユーザー] により適しており、低レベルの IO はバイナリファイルにより適しています
ディレクトリ操作#
本質的にはファイルでもあります [初期には直接 open 可能]
opendir#
- man opendir
- 戻り値 DIR *:ディレクトリストリームポインタ または NULL
- ディレクトリストリームはデフォルトでディレクトリの最初のエントリに置かれます
- エラーが発生した場合、NULL を返し、errno を設定します
readdir#
- man readdir
- 戻り値 struct dirent *:ディレクトリエントリ または NULL
- ディレクトリストリーム内の次のディレクトリエントリ [構造体] のポインタ
- 構造体の主要なフィールド:d_ino、d_name
- [PS]
- 一度に次のファイルを返します
- d_off:telldir が返す値と同じで、ftell () に似ています
- このオフセット [各ファイルのサイズが異なる] は一般的な意味 [バイト単位] とは異なります
- ftell () は現在のファイルの位置指示器の値を取得します
- NULL [ディレクトリストリームの末尾に達するか、エラーが発生した場合]
- ディレクトリストリーム内の次のディレクトリエントリ [構造体] のポインタ
closedir#
- ディレクトリを閉じます
ls -al の基本的な考え方の実装#
- ls -al の効果
- 必要な情報:ファイル権限、リンク数、ユーザー名、グループ名、ファイルサイズ、変更時間、ファイル名
- 考え方
- readdir()
- man readdir
- ディレクトリ内の各ファイルを読み取ります
- ファイル名を取得できます
- stat()、lstat()
- man 2 stat
- ファイルパスに基づいてファイル情報を取得します:stat 構造体
- ファイル権限、ハードリンク数、uid、gid、ファイルサイズ、変更時間を取得できます
- その中の EXAMPLE:lstat を参考にできます
- lstat () と stat () の違い
- lstat () はシンボリックリンクの情報を確認できますが、シンボリックリンクが指すファイルにはジャンプしません
- getpwuid()
- man getpwuid
- uid に基づいて passwd 構造体を取得します
- 対応するユーザー名を取得できます
- getgrgid
- man getgrgid
- gid に基づいて group 構造体を取得します
- 対応するグループ名を取得できます
- 自分で実装する場合 → ファイルを読み取り、分割します
- ユーザー情報:/etc/passwd
- グループ情報:/etc/group
- readdir()
- その他の詳細
- 色
- ソート
- 純粋な ls コマンドの出力の表示列数は幅によって変化します
- ターミナルサイズを取得します
- ioctl を参考にし、man ioctl を参照します
- 列幅の決定方法は、暴力的な方法、二分探索、徐々に近づく方法を使用できます
コードデモ#
低レベルファイル操作#
- ⭐ コメントを詳しく見て、使用に注意してください
- ❗ 文字化けを避けるために
- 文字列バッファの末尾に '\0' を置くために 1 つの位置を残します
- sizeof(buff) - 1
- 最後の読み取りが 512 バイト未満の場合、余分なバイトの干渉を排除する必要があります
- 方法①:手動で menset (buff, 0, sizeof (buff))
- 方法②:常にデータの末尾が '\0' であることを保持し、buff [nread] = '\0' とします
- [PS] システムの上位コマンドを学ぶ際には、これらにあまり注意を払う必要はありません
- 文字列バッファの末尾に '\0' を置くために 1 つの位置を残します
- perror はシステムエラー情報を出力します
- man 3 perror
- プロトタイプ
- fopen などでエラーが発生すると errno が設定されます
- 説明
- stderr に前回の呼び出しのエラー情報を出力します
- s は通常関数の名前を含みます
- よく使う common ヘッダーファイルを作成し、よく使うヘッダーファイルを置きます
- head.h
標準ファイル操作#
- バッファはループ内にあり、毎回初期化されます
- nread は非負数であり、EOF とエラーを区別できません
標準 IO はバッファ IO です#
- 最初の "Hello world" は直接出力され、stderr はバッファリングされません
- 2 番目の "Hello world" は本来 sleep が終了するのを待つはずですが、stdout に出力できず、すぐに出力できます。👇
- バッファを手動でフラッシュ:fflush
- 出力に改行
- sleep 関数は unistd.h にあります
追加の知識点#
- ulimit -a で開けるファイルの数の上限を確認できます
- 各プロセスのファイルオープン数の上限は 1024 です
- 超えるとシステムがクラッシュします
- [PS]
- システムのクラッシュはメモリも考慮する必要があります
- 責任あるプログラムを作成する:手動で close /free、エラーログを出力
- 標準出力のみが行バッファリングされています
考察点#
- ❓ ファイルの保存はすぐにディスクに書き込まれますか?
- 参考 Are file edits in Linux directly saved into disk?——StackExchange
ヒント#
- vim で Shift + K を押すと man マニュアルにジャンプできます
- おすすめのコピー即翻訳ソフト:CopyTranslator
- man マニュアルのオンラインドキュメント:man page——die.net