コース内容#
ファイル操作#
- 【導入】以前学んだ 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
- 列幅の決定方法は、暴力的な方法、二分探索、徐々に近づく方法を使用できます
コードデモ#
低レベルファイル操作#
-
- ⭐ 注釈を詳しく見て、使用に注目してください
- ❗ 文字化けを避けるために
- 文字列 buff の末尾に '\0' を置く
- sizeof(buff) - 1
- 最後の 512 バイト未満の読み取りでは、余分なバイトの干渉を排除する必要があります
- 方法①:手動で menset (buff, 0, sizeof (buff))
- 方法②:常にデータの末尾を '\0' に保つ、buff [nread] = '\0'
- [PS] システムの上位コマンドを学ぶ際には、これらにあまり注意を払う必要はありません
- 文字列 buff の末尾に '\0' を置く
- 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