epollです。ここでは、epollイベントをカーネルスペースからユーザースペースに転送する方法と、エッジおよびレベルトリガーモードを実装する方法について説明します。
この記事は他の記事よりも遅れて書かれました。私が最初の資料に取り組み始めたとき、最新の安定したLinuxカーネルは3.16.1でした。そして、この記事の執筆時点では、これはすでにバージョン4.1です。この記事は、このカーネルバージョンのコードに基づいています。ただし、コードはあまり変更されていないため、以前の記事の読者は、実装の何かが大幅に変更されたことを心配する必要はありません。
epoll
ユーザースペースとの対話
以前の記事では、カーネルのイベント処理システムがどのように機能するかを説明するのにかなりの時間を費やしました。ただし、ご存知のように、プログラムがこの情報を使用するには、カーネルがイベントに関する情報をユーザースペースで実行されているプログラムに渡す必要があります。これは主にepoll_wait(2)システム呼び出しで行われます。
この関数のコードは、ファイルの1961行目にあります
fs/eventpoll.c。関数自体は非常に単純です。非常に通常のチェックの後eventpoll、ファイル記述子からへのポインタを取得し、次の関数を呼び出します。
error = ep_poll(ep, events, maxevents, timeout);
Ep_poll()関数
この関数は
ep_poll()、同じファイルの1585行で宣言されています。まず、ユーザーが値を設定したかどうかを確認しますtimeout。その場合、関数は待機キューを初期化し、タイムアウトをユーザーが指定した値に設定します。ユーザーが待ちたくない場合、つまり, timeout = 0、関数check_events:は、イベントのコピーを担当するラベルが付いたコードのブロックにすぐに移動します。
ユーザーが値を指定し、ユーザー
timeoutに報告できるイベントがない場合(それらの存在は呼び出しを使用して決定されますep_events_available(ep))、関数ep_poll()は待機キューに自分自身を追加しますep->wq(このシリーズの3番目の記事で説明したことを思い出してください)。そこでep_poll_callback()、プロセスで、キューで待機しているすべてのプロセスをアクティブ化することを説明しました。ep->wq..。
次に、関数はを呼び出すことによってスタンバイになり
schedule_hrtimeout_range()ます。「スリープ」プロセスが「ウェイクアップ」できる状況は次のとおりです。
- タイムアウトが切れました。
- プロセスは信号を受信しました。
- 新しいイベントが発生しました。
- 何も起こらず、スケジューラーはプロセスをアクティブ化することを決定しました。
シナリオ1、2、および3では、関数は適切なフラグを設定し、待機ループを終了します。後者の場合、関数は単にスタンバイモードに戻ります。
作業のこの部分が完了した後
ep_poll()、ブロックコードの実行を続行しますcheck_events:。
このブロックでは、最初にイベントの存在がチェックされ、次に最も興味深いことが発生する次の呼び出しが行われます。
ep_send_events(ep, events, maxevents)
ep_send_events()1546行目で宣言された
関数。呼び出し後、関数を呼び出し、ep_scan_ready_list()コールバックを渡しますep_send_events_proc()。この関数ep_scan_ready_list()は、準備完了ファイル記述子のリストをループし、ep_send_events_proc()検出した準備完了イベントごとに呼び出します。セキュリティとコードの再利用を確保するには、コールバックの使用を伴うメカニズムが必要であることが以下で明らかになります。
この関数は、
ep_send_events()最初に、構造の既製のファイル記述子のリストからのデータeventpoolをそのローカル変数に入れます。次に、ovflist構造フィールドeventpoolをに設定NULLします(デフォルトはEP_UNACTIVE_PTR)。
著者が
epoll使用する理由ovflist?これは高効率を確保するために行われますepoll!あなたは準備ができてファイルディスクリプタのリストが構造から取られた後のことに気づくことがありeventpool、それがされるep_scan_ready_list()設定ovflistにNULL。これにより、ep_poll_callback()ユーザースペースに渡されたイベントをにアタッチしようとしないことになり、ep->rdllist大きな問題が発生する可能性があります。このovflist機能を使用すると、イベントをユーザースペースにコピーep_scan_ready_list()するときにロックを保持する必要がありませんep->lock。その結果、ソリューションの全体的なパフォーマンスが向上します。
その後
ep_send_events_proc()、準備ができているファイル記述子のリストをバイパスし、それらのメソッドを再度呼び出します。poll()イベントが実際に起こったことを確認するために。なぜepollここでイベントをもう一度チェックするのですか?これは、ユーザーが登録した1つまたは複数のイベントが引き続き使用可能であることを確認するために行われます。EPOLLOUTユーザープログラムがその記述子に書き込んでいるときに、イベントごとにファイル記述子が準備完了ファイル記述子のリストに追加された状況を考えてみます。プログラムが書き込みを終了すると、ファイル記述子は書き込みできなくなる可能性があります。Epollこのような状況を正しく処理する必要があります。それ以外の場合、ユーザーはEPOLLOUT書き込み操作がブロックされた瞬間に受信します。
ただし、ここで1つの詳細に言及する価値があります。関数
ep_send_events_proc()ユーザースペースプログラムが正確なイベント通知を確実に受信できるようにあらゆる努力をします。可能性は低いですが、一連のイベントの可用性がep_send_events_proc()トリガー後に変更される可能性がありますpoll()。この場合、ユーザースペースプログラムは、存在しなくなったイベントの通知を受け取る可能性があります。これが、適用時に常に非ブロッキングソケットを使用することが正しいと見なされる理由epollです。これにより、アプリケーションが予期せずブロックされるのを防ぎます。
イベントマスクをチェックした後
ep_send_events_proc()、ユーザースペースプログラムによって提供されるバッファにイベント構造をコピーするだけです。
エッジトリガーおよびレベルトリガー
これで、最終的に、エッジトリガー(ET)とレベルトリガー(LT)の実装の違いについて説明できます。
else if (!(epi->event.events & EPOLLET)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
}
それは非常に簡単です!この関数
ep_send_events_proc()は、イベントを準備完了ファイル記述子のリストに追加し直します。その結果、次の呼び出しでep_poll()、同じファイル記述子が再度チェックされます。ユーザースペースに返す前にep_send_events_proc()常にファイルを呼び出すため、ファイル記述子が使用できなくなった場合、poll()システムの負荷がわずかに増加します(と比較してET)。ただし、このすべてのポイントは、前述のように、使用できなくなったイベントを報告しないことです。
それが後
ep_send_events_proc()のイベントのコピーを終了し、機能は最新のユーザ空間のアプリケーションを保ち、それにコピーされたイベントの数を返します。
機能が
ep_send_events_proc()終了すると、機能ep_scan_ready_list()少しクリーンアップする必要があります。まず、関数によって未処理のままにされたイベントを準備完了ファイル記述子のリストに戻しますep_send_events_proc()。これは、使用可能なイベントの数がユーザープログラムによって提供されるバッファーのサイズを超える場合に発生する可能性があります。また、存在する場合は、準備ができているファイル記述子のリストにep_send_events_proc()すべてのイベントをすばやく添付しますovflist。さらに、でovflist再び記録されEP_UNACTIVE_PTRます。その結果、新しいイベントがメインの順番待ちリストに添付されます(rdllist)。この関数は、他に利用可能なイベントがある場合に、他の「スリープ」プロセスをアクティブ化することによって終了します。
結果
これで、実装シリーズの4番目で最後の記事は終わり
epollです。これらの記事を書いているとき、Linuxカーネルコードの作成者が最大の効率とスケーラビリティを実現するために行った途方もない精神的な作業に感銘を受けました。そして、Linuxコードのすべての作成者が、作業の結果を共有することによって、知識を必要とするすべての人と知識を共有してくれたことに感謝します。
オープンソースソフトウェアについてどう思いますか?
