コース内容#
両者の真の意味と違いを理解し、上記の質問に答えられるようにする
両者の文字通りの意味#
出勤
- ブロッキング:出勤時に渋滞が発生し、待つか他の方法を使う、いずれにせよ出勤しなければならない
- 非ブロッキング:出勤時に渋滞が発生し、直接行かない
お母さんが醤油を買ってくるように言った
- ブロッキング:醤油がなくなったので、お母さんに他のものに変えてもらうか、他のものを買う
- 非ブロッキング:醤油がなくなったので、買わない
小明にレポートを書かせる
- ブロッキング:小明が書いている間、彼が書き終わるのを待つ
- 非ブロッキング:小明が書くのを待つ必要はなく、後で結果を知らせる [自分で小明に聞く - 同期 / 小明が自発的に教えてくれる - 非同期]
お湯を沸かす
- ブロッキング:お湯を沸かしている間、他のことができず、ただ待っている
- 非ブロッキング:お湯を沸かしている間、他のことができる、例えばリビングでテレビを見る
- 参考同期と非同期、ブロッキングと非ブロッキングの簡単な説明——CSDN
導入#
- open 関数の O_NONBLOCK を振り返る
- すべてはファイルであるため、すべてがブロッキング / 非ブロッキングになることができる
- ファイル書き込みのプロセスを分析する
- 開く→書く→閉じる
- その中で、write はシステムコールに属し、具体的なプロセスは以下の通り
- データ→カーネル→ディスク:カーネルがデータをコピーし、バッファに置く [ブロックバッファ]、バッファをフラッシュする際に IO デバイスをスケジュールし、inode とブロックを見つけ、データをディスクに書き込む
- 最初のステップのデータ→カーネルはユーザーモード、2 番目のステップのカーネル→ディスクはカーネルモードであり、変換が存在する
- 参考Linux のユーザーモードとカーネルモードを理解するには?—— 知乎
- NON_BLOCK はユーザーモード [データ→カーネル または カーネル→データ] のプロセスを設定する
- 大部分のブロッキングはカーネルからデータを取り出す際に発生する
- カーネル→ディスクのプロセスは実際にはブロッキングである
- 上層アプリケーションが書き込みの状態を迅速に返すために、カーネルはデータをディスクに書き込むプロセスを普通のユーザーが認識する必要はない
- カーネルがデータをコピーし終わると、プログラムが戻り、普通のユーザーは書き込みが成功したと考えるが、データはバッファにまだ存在する可能性が高い [バッファ IO]
- データ→カーネル→ディスク:カーネルがデータをコピーし、バッファに置く [ブロックバッファ]、バッファをフラッシュする際に IO デバイスをスケジュールし、inode とブロックを見つけ、データをディスクに書き込む
fcntl#
ファイルディスクリプタを操作する [ファイルを非ブロッキングにすることができる]
- man fcntl
- プロトタイプ
- fd:ファイルディスクリプタ。最も一般的なファイルディスクリプタ:0、1、2
- cmd:操作方法
- ... /* arg */
- 可変引数
- arg は前の引数 [cmd] の引数を示し、その意味は cmd によって異なる
- 説明
- cmd は丸括弧内で可変引数があるかどうかを示し、最大 1 つ
- 可変引数の型は一般的に int で、マクロ定義を使用し、本質的にはビットマスクであり、ビット演算を通じて状態を変更する
- その中に cmd の一種があり:ファイル状態フラグに関連する
- すなわちファイル状態を取得または設定できる [例:O_NONBLOCK]
- 戻り値
- 戻り値を観察し、プログラム内の判断を考慮する
- エラーはすべて - 1 を返し、設定操作が成功すると 0 を返す
select#
同期 I/O の多重化 [インターフェース]
- man select
- プロトタイプ
- nfds:ファイルディスクリプタの数
- fd_set:ファイルディスクリプタの集合
- 読み取り可能、書き込み可能、例外
- 低レベルでは配列を使用して実装されている
- timeout:時間間隔
- 4 つのマクロを使用して集合を操作する、後で説明
- 説明
- プログラムが複数のファイルディスクリプタを監視し、1 つまたは複数のファイルディスクリプタの I/O 操作が「準備完了」になるのを待つことを許可する
- 準備完了:ファイルに対して適切な IO 操作を行うことができる、例えばブロッキングのない読み取りや十分に小さな書き込み
- 監視可能なファイルディスクリプタの数は FD_SETSIZE [通常は 1024] より小さい
- 終了時に、各ファイルディスクリプタの集合が変更され、状態が変化したファイルディスクリプタだけが残り、指示的な役割を果たす
- したがって、select をループで使用する場合、select を呼び出す前に各集合を再初期化する必要がある
- 集合は NULL であり、これはそのクラスのイベントで監視されているファイルがないことを示す
- マクロを使用して集合を操作する
- FD_ZERO:集合をクリアする
- FD_SET:集合にファイルディスクリプタを追加する
- FD_CLR:集合からファイルディスクリプタを削除する
- FD_ISSET:ファイルディスクリプタの所属する集合を判断する、select が戻った後に使用できる、集合が変更されているため
- nfds の値は 3 つの集合の中で最大の数字のファイルディスクリプタに 1 を加えたもの
- ファイルディスクリプタのインデックスは 0 から始まる
- struct timeval timeout
- select が各ファイルディスクリプタが準備完了になるのを待つ時間間隔を指定する
- ここでのブロッキングの意味は単純なブロッキングであり、ブロッキング I/O のブロッキングではない
- [個人的な理解] 同期 I/O の多重化の同期はここに表れている
- ブロッキングを停止する 3 つの状況
- 1 つのファイルディスクリプタが準備完了
- シグナルによる中断 [kill]
- タイムアウト
- 時間間隔は正確ではなく、真に正確にすることは難しい
- システムクロックの粒度、カーネルスケジューリングの遅延
- timeval 構造体には 2 つのメンバーがある:秒、マイクロ秒
- 両方が 0 の場合、即座に戻り、ポーリング [polling] に使用できる
- NULL の場合、無期限に待機する
- timeout の更新機能は Linux 上でのみ有効
- 互換性のため、できるだけ使用しないようにし、できるだけ一般的な機能を使用する
- select が各ファイルディスクリプタが準備完了になるのを待つ時間間隔を指定する
- 戻り値
- 現在のすべての集合の中のファイルディスクリプタ [準備完了] の数を返す
- 数量に基づいて、マクロ定義 FD_ISSET を通じてすべてのファイルディスクリプタの所属する集合を問い合わせ、状態の変化を感知する
- 0:時間が過ぎても、興味のあるイベントは発生しなかった
- -1:エラーが発生し、errno が設定される;この時、集合は変更されず、timeout は未定義になる
[PS]
- select → poll → epoll、どんどん高度になる
- man poll
- man epoll
- いずれも select が行うタスクと似ている
コードデモ#
ファイルを非ブロッキングとブロッキングにするインターフェースの実装#
- common.h
- 2 つのインターフェース
- head.h
- ヘッダーファイルの順序には意味があり、一般的に自プロジェクトのヘッダーファイルを後ろに置く
- 参考#include のパスと順序——Google C++ プログラミングスタイル
- common.c
- フラグを設定する際に元のフラグを変更してはいけない
- 👉まず get し、次にビット OR / ビット AND を通じてフラグを追加 / 削除する
非ブロッキングの 0 番ファイル#
- よく使うファイル 0、1、2 は手動で開く必要はない
- プロセスを作成する際に自動的にこの 3 つのファイルが開かれる
- それらは継承される
- コンパイルコマンド:
gcc 1.test.c ../common/common.c -I ../common/
- コンパイル時に common.c ファイルを追加することを忘れないでください
- 0 番ファイルを非ブロッキングに設定すると、重要な変化は 0 番ファイルの読み書きが非ブロッキングになること
- ① スリープなし
- scanf は 0 番ファイルを読み取る
- しかしデータがないため、直接進み、0 番ファイルにデータがあるのを待つことはない
- ② スリープあり
- スリープの 5 秒間に、ターミナルにデータを入力 [+ エンター、行バッファ]
- プロセスは一時停止し、CPU を占有しない
- しかし標準入力ストリームはまだ開いており、ユーザーがターミナルに入力したデータはカーネルによって 0 番ファイルに渡される
- ファイルはカーネルによって管理されており、スリープの間にカーネルはファイルにデータを書き込むことができる
- スリープが終了した後、scanf は 0 番ファイルのデータを読み取る
- 【明確に】非ブロッキングは特定のオブジェクト、例えば 0 番ファイルを指し、特定の関数や操作を指すわけではない
- ① スリープなし
- [PS]
- ブロッキング:時間を無駄にする可能性がある
- 非ブロッキング:リソースを無駄にする可能性がある
- 一般的に大規模なプログラムや高並列サーバーに使用される
0 番ファイルの select#
- man マニュアルからコピー ——man select の EXAMPLE
- select は I/O の到来を感知できる
- 実行結果
- ターミナルに ls を入力すると、プログラムが終了した後も ls が実行される
- バッファの内容が取り出されていないため [例えば scanf]、最終的に zsh がそれを受け取った
- select はこの監視の時間間隔内でブロッキングが発生した
- すべてのユーザーレベルのプログラムにおいて、いわゆるブロッキングは睡眠 [sleep ()] である
- ブロッキングの終了条件:準備完了、シグナルによる中断、タイムアウト
- [延伸アプリケーション]
- ブロッキングの場所にタイマーを設定し、タイムアウト時にはデフォルト値を使用する
- 異常な状況でのブロッキングを長引かせない [SSH ホストに到達できない...]
- より人間的
- ブロッキングの場所にタイマーを設定し、タイムアウト時にはデフォルト値を使用する
追加知識点#
- I/O の感知方法は多くあり、カーネルは I/O の到来を完全に知っている
考察点#
- scanf の際、ターミナルにエンターを入力すると、データはバッファに入れられるのか、それともバッファをクリアして scanf に渡されるのか?
ヒント#
- コース予告:マルチプロセス [fork...]
- cp の実装を考慮することができる
- ファイルの読み書き操作を考察する
- ブロッキングでなければならない