Linux の高性能ネットワーク IO と Reactor モデルの分析

Linux の高性能ネットワーク IO と Reactor モデルの分析

1. 基本概念の紹介

  • プロセス(スレッド)の切り替え:すべてのシステムにはプロセスをスケジュールする機能があり、現在実行中のプロセスを一時停止し、以前に一時停止したプロセスを再開することができます。
  • プロセス (スレッド) のブロッキング: 実行中のプロセスは、ロックの待機や I/O の読み取りおよび書き込みの要求など、他のイベントが完了するまで待機することがあります。待機中は、システムによってプロセスが自動的にブロックされ、プロセスは CPU を占有しません。
  • ファイル記述子: Linux では、ファイル記述子はファイルへの参照を記述するために使用される抽象概念であり、負でない整数です。プログラムが既存のファイルを開いたり、新しいファイルを作成したりすると、カーネルはプロセスにファイル記述子を返します。
  • Linux信号処理:Linuxプロセスは、動作中にシステムまたはプロセスから信号値を受信し、信号値に応じて対応するキャプチャ関数を実行します。信号は、ハードウェア割り込みのソフトウェアシミュレーションに相当します。

ゼロコピーメカニズムの章では、ユーザー空間、カーネル空間、バッファについて紹介したので、ここでは省略します。

2. ネットワークIOの読み取りと書き込みのプロセス

  • ソケットの読み取り操作がユーザー空間で開始されると、コンテキスト スイッチが発生します。ユーザー プロセスはブロック (R1) され、ネットワーク データ ストリームが到着するのを待機し、それをネットワーク カードからカーネルにコピーします。(R2) 次に、それをカーネル バッファーからユーザー プロセス バッファーにコピーします。このとき、プロセスは切り替わり、再開され、取得したデータを処理する。
  • ここでは、ソケット読み取り操作の最初のステージに別名R1を付け、2番目のステージをR2と呼びます。
  • ユーザー空間のソケットで送信操作が開始されると、コンテキストスイッチが発生し、ユーザープロセスはブロックされ、データがユーザープロセスバッファからカーネルバッファにコピーされるまで待機します(1)。データコピーが完了し、プロセススイッチが復元されました

3. 5つのLinuxネットワークIOモデル

3.1 ブロッキングI/O

ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flags, struct sockaddr *from,socket_t *fromlen); 

  • 最も基本的な I/O モデルはブロッキング I/O モデルであり、これは最も単純なモデルでもあります。すべての操作は順番に実行されます
  • ブロッキング IO モデルでは、ユーザー空間アプリケーションがシステム コール (recvform) を実行し、カーネル バッファー内のデータが準備され、データがカーネルからユーザー プロセスにコピーされるまで、アプリケーションがブロックされます。最後のプロセスはシステムによって起動され、データを処理します。
  • 連続する 2 つのステージ R1 と R2 では、プロセス全体がブロックされます。

3.2 ノンブロッキングIO

  • ノンブロッキング IO も同期 IO の一種です。これはポーリング メカニズムに基づいて実装されており、ソケットは非ブロッキング方式で開かれます。つまり、I/O 操作はすぐには完了しませんが、I/O 操作は操作が完了しなかったことを示すエラー コード (EWOULDBLOCK) を返します。
  • カーネル データをチェックするためにポーリングし、データの準備ができていない場合は EWOULDBLOCK を返します。プロセスはrecvfrom呼び出しを開始し続けますが、もちろん一時停止して他の作業を行うこともできます。
  • カーネル データが準備されるまで、データはユーザー スペースにコピーされ、その後、プロセスはエラー コード以外のデータを取得してデータの処理を続行します。データのコピープロセス全体を通じて、プロセスは依然としてブロックされた状態にあることに注意してください。
  • プロセスは R2 フェーズでブロックされます。R1 フェーズではブロックされませんが、継続的にポーリングする必要があります。

