1. はじめにLinux での高性能ネットワークプログラミングでは、epoll が不可欠です。 select、poll などのシステム コールと比較すると、多数のファイル記述子を監視する必要があり、そのうちの少数だけがアクティブである場合に、epoll は比類のない利点を示します。 Epoll を使用すると、カーネルは対象の記述子を記憶し、対応する記述子イベントの準備ができたら、これらの準備完了要素を epoll 準備完了リストに追加し、対応する epoll 待機プロセスを起動できます。 2. シンプルなepollの例次の例は、著者が C 言語で記述した dbproxy のコードです。詳細が多数あるため、一部省略されています。 int init_reactor(int listen_fd,int ワーカーカウント){ ...... // マルチコアを最大限に活用するために複数のepoll fdを作成します for(i=0;i<worker_count;i++){ リアクター->worker_fd = epoll_create(EPOLL_MAX_EVENTS); } /* epoll は listen_fd を追加し、受け入れます */ // accept 後のイベントを対応する epoll fd に追加します int client_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&client_len))); // 対応するワーカーに接続記述子を登録します epoll_ctl(reactor->client_fd,EPOLL_CTL_ADD,epifd,&event); } //リアクターのワーカースレッド static void* rw_thread_func(void* arg){ ...... のために(;;){ // epoll_wait はイベントトリガーを待機します int retval = epoll_wait(epfd,events,EPOLL_MAX_EVENTS,500); if(戻り値 > 0){ for(j=0; j < retval; j++){ // 読み取りイベントを処理する if(event & EPOLLIN){ handle_ready_read_connection(接続); 続く; } /* その他のイベントを処理する */ } } } ...... } 上記のコードは、次の図に示すように、実際に、受け入れおよび読み取り/書き込み処理スレッドをリアクター モードで実装します。 2.1、epoll_createすべてがファイルであるという Unix の考え方は、epoll にも反映されています。epoll_create 呼び出しは、anon_inode_fs (匿名 inode ファイル システム) のルート ディレクトリの下にマウントされるファイル記述子を返します。具体的な epoll_create システム コールのソース コードを見てみましょう。 SYSCALL_DEFINE1(epoll_create、int、サイズ) { (サイズ <= 0)の場合 -EINVAL を返します。 sys_epoll_create1(0) を返します。 } 上記のソースコードからわかるように、epoll_create のパラメータは基本的に意味がありません。カーネルは単に 0 かどうかを判定し、sys_epoll_create1 を直接呼び出します。 Linux システムコールは (SYSCALL_DEFINE1、SYSCALL_DEFINE2...SYSCALL_DEFINE6) で定義されているため、sys_epoll_create1 に対応するソースコードは SYSCALL_DEFINE(epoll_create1) になります。 (注: レジスタの数が限られているため、カーネル (80x86 未満) はシステム コールを最大 6 つのパラメータに制限します。ulk3 によると、これは 32 ビット 80x86 レジスタの制限によるものです) 次に、epoll_create1 のソースコードを見てみましょう。 SYSCALL_DEFINE1(epoll_create1、int、フラグ) { // kzalloc(sizeof(*ep), GFP_KERNEL)、カーネル空間を使用します error = ep_alloc(&ep); // 使用されていないファイル記述子、つまり記述子配列内のスロットを取得します。 fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC)); // 匿名 inode ファイルシステムに inode を割り当て、そのファイル構造を取得します // そして file->f_op = &eventpoll_fops // そして file->private_data = ep; ファイル = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep, O_RDWR | (フラグ & O_CLOEXEC)); // ファイル記述子配列の対応するスロットにファイルを埋め込む fd_install(fd,file); ep->file = ファイル; fd を返します。 } 最後に、epoll_create によって生成されたファイル記述子を次の図に示します。 2.2、構造体イベントポーリングすべての epoll システム コールは、eventpoll 構造体を中心に動作します。次に、そのメンバーの簡単な説明を示します。 /* * この構造はfile->private_data*/に保存されます 構造体イベントポーリング{ // スピンロック、カーネル内のスピンロックを使用してロックし、複数のスレッド(プロセス)がこの構造体を同時に操作できるようにします // 主に ready_list を保護します spinlock_t ロック; // このミューテックスは、イベントループが対応するファイル記述子を使用するときに、ファイル記述子が削除されないようにするためのものです。struct mutex mtx; // epoll_wait によって使用される待機キューは、プロセスのウェイクアップに関連しています wait_queue_head_t wq; // file->poll によって使用される待機キューは、プロセスのウェイクアップに関連しています wait_queue_head_t poll_wait; // 準備完了記述子キュー struct list_head rdllist; // epoll が現在追跡しているファイル記述子を赤黒ツリー struct rb_root rbr で整理します。 // 準備完了イベントをユーザー空間に送信するときに、同時に発生するイベントのファイル記述子をこのリンクリストにリンクします struct epitem *ovflist; // 対応するユーザー 構造体 user_struct *ユーザー; //対応するファイル記述子 struct file *file; // 次の 2 つはループ検出の最適化です int visits; 構造体 list_head 訪問リストリンク; }; この記事では、カーネルが準備完了イベントを epoll に渡して対応するプロセスを起動する方法について説明します。ここでは、主に (wait_queue_head_t wq) などのメンバーに焦点を当てます。 2.3、epoll_ctl(追加) epoll_ctl (EPOLL_CTL_ADD) が対応するファイル記述子を eventpoll に挿入する方法を見てみましょう。 SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd, 構造体 epoll_event __user *、イベント) { /* epfd が epoll 記述子であるかどうかを確認します */ // ここでのミューテックスは、epoll_ctl への同時呼び出しを防ぐためのもので、つまり、内部データ構造を保護するためのものです。// 同時追加、変更、削除によって破壊されることはありません。mutex_lock_nested(&ep->mtx, 0); スイッチ(op){ EPOLL_CTL_ADDの場合: ... // 赤黒木に挿入 error = ep_insert(ep, &epds, tfile, fd); ... 壊す; ...... } mutex_unlock(&ep->mtx); } 上記のプロセスを次の図に示します。 2.4、ep_挿入Epitem は ep_insert で初期化され、その後、この記事の焦点であるイベントの準備ができたときのコールバック関数が初期化されます。コードは次のとおりです。 静的 int ep_insert(構造体eventpoll *ep, 構造体epoll_event *event, 構造体ファイル *tfile、int fd) { /* エピテムを初期化する */ // &epq.pt->qproc = ep_ptable_queue_proc init_poll_funcptr(&epq.pt、ep_ptable_queue_proc); // ここでコールバック関数を挿入します revents = tfile->f_op->poll(tfile, &epq.pt); // 準備が整ったイベントがある場合は、最初に準備完了リストに追加されます // たとえば、書き込み可能なイベント // さらに、TCP 内部 ack の後に tcp_check_space を呼び出し、最後に sock_def_write_space を呼び出して、epoll_wait の下で対応するプロセスを起動します if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) { リストの末尾に&epi->rdllink、&ep->rdllistを追加します。 // wake_up epはepoll_wait下のプロセスに対応するif (waitqueue_active(&ep->wq)){ ウェイクアップロックされました(&ep->wq); } ...... } //epitem を赤黒木に挿入します ep_rbtree_insert(ep, epi); ...... } 2.5. tfile->f_op->pollの実装カーネルの下位レベルに登録されているコールバック関数は、tfile->f_op->poll(tfile, &epq.pt) です。対応するソケット ファイル記述子の fd=>file->f_op->poll の初期化プロセスを見てみましょう。 // accept 後のイベントを対応する epoll fd に追加します int client_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&client_len))); // 対応するワーカーに接続記述子を登録します epoll_ctl(reactor->client_fd,EPOLL_CTL_ADD,epifd,&event); 上記のユーザー空間コードを振り返ると、fd、つまり client_fd は、accept を介して tcp の listen_fd によって呼び出されるので、accept 呼び出しチェーンのキー パスを見てみましょう。
そして、accept によって取得される client_fd の構造は以下のようになります。 (注: これは TCP ソケットなので、ここでは sock->ops=inet_stream_ops です。tfile->f_op->poll の実装がわかったので、このポーリングがコールバック関数をインストールする方法がわかります。 2.6. コールバック関数のインストールカーネル呼び出しパスは次のとおりです。
長い回り道の後、コールバック関数のインストールは、実際には、eventpoll.c の ep_ptable_queue_proc を呼び出し、その待機キューの先頭として sk->sk_sleep を渡すことです。ソース コードは次のとおりです。 静的 void ep_ptable_queue_proc(構造体ファイル *file、wait_queue_head_t *whead、 ポーリングテーブル *pt) { // 現在の client_fd に対応する epitem を取得します 構造体epitem *epi = ep_item_from_epqueue(pt); // &pwq->wait->func=ep_poll_callback、コールバックウェイクアップに使用 // これは init_waitqueue_entry ではないことに注意してください。つまり、現在の KSE (現在のプロセス/スレッド) は wait_queue に書き込まれません。これは、現在インストールされている KSE からウェイクアップされる必要はなく、epoll_wait をウェイクアップする KSE である必要があるためです。 init_waitqueue_func_entry(&pwq->wait, ep_poll_callback); // ここでの whead は sk->sk_sleep であり、現在のウェイトキューをソケットに対応するスリープ リストにリンクします。add_wait_queue(whead, &pwq->wait); } このようにして、次の図に示すように、client_fd の構造がさらに改善されます。 ep_poll_callback 関数は、対応する epoll_wait が起動される場所です。これについては後で説明します。 2.7、epoll_waitepoll_wait は主に ep_poll を呼び出します。 SYSCALL_DEFINE4(epoll_wait、int、epfd、struct epoll_event __user *、イベント、 int、maxevents、int、タイムアウト) { /* epfd が epoll_create によって作成された fd であるかどうかを確認します */ //ep_pollを呼び出す エラー = ep_poll(ep、イベント、最大イベント、タイムアウト); ... } 次に、ep_poll 関数を見てみましょう。 静的 int ep_poll(構造体eventpoll *ep, 構造体epoll_event __user *events, int maxevents、長いタイムアウト) { ...... リトライ: // スピンロックを取得 spin_lock_irqsave(&ep->lock, フラグ); // 現在の task_struct をウェイクアップの待機キューに書き込みます // wq_entry->func = default_wake_function; init_waitqueue_entry(&wait, 現在の値); // WQ_FLAG_EXCLUSIVE、排他的ウェイクアップ、SO_REUSEPORT と連携して受け入れパニック問題を解決します wait.flags |= WQ_FLAG_EXCLUSIVE; // ep の waitqueue へのリンク __add_wait_queue(&ep->wq, &wait); のために (;;) { // 現在のプロセス状態を割り込み可能に設定します。set_current_state(TASK_INTERRUPTIBLE); // 現在のスレッドに処理すべきシグナルがあるかどうかを確認し、ある場合は -EINTR を返します。 if (signal_pending(現在)) { 解像度 = -EINTR; 壊す; } spin_unlock_irqrestore(&ep->lock, フラグ); // スケジュールをスケジュールし、CPU を放棄する jtimeout = schedule_timeout(jtimeout); spin_lock_irqsave(&ep->lock, フラグ); } // ここでは、タイムアウトまたはイベントのトリガーによってプロセスが再スケジュールされたことを示します __remove_wait_queue(&ep->wq, &wait); // プロセスステータスを実行中に設定する TASK_RUNNING の現在の状態を設定します。 ...... // 利用可能なイベントがあるかどうかを確認します eavail = !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR; ...... //準備完了イベントをユーザー空間にコピー ep_send_events(ep, events, maxevents) } 上記のロジックは次の図に示されています。 2.8、ep_send_eventsep_send_events 関数は主に ep_scan_ready_list を呼び出します。名前が示すように、ep_scan_ready_list はスキャン準備リストです。 静的int ep_scan_ready_list(構造体eventpoll *ep, int (*sproc)(構造体eventpoll *, 構造体 list_head *、void *)、 void *priv、 整数の深さ) { ... // epfd の rdllist を txlist にリンクする list_splice_init(&ep->rdllist, &txlist); ... /* sproc = ep_send_events_proc */ エラー = (*sproc)(ep, &txlist, priv); ... // プロセス ovflist、つまり上記の sproc プロセスで発生するイベント... } 主に ep_send_events_proc を呼び出します。 静的 int ep_send_events_proc(構造体eventpoll *ep、構造体list_head *head、 void *priv) { (eventcnt = 0、uevent = esed->events;の場合 !list_empty(head) && eventcnt < esed->maxevents;) { // 準備完了リストを走査する epi = list_first_entry(head、構造体epitem、rdllink); list_del_init(&epi->rdllink); // readylist は現在のエピにイベントがあることのみを示します。特定のイベント情報については、対応するファイルのポーリングを呼び出す必要があります。 // ここでのポーリングは tcp_poll であり、tcp 自体の情報に従ってマスクとその他の情報を設定し、現在のイベントが興味のあるイベントであるかどうかを知るために興味のあるイベントマスクを設定します。epoll_wait revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) & epi->event.events; if(revents){ /* イベントをユーザー空間に配置する */ /* ONESHOTロジックを処理 */ // エッジ トリガーでない場合は、現在のエピを使用可能なリストに追加して、次回ポーリングをトリガーできるようにします。次のポーリングの revents が 0 でない場合、ユーザー スペースはそれを認識できます*/ そうでない場合 (!(epi->event.events & EPOLLET)){ リストの末尾に&epi->rdllink、&ep->rdllistを追加します。 } /* エッジ トリガーの場合は、使用可能なリストに追加されません。そのため、対応するエピは、次の使用可能なイベントがトリガーされたときにのみ使用可能なリストに追加されます */ イベントcnt++ } /* epoll_wait がポーリングされた revents イベントに興味がない場合 (またはイベントがまったくない場合)、そのイベントは利用可能なリストに追加されません */ ...... } eventcnt を返します。 } 上記のコードのロジックは次のとおりです。 3. epoll準備キュー(rdllist)にイベントを追加するプロセス上記の章で詳細に説明した後、データが到着したときに TCP が epoll の準備キューに参加する方法を最終的に説明できます。 3.1. 読み取り可能なイベントが到着するまず、ネットワーク カード ドライバーからカーネルの内部 TCP プロトコル処理呼び出しチェーンへの TCP データ パケットを見てみましょう。 ステップ1: ネットワーク パケット到着のカーネル パスは、ネットワーク カードが割り込みを開始した後、netif_rx を呼び出してイベントを CPU の待機キューにハングさせ、ソフト割り込み (soft_irq) を呼び出し、次に Linux ソフト割り込みメカニズムを通じて net_rx_action を呼び出します (次の図を参照)。 注: 上記の画像は PLKA (<<詳細な Linux カーネル アーキテクチャ>>) から引用したものです。 ステップ2: 次にnext_rx_actionに従ってください
対応するtcp_v4_rcvを見てみましょう
このようにして、最終的に epoll_wait を起動する ep_poll_callback 関数を見てみましょう。 静的 int ep_poll_callback(wait_queue_t *wait、符号なしモード、int sync、void *key) { // waitに対応するエピテムを取得する 構造体epitem *epi = ep_item_from_wait(wait); // epitem に対応する eventpoll 構造体 struct eventpoll *ep = epi->ep; // ready_list およびその他の構造体を保護するためにスピン ロックを取得します spin_lock_irqsave(&ep->lock, flags); // 現在の epi が ep の準備完了リストにリンクされていない場合は、リンクします // このようにして、現在利用可能なイベントが epoll の利用可能なリストに追加されます if (!ep_is_linked(&epi->rdllink)) リストの末尾に&epi->rdllink、&ep->rdllistを追加します。 // epoll_wait 待ちがある場合は、epoll_wait プロセスを起動します // 対応する &ep->wq は、epoll_wait が呼び出されたときに init_waitqueue_entry(&wait, current) によって生成されます // current は、epoll_wait の呼び出しに対応するプロセス情報 task_struct です waitqueue_active(&ep->wq) の場合 ウェイクアップロックされました(&ep->wq); } 上記のプロセスを次の図に示します。 最後に、wake_up_locked は __wake_up_common を呼び出し、次に init_waitqueue_entry に登録されている default_wake_function を呼び出します。呼び出しパスは次のとおりです。
epoll_wait プロセスを実行可能キューにプッシュし、カーネルがプロセスを再スケジュールするのを待ちます。その後、epoll_wait に対応するプロセスが再実行された後、スケジュールから再開し、次の ep_send_events (イベントをユーザー空間にコピーして戻る) を続行します。 wake_up プロセスを次の図に示します。 3.2. 書き込み可能なイベントの到着書き込み可能なイベントの操作プロセスは、読み取り可能なイベントの操作プロセスと似ています。 まず、epoll_ctl_add が呼び出されると、対応するファイル記述子のポーリングが事前に 1 回呼び出されます。戻りイベントに書き込み可能なマスクがある場合は、wake_up_locked が直接呼び出され、対応する epoll_wait プロセスが起動されます。 次に、データが TCP の基盤となるドライバーに到着すると、ACK が伝送され、もう一方の端で受信された一部のデータが解放され、書き込み可能なイベントがトリガーされます。この部分の呼び出しチェーンは次のとおりです。
最後に、この関数では、sk_stream_write_spaceが対応するepoll_waitプロセスを起動します。 void sk_stream_write_space(構造体 sock *sk) { // つまり、書き込み可能イベントは書き込み可能なスペースが 1/3 ある場合にのみトリガーされます if (sk_stream_wspace(sk) >= sk_stream_min_wspace(sk) && sock) { clear_bit(SOCK_NOSPACE, &sock->flags); if (sk->sk_sleep && waitqueue_active(sk->sk_sleep)) wake_up_interruptible_poll(sk->sk_sleep、POLLOUT | ポルワーノーム | ポルワーバンド) ...... } } 4. 記述子を閉じる(fdを閉じる)対応するファイル記述子を閉じると、対応するファイルを関連付けられた epoll_fd から削除するために、eventpoll_release が自動的に呼び出されることに注意してください。カーネル キー パスは次のとおりです。
したがって、対応するファイル記述子を閉じた後、epoll_ctl_del を介して対応する epoll 内の対応する記述子を削除する必要はありません。 V. 結論Epoll は、Linux における優れたイベント トリガー メカニズムとして広く使用されています。ソース コードは比較的複雑です。この記事では、epoll 読み取りおよび書き込みイベントのトリガー メカニズムについてのみ説明します。 上記は Linux ソースコード epoll の解析の詳細な内容です。Linux ソースコード epoll の詳細については、123WORDPRESS.COM の他の関連記事に注目してください。 以下もご興味があるかもしれません:
|
この記事では主に、Vue を使用してタブ ナビゲーション バーを実装し、flex レイアウトを使用し...
最近、製品部門のユーザーエクスペリエンスチームの学生は、アライアンス環境における広告に関する一連の研...
Fuser コマンドとは何ですか? fuser コマンドは、特定のファイル、ディレクトリ、またはソケ...
join() メソッド: 指定された区切り文字を使用して配列内のすべての要素を文字列に接続します。例...
この記事では、Jingdongの虫眼鏡効果を実現するためのJavaScriptの具体的なコードを紹介...
目次1.1Tinyint型の説明1.2 練習環境の説明1.3 未署名属性の追加1.3.1 SQLモー...
目次Vue が DOM を非同期更新する原理1 実際の DOM 要素を取得できるのはいつですか? 2...
読み取り専用入力を実現するには、無効と読み取り専用の 2 つの方法があります。当然、どちらの結果も読...
序文JSON は、言語に依存しないテキスト形式を使用する軽量のデータ交換形式で、XML に似ています...
スクリプトの要件: MySQL データベースを毎日バックアップし、スクリプトを 7 日間保存します。...
目次1. usrディレクトリにHadoopディレクトリを作成し、インストールパッケージをそのディレク...
以下に記録されているように、WIN10システムにMYSQLをダウンロードしてインストールするための詳...
前書き: Docker のポート マッピングは、多くの場合、Docker Run コマンド中に -p...
最近、プロジェクトの開発時に MySql データベースを使用しました。MySql に関する記事をいく...
Vue.js を使用して、クリックしてズームできる 9 グリッドの画像表示モジュールを作成しました。...