epoll
、サイクル翻訳を継続する可能性について調査を実施しました。調査参加者の90%以上が、残りの記事の翻訳を支持しました。したがって、本日、このサイクルの2番目の資料の翻訳を公開します。
Ep_insert()関数
関数
ep_insert()
は、実装で最も重要な関数の1つですepoll
。それがどのように機能するかを理解epoll
することは、それが監視しているファイルから新しいイベントに関する情報をどのように正確に取得するかを理解するために非常に重要です。
宣言
ep_insert()
はファイルの1267行目にありますfs/eventpoll.c
。この関数のいくつかのコードスニペットを見てみましょう。
user_watches = atomic_long_read(&ep->user->epoll_watches);
if (unlikely(user_watches >= max_user_watches))
return -ENOSPC;
このコードスニペットでは、関数は
ep_insert()
最初に、現在のユーザーが監視しているファイルの総数がで指定された値を超えていないかどうかを確認します/proc/sys/fs/epoll/max_user_watches
。の場合user_watches >= max_user_watches
、関数はにerrno
設定された状態ですぐに終了しENOSPC
ます。
次に
ep_insert()
、Linuxカーネルスラブメモリ管理メカニズムを使用してメモリを割り当てます。
if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
return -ENOMEM;
関数がに十分なメモリを割り当てることができた場合、
struct epitem
次の初期化プロセスが実行されます。
/* ... */
INIT_LIST_HEAD(&epi->rdllink);
INIT_LIST_HEAD(&epi->fllink);
INIT_LIST_HEAD(&epi->pwqlist);
epi->ep = ep;
ep_set_ffd(&epi->ffd, tfile, fd);
epi->event = *event;
epi->nwait = 0;
epi->next = EP_UNACTIVE_PTR;
その後
ep_insert()
、ファイル記述子にコールバックを登録しようとします。しかし、それについて話す前に、いくつかの重要なデータ構造に精通する必要があります。
フレームワーク
poll_table
は、poll()
VFS実装で使用される重要なエンティティです。(これは混乱を招く可能性があることを理解していますが、ここで説明した関数は、システム呼び出しではなく、poll()
ファイル操作の実装であることを説明しpoll()
ますpoll()
)。彼女はで発表されinclude/linux/poll.h
ます:
typedef struct poll_table_struct {
poll_queue_proc _qproc;
unsigned long _key;
} poll_table;
エンティティ
poll_queue_proc
は、次のようなコールバック関数のタイプを表します。
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
_key
テーブルの
メンバーは、poll_table
実際には最初に表示されたものではありません。つまり、ある「鍵」を連想させる名前にもかかわらず、_key
実際、私たちが関心を持っている出来事のマスクが保存されています。実装ではepoll
_key
、~0
(complement to 0)に設定されます。これはepoll
、あらゆる種類のイベントに関する情報を受信しようとしていることを意味します。これは理にかなっています。ユーザースペースアプリケーションはepoll_ctl()
、を使用していつでもイベントマスクを変更でき、VFSからすべてのイベントを受け入れてから、実装epoll
でそれらをフィルタリングするため、作業が簡単になります。元の構造の
復元を容易にするために、それはと呼ばれる単純な構造を使用します
poll_queue_proc
epitem
epoll
ep_pqueue
これはpoll_table
、対応する構造epitem
(ファイルfs/eventpoll.c
、行243)へのポインターを持つラッパーとして機能します。
/* -, */
struct ep_pqueue {
poll_table pt;
struct epitem *epi;
};
次に、を
ep_insert()
初期化しますstruct ep_pqueue
。次のコードは、最初に、追加しようとしているファイルに対応する構造へepi
のep_pqueue
ポインターを構造メンバーに書き込み、epitem
次に構造ep_ptable_queue_proc()
メンバーに書き込み、それに書き込みます。_qproc
ep_pqueue
_key
~0
/* */
epq.epi = epi;
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
それは
ep_insert()
なり、その後呼び出すep_item_poll(epi, &epq.pt);
実装への呼び出しになりますこれは、poll()
ファイルに関連付けられています。Linux TCPスタック
実装を使用する例を見て、
poll()
その実装がで何をするのかを正確に理解しましょうpoll_table
。
関数
tcp_poll()
はpoll()
TCPソケットの実装です。そのコードは、ファイルのnet/ipv4/tcp.c
436行目にあります。このコードのスニペットは次のとおりです。
unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)
{
unsigned int mask;
struct sock *sk = sock->sk;
const struct tcp_sock *tp = tcp_sk(sk);
sock_rps_record_flow(sk);
sock_poll_wait(file, sk_sleep(sk), wait);
//
}
関数
tcp_poll()
はsock_poll_wait()
、を2番目の引数sk_sleep(sk)
として、および3番目の引数として渡しますwait
(これは、以前に関数に渡されたtcp_poll()
テーブルですpoll_table
)。
それはなん
sk_sleep()
ですか?結局のところ、これは特定の構造のイベント待機キューにアクセスするための単なるゲッターですsock
(ファイルinclude/net/sock.h
、1685行目)。
static inline wait_queue_head_t *sk_sleep(struct sock *sk)
{
BUILD_BUG_ON(offsetof(struct socket_wq, wait) != 0);
return &rcu_dereference_raw(sk->sk_wq)->wait;
}
何される
sock_poll_wait()
イベント待ちキューに何をするつもり?この関数はいくつかの簡単なチェックを実行poll_wait()
してから、同じパラメーターで呼び出すことがわかります。次に、関数poll_wait()
は指定したコールバックを呼び出し、それにイベント待機キューを渡します(ファイルinclude/linux/poll.h
、42行目)。
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
if (p && p->_qproc && wait_address)
p->_qproc(filp, wait_address, p);
}
epoll
エンティティ
の場合、1091行目のファイルで宣言されて_qproc
いる関数ep_ptable_queue_proc()
になりますfs/eventpoll.c
。
/*
* - ,
* , .
*/
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
poll_table *pt)
{
struct epitem *epi = ep_item_from_epqueue(pt);
struct eppoll_entry *pwq;
if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
pwq->whead = whead;
pwq->base = epi;
add_wait_queue(whead, &pwq->wait);
list_add_tail(&pwq->llink, &epi->pwqlist);
epi->nwait++;
} else {
/* */
epi->nwait = -1;
}
}
まず、作業中の待機キューからファイルに対応する
ep_ptable_queue_proc()
構造を復元しようとしepitem
ます。epoll
ラッパー構造を使用しているため、ポインターからのep_pqueue
復元は単純なポインター操作です。
その後、に必要なだけのメモリを割り当てます。この構造は、監視されているファイルの待機キューとそのファイルの対応する構造の間の「接着剤」として機能します。監視対象のファイルの待機キューヘッドがどこにあるかを知ることは非常に重要です。そうしないと、後で待機キューの登録を解除できなくなります。構造epitem
poll_table
ep_ptable_queue_proc()
struct eppoll_entry
epitem
epoll
epoll
eppoll_entry
pwq->wait
プロセス再開機能が提供された待機()キューも含まれますep_poll_callback()
。このエンティティは次のタスクを解決するために使用されるため、おそらくpwq->wait
これは実装全体の中で最も重要な部分epoll
です。
- 監視対象の特定のファイルで発生するイベントを監視します。
- そのような必要が生じた場合に他のプロセスの作業を再開する。
次に、ターゲットファイルの待機キューに
ep_ptable_queue_proc()
アタッチpwq->wait
します(whead
)。この関数はstruct eppoll_entry
、struct epitem
(epi->pwqlist
)からリンクリストに追加し、リストepi->nwait
の長さを表す値をインクリメントしますepi->pwqlist
。
そして、ここで私は1つの質問があります。
epoll
リンクリストを使用してeppoll_entry
、epitem
単一のファイル構造内に構造を格納するのはなぜですか?epitem
1つの要素だけがeppoll_entry
必要ではありませんか?
しかし、私はこの質問に正確に答えることはできません。私の知る限り、誰かが
epoll
いくつかのクレイジーなループでインスタンスを使用する予定がない限り、リストepi->pwqlist
には1つの要素のみが含まれstruct eppoll_entry
、epi->nwait
ほとんどのファイルでは、である可能性があります1
。
良いことは、周りのあいまいさが、
epi->pwqlist
以下で説明する内容にまったく影響を与えないことです。つまり、Linuxepoll
が監視対象のファイルに発生したイベントのインスタンスを通知する方法について説明します。
前のセクションで話したことを覚えていますか?それは、ターゲットファイルの待機リストに何が
epoll
追加さwait_queue_t
れるかについてでした(to wait_queue_head_t
)。がwait_queue_t
最も一般的にプロセスを再開するためのメカニズムとして使用さ、それは基本的に単なる構造である店舗Linuxはキューからプロセスを再開することを決定したときに呼び出される関数へのポインタwait_queue_t
に添付wait_queue_head_t
。この機能ではepoll
再開信号をどうするかを決めることができますが、epoll
プロセスを再開する必要はありません!後でわかるように、通常、ep_poll_callback()
resumeを呼び出しても何も起こりません。
で使用されるプロセス再開メカニズム
poll()
は完全に実装に依存していることにも注意する価値があると思います。 TCPソケットファイルの場合、待機キューヘッドはsk_wq
構造に格納されているメンバーsock
です。これep_ptable_queue_proc()
は、待機キューを操作するためにコールバックを使用する必要があることも説明しています。異なるファイルのキューの実装では、キューの先頭が完全に異なる場所に表示される可能性があるため、必要な値を見つける方法がありません。wait_queue_head_t
コールバックを使用せずに。構造内
の作業の再開はいつ正確に実行されますか?結局のところ、LinuxソケットシステムはVFSと同じ「OO」設計原則に従います。この構造は、ファイルの2312行目で次のフックを宣言しています。
sk_wq
sock
sock
net/core/sock.c
void sock_init_data(struct socket *sock, struct sock *sk)
{
// ...
sk->sk_data_ready = sock_def_readable;
sk->sk_write_space = sock_def_write_space;
// ...
}
B
sock_def_readable()
およびsock_def_write_space()
コールがあるwake_up_interruptible_sync_poll()
ため(struct sock)->sk_wq
、関数コールバック、再生可能な処理作業の目的。
ときになる
sk->sk_data_ready()
と呼び出されますかsk->sk_write_space()
?実装によって異なります。例としてTCPソケットを取り上げましょう。この関数sk->sk_data_ready()
は、TCP接続が3方向ハンドシェイク手順を完了したとき、または特定のTCPソケットのバッファーを受信したときに、割り込みハンドラーの後半で呼び出されます。この関数sk->sk_write_space()
は、バッファの状態がからfull
に変わるときに呼び出されavailable
ます。次のトピック、特にフロントトリガーに関するトピックを分析するときにこれを覚えておくと、これらのトピックはより興味深いものになります。
結果
これで、実装に関する一連の記事の2番目の記事は終わり
epoll
です。次回epoll
は、ソケットプロセス再開キューに登録されたコールバックで正確に何をするかについて話しましょう。
epollを使用したことがありますか?