3.3. I/Oの多重化(IO多重化)

  • 通常、バックエンド サービスには多数のソケット接続があります。一度に複数のソケットの読み取りおよび書き込みステータスを照会し、いずれかのソケットが準備完了であればそれを処理できれば、効率が大幅に向上します。これは「I/O 多重化」です。多重化とは複数のソケットを指し、多重化は同じプロセスを多重化することを指します。
  • Linuxは、select、poll、epollなどの多重化されたI/O実装を提供します。
  • selectまたはpoll、epollは呼び出しをブロックしています
  • ブロッキング IO とは異なり、select はすべてのソケット データが到着するまで処理を待機せず、一部のソケット データが準備されるとユーザー プロセスを再開して処理を開始します。カーネル内にデータが準備できたことをどうやって知るのでしょうか?回答: システムに処理させましょう。
  • プロセスは R1 ステージと R2 ステージでもブロックされますが、R1 ステージにはトリックがあります。マルチプロセスおよびマルチスレッドのプログラミング環境では、select の呼び出しをブロックするプロセス (スレッド) を 1 つだけ割り当てることができるため、他のスレッドを解放できます。

3.4 シグナル駆動I/O (SIGIO)

  • シグナルキャプチャ関数を提供し、ソケットに関連付ける必要があります。シグナルアクション呼び出しを開始した後、プロセスは他の処理を処理できるように解放されます。
  • カーネル内でデータの準備ができると、プロセスはSIGIO信号を受信し、割り込みによって信号キャプチャ関数を実行し、recvfromを呼び出してカーネルからユーザー空間にデータを読み取り、データを処理します。
  • R1段階ではユーザープロセスはブロックされないが、R2は待機状態でブロックされることがわかります。

3.5. 非同期IO(POSIX aio_シリーズ関数)

  • 同期 IO と比較すると、非同期 IO は、カーネル バッファー データが準備されているかどうかに関係なく、ユーザー プロセスが非同期読み取り (aio_read) システム コールを開始した後、現在のプロセスをブロックしません。プロセスは、aio_read システム コールが返された後、他のロジックを処理できます。
  • カーネル内でソケットデータが準備されると、システムはカーネルからユーザー空間にデータを直接コピーし、シグナルを使用してユーザープロセスに通知します。
  • R1ステージとR2ステージの両方のプロセスは非ブロッキングである

4. 多重化IOの深い理解

4.1、選択

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

1) copy_from_user を使用して fd_set をユーザー空間からカーネル空間にコピーする

2) コールバック関数__pollwaitを登録する

3) すべての fd を走査し、対応するポーリング メソッドを呼び出します (ソケットの場合、このポーリング メソッドは sock_poll です。sock_poll は状況に応じて tcp_poll、udp_poll、または datagram_poll を呼び出します)

4) tcp_pollを例にとると、そのコア実装は__pollwaitであり、これは上記で登録されたコールバック関数である。

5) __pollwait の主なタスクは、デバイスの待機キューに現在のプロセス (現在のプロセス) をハングさせることです。デバイスによって待機キューは異なります。tcp_poll の場合、待機キューは sk->sk_sleep です (待機キューにプロセスがハングしても、プロセスがスリープ状態になったわけではないことに注意してください)。デバイスがメッセージを受信した後 (ネットワーク デバイス)、またはファイル データを入力した後 (ディスク デバイス)、デバイス待機キュー上のスリープ プロセスが起動され、現在のプロセスが起動されます。

6) ポーリング メソッドが返されると、読み取りおよび書き込み操作の準備ができているかどうかを示すマスクが返されます。このマスクに従って、fd_set に値が割り当てられます。

7) すべてのfdが走査され、読み取り可能および書き込み可能なマスクが返されなかった場合、schedule_timeoutが呼び出され、selectを呼び出したプロセス(つまり、現在のプロセス)がスリープ状態になります。

8) デバイス ドライバー自身のリソースが読み取りおよび書き込み可能になると、待機キュー内のスリープ状態のプロセスが起動されます。一定のタイムアウト(timeoutで指定)後に誰も起動しない場合は、selectを呼び出すプロセスが再度起動されてCPUを取得し、fdを再度走査して準備完了のfdがあるかどうかを確認します。

9) fd_setをカーネル空間からユーザー空間にコピーする

選択の欠点:

  • select が呼び出されるたびに、fd セットをユーザー状態からカーネル状態にコピーする必要があります。fd が多数ある場合、このオーバーヘッドは非常に大きくなります。
  • 同時に、select を呼び出すたびに、カーネルは渡されたすべての fd を走査する必要があり、fd が多数ある場合はコストが非常に高くなります。
  • select でサポートされるファイル記述子の数が少なすぎます。デフォルトは 1024 です。

4.2、エポール

int epoll_create(int サイズ);  
int epoll_ctl(int epfd、int op、int fd、struct epoll_event *event);  
int epoll_wait(int epfd、struct epoll_event *events、int maxevents、int timeout);
  • epoll_create が呼び出されると、将来 epoll_ctl によって送信されたソケットを保存するために、カーネル キャッシュに赤黒ツリーが構築されます。同時に、準備完了イベントを保存するために、rdllist 双方向リンク リストが作成されます。 epoll_waitが呼び出されると、rdllist双方向リンクリストのデータをチェックするだけです。
  • epoll_ctl が epoll オブジェクトにイベントを追加、変更、または削除する場合、非常に高速な rbr 赤黒ツリーで動作します。
  • epoll に追加されたイベントは、デバイス (ネットワーク カードなど) とのコールバック関係を確立します。デバイス上で対応するイベントが発生すると、コールバック メソッドが呼び出され、イベントが rdllist 双方向リンク リストに追加されます。このコールバック メソッドは、カーネルでは ep_poll_callback と呼ばれます。

epoll の 2 つのトリガー モード:

epoll には、EPOLLLT と EPOLLET の 2 つのトリガー モードがあります。LT はデフォルト モードで、ET は「高速」モードです (ブロックなしソケットのみをサポートします)。

  • LT(レベルトリガー)モードでは、このファイル記述子から読み取るデータがある限り、各epoll_waitは読み取りイベントをトリガーします。
  • ET (エッジ トリガー) モードでは、I/O イベントが検出されると、イベント通知を含むファイル記述子が epoll_wait 呼び出しを通じて取得されます。ファイル記述子が読み取り可能な場合は、ファイル記述子が空になるまで (または EWOULDBLOCK が返されるまで) 読み取る必要があります。そうでない場合、次の epoll_wait はイベントをトリガーしません。

4.3. epoll が select より優れている点

選択の 3 つの欠点を解決します。

  • 最初の欠点については、epoll の解決策は epoll_ctl 関数にあります。新しいイベントが epoll ハンドルに登録されるたびに (epoll_ctl で EPOLL_CTL_ADD を指定)、epoll_wait 中に繰り返しコピーされるのではなく、すべての fd がカーネルにコピーされます。 epoll は、プロセス全体を通じて各 fd が 1 回だけコピーされることを保証します (epoll_wait はコピーを必要としません)
  • 2 番目の欠点: epoll は各 fd に対してコールバック関数を指定します。デバイスの準備が整い、待機キューのウェイターを起動すると、コールバック関数が呼び出され、コールバック関数は準備完了の fd を準備完了リストに追加します。 epoll_wait の実際の仕事は、この準備完了リストに準備完了の fd があるかどうかを確認することです (トラバースする必要はありません)
  • 3 つ目の欠点については、epoll にはこの制限はありません。サポートされる FD の上限は、開いているファイルの最大数です。この数は、通常 2048 よりはるかに大きくなります。たとえば、1 GB のメモリを搭載したマシンでは、約 100,000 になります。一般的に、この数はシステム メモリと密接に関係しています。

epollの高性能:

  • epoll は、監視する必要があるファイル記述子イベントを保存するために赤黒木を使用し、epoll_ctl はファイルをすばやく追加、削除、および変更します。
  • epollは走査せずに準備完了のfdを取得し、準備完了リストに直接戻ることができます。
  • Linux 2.6以降では、mmap技術が使用され、カーネルからユーザー空間にデータをコピーする必要がなくなり、ゼロコピーが可能になった。

4.4. epoll IO モデルが同期か非同期かに関する質問

概念の定義:

  • 同期I/O操作: I/O操作が完了するまで要求プロセスをブロックします。
  • 非同期 I/O 操作: 要求プロセスがブロックされることはありません。非同期操作は、I/O 操作が完了した後の通知のみを処理し、データの読み取りや書き込みを積極的に行いません。システム カーネルがデータの読み取りと書き込みを完了します。
  • ブロッキング、非ブロッキング: プロセス/スレッドがアクセスするデータが準備されているかどうか、プロセス/スレッドが待機する必要があるかどうか

非同期 IO の概念では、非ブロッキング I/O 呼び出しが必要です。前述したように、I/O 操作は 2 つの段階に分かれています。R1 はデータの準備ができるまで待機します。 R2 はカーネルからプロセスにデータをコピーします。 epoll は 2.6 カーネル以降では mmap メカニズムを使用するため、R2 ステージでのレプリケーションは不要になりますが、R1 では依然としてブロックされます。したがって、同期IOに分類されます。

5. 原子炉モデル

Reactor の核となるアイデアは、処理するすべての I/O イベントを中央の I/O マルチプレクサに登録し、メイン スレッド/プロセスをマルチプレクサ上でブロックすることです。I/O イベントが到着するか準備ができると、マルチプレクサは、事前に登録された対応する I/O イベントを返して、対応するプロセッサに配布します。

5.1. 関連概念の紹介

  • イベント: 状態です。たとえば、読み取り準備完了イベントは、カーネルからデータを読み取ることができる状態を指します。
  • イベントセパレータ: 一般的に、イベントの待機は epoll と select に引き継がれます。イベントの到着はランダムかつ非同期なので、epoll は周期的に呼び出す必要があります。フレームワーク内の対応するカプセル化されたモジュールは、イベントセパレータです (単に epoll のカプセル化として理解されます)。
  • イベント ハンドラー: イベントが発生した後、それを処理するためのプロセスまたはスレッドが必要になります。このハンドラーはイベント ハンドラーであり、通常はイベント セパレーターとは異なるスレッドです。

5.2. 反応炉の一般的なプロセス

1) アプリケーションは、イベントセパレータに読み取り書き込み準備完了イベントと読み取り書き込み準備完了イベントハンドラを登録します。

2) イベントセパレータは、読み取り書き込み準備完了イベントが発生するのを待ちます。

3) 読み取り書き込み準備完了イベントが発生し、イベントセパレータがアクティブになり、読み取り書き込み準備完了イベントハンドラが呼び出されます。

4) イベントハンドラはまずカーネルからユーザー空間にデータを読み込み、次にそのデータを処理する。

5.3. シングルスレッド + リアクター

5.4 マルチスレッド + リアクター

5.5. マルチスレッド + 複数のリアクター

6. プロアクターモデルの一般的なプロセス

1) アプリケーションは、イベントセパレータに読み取り完了イベントと読み取り完了イベントハンドラを登録し、システムに非同期読み取り要求を送信します。

2) イベントセパレータは読み取りイベントの完了を待機します。

3) セパレータが待機している間、システムは並列カーネル スレッドを使用して実際の読み取り操作を実行し、データをプロセス バッファーにコピーし、最後に読み取りが完了したことをイベント セパレータに通知します。

4) イベントセパレータは読み取り完了イベントをリッスンし、読み取り完了イベントのハンドラをアクティブにします。

5) 読み取り完了イベントハンドラは、ユーザープロセスバッファ内のデータを直接処理します。

6.1. ProactorとReactorの違い

  • Proactor は非同期 I/O の概念に基づいていますが、Reactor は一般に多重化 I/O の概念に基づいています。
  • Proactorはカーネルからユーザー空間にデータをコピーする必要がなく、このステップはシステムによって実行されます。

上記は、Linux 高性能ネットワーク IO と Reactor モデルの分析の詳細な内容です。Linux 高性能ネットワーク IO と Reactor モデルの詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Linux IOの詳細な紹介
  • Linux で iostat コマンドを使用するチュートリアル
  • Linux IO 多重化 epoll ネットワーク プログラミング
  • Linux のソケット IO モデルの興味深い説明
  • Linux の 5 つの IO モデルの詳細な紹介

<<:  ウェブサイトアイコンを追加するにはどうすればいいですか?

>>:  Mysql ファジークエリが大文字と小文字を区別するかどうかの詳細な調査

推薦する

Linux (CentOS7) で RPM を使用して MySQL 8.0.11 をインストールするチュートリアル

目次1. インストールの準備1. Linux関連情報の表示(Linuxコマンドライン操作) 2. M...

vue3.2 で追加された defineCustomElement の基本原理の詳細な説明

目次Webコンポーネントカスタム要素概要HTMLTemplateElement コンテンツ テンプレ...

MySQL 8.0.19 のインストールと設定方法のグラフィックチュートリアル

この記事は、参考のためにMySQL 8.0.19のインストールと設定のグラフィックチュートリアルを記...

マージントップ崩壊現象とその具体的解決策

マージントップの崩壊とはmargin-top の崩壊は、CSS ボックス モデルで発生する現象です。...

MySQL Innodbの主な機能挿入バッファ

目次挿入バッファとは何ですか?挿入バッファのトリガー条件は何ですか?なぜ一意のインデックスにできない...

Vue はボタンをクリックしてファイルをダウンロードする操作コードを実装します (バックエンド Java)

前回の記事では、ボタンをクリックしてファイルをダウンロードするVueの機能を紹介しました。今日は、ボ...

DockerはRedisを起動し、パスワードを設定します

RedisはRedisバージョン5のapline(Alps)イメージを使用します。これは小さくて高速...

WeChatアプレットはシンプルなサイコロゲームを実装します

この記事では、サイコロゲームを実装するためのWeChatアプレットの具体的なコードを参考までに共有し...

フォーム送信の更新ページはソースコード設計にジャンプしません

1. ソースコードの設計コードをコピーコードは次のとおりです。 <!DOCTYPE html ...

MySQL マスタースレーブレプリケーションの役割と動作原理の詳細な説明

1. マスタースレーブレプリケーションとは何ですか?マスタースレーブレプリケーションは、スレーブデー...

コンピュータが予期せずシャットダウンした後、VMware で Linux がインターネットに接続できない問題の解決策

問題の説明: Linux システムのネットワーク カード ファイル /etc/sysconfig/n...

Linux システムで IPv6 をサポートするように Nginx を設定する方法

1. 既存のnginxがipv6をサポートしているかどうかを確認する既存の nginx が ipv6...

webpackを使用してTypeScriptコードをパッケージ化およびコンパイルする方法を教えます

TypeScript バンドルwebpack 統合通常、実際の開発では、ビルド ツールを使用してコー...

Vueはカルーセルのフレームレート再生を実装します

この記事の例では、カルーセルのフレームレート再生を実現するためのVueの具体的なコードを参考までに共...

DockerがElasticsearch7.xを起動してエラーを報告する問題を解決する

Docker実行コマンドの使用docker run -d -p 9200:9200 -p 9300:...