この2日間のブラシアルゴリズム、寒さを感じる、でも読むことができない、自分の質問に対する他の人の答えを見ていません。タレントそれ、これは保存されません。私たちは、気まぐれのepollはこの事を一日中、それのソースを調べる方が確実になるよう、いくつかのことを行うことがもっと面白いものを見つけ、彼はそれを傷つける達成を見てについて置く最初のアルゴリズムに決めました!
ネットワークプログラミングは、再集計のための3つの異なるシナリオがありますが、共通の多重IOがあります。それを選択し、世論調査、ファイルディスクリプタは、もちろん、選択し、投票はありません記事の主人公は、次のファイルディスクリプタの私の主な原則の詳細な実現です。もちろん、私が選択し、投票は対照的なを説明します。
問題について考えてみましょう:
- なぜI / Oの多重化は、IO多重化を使用して、単一のプロセスの使用上の任意の効果を持っていない、あるのでしょうか?
この問題は、多分これが「なぜ」を提案意味をなさない、インターネット上で答えを見つけられませんでした。答えは、シングルスレッドでのマルチソケットソケット状態の変化を追跡するためのメカニズムを多重化し、実際には比較的簡単です。IO多重化せずに、検出複数のシングルスレッドソケットの状態変化を達成できません。(上記は参考のために、個人的な意見です)
私たちは、IO多重化機構投票、ファイルディスクリプタシステムコールを達成するために、選択します。最初の二つの場合は、限り、すべての準備ができてイベントがあると、すべてのユーザ・モードからのすべてのFDコピーはカーネルモードすることを意味し選択するために渡されたか、pollシステムコールを、fdは。イベントはIOイベントは、カーネルモードにコピーされ、他の記述子があるという理由だけで、いくつかの記述子にIOが起こっていないため、コピーのこの過剰数は、意味がありません。
選択及びポーリング選択システムコールを区別する必要があるビットfd_bits記述子のアレイを介して聞くことを集め、カーネルマクロ定義は、1024を聞く記述子の最大数を制限します。
制限なく聴い投票数の場合は、限り新しい接続に関しては、接続は投票によって維持記述子リストに追加されます。
IO多重化メカニズムの両方について、非中核業務に接続する準備ができて、無効なコピーを避けるために解決されていません。以下、接続状態のため、接続の数が増加するが、プログラムの使用は、3点の性能差であるIO多重なく、しかし、もし上記の3つのメカニズムの効率が明らかになる。
なぜ、epollを効果的かつ効率的な?
ファイルディスクリプタ関連のシステムコール:epoll_create、epoll_ctlとイベントがepoll_wait。
Linuxシステムでは、カーネルのepollを監視するファイルディスクリプタを格納するepollファイルシステムを登録したいです。
epoll_createすると、それだけのepollに仕える、ファイルノードepollファイルシステムを作成します。ファイルディスクリプタはカーネルに初期化されると、バックのepollに、オペレーティングシステムは、カーネルのバッファキャッシュを開く(连续的物理内存页
)、保存を保存するために使用红黑树结构
。
static int __init eventpoll_init(void)
{
mutex_init(&pmutex);
ep_poll_safewake_init(&psw);
//开辟slab高速缓冲区
epi_cache = kmem_cache_create(“eventpoll_epi”,sizeof(struct epitem),
0,SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|
SLAB_PANIC,NULL);
pwq_cache = kmem_cache_create(“eventpoll_pwq”,sizeof(struct
eppoll_entry),0,EPI_SLAB_DEBUG|SLAB_PANIC,NULL);
return 0;
}
ファイルディスクリプタを学習の始まりでのepoll基盤となるコアデータ構造は赤黒木で実現聞いたことがある、赤黒木は、より高い効率性を見つけて削除し、自己均衡バイナリツリーです。
epoll_createを呼び出した後、ファイルディスクリプタ基礎となる実装はfdの準備イベントを保存、赤黒木は、epollを高速バッファの視点の背後に着信接続を受信するために使用されて作成するだけでなく、準備ができてリストを作成します:
//当来一个新连接,基于slab高速缓存(可以理解为一种分配内存的机制,对于频繁的使用和销毁特别的高效),创建一个epitem对象,保存下面描述的信息,然后将这个对象加到红黑树中。
struct epitem {
struct rb_node rbn; //用于主结构管理的红黑树
struct list_head rdllink; //事件就绪队列
struct epitem *next; //用于主结构体中的链表
struct epoll_filefd ffd; //这个结构体对应的被监听的文件描述符信息
int nwait; //poll操作中事件的个数
struct list_head pwqlist; //双向链表,保存着被监视文件的等待队列,功能类似于select/poll中的poll_table
struct eventpoll *ep; //该项属于哪个主结构体(多个epitm从属于一个eventpoll)
struct list_head fllink; //双向链表,用来链接被监视的文件描述符对应的struct file。因为file里有f_ep_link,用来保存所有监视这个文件的epoll节点
struct epoll_event event; //注册的感兴趣的事件,也就是用户空间的epoll_event
}
//每个epoll fd(epfd)对应的主要数据结构为:
struct eventpoll {
spin_lock_t lock; //对本数据结构的访问
struct mutex mtx; //防止使用时被删除
wait_queue_head_t wq; //sys_epoll_wait() 使用的等待队列
wait_queue_head_t poll_wait; //file->poll()使用的等待队列
struct list_head rdllist; //事件满足条件的链表
struct rb_root rbr; //用于管理所有fd的红黑树(树根)
struct epitem *ovflist; //将事件到达的fd进行链接起来发送至用户空间
}
//内核通过创建一个fd与这个结构相关联,管理红黑树结构,即epoll_create1实现
SYSCALL_DEFINE1(epoll_create1, int, flags)
{
int error, fd;
//////////////////////////////////////////////////////////////////////////////////////////////////////
struct eventpoll *ep = NULL;
struct file *file;
/* Check the EPOLL_* constant for consistency. */
BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);
if (flags & ~EPOLL_CLOEXEC)
return -EINVAL;
/*
* Create the internal data structure ("struct eventpoll").
*/
error = ep_alloc(&ep);
if (error < 0)
return error;
/*
* Creates all the items needed to setup an eventpoll file. That is,
* a file structure and a free file descriptor.
*/
////////////////////////////////////////////////////////////////////////////////////////////////////
fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
if (fd < 0) {
error = fd;
goto out_free_ep;
}
file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
O_RDWR | (flags & O_CLOEXEC));
if (IS_ERR(file)) {
error = PTR_ERR(file);
goto out_free_fd;
}
////////////////////////////////////////////////////////////////////////////////////////////
ep->file = file;
fd_install(fd, file);
return fd;
out_free_fd:
put_unused_fd(fd);
out_free_ep:
ep_free(ep);
return error;
}
//创建并初始化eventpoll结构体实例,由ep_alloc实现(第52行)可知,调用kzalloc在内核空间申请内存并初始化清零。
static int ep_alloc(struct eventpoll **pep)
{
int error;
struct user_struct *user;
struct eventpoll *ep;
user = get_current_user();
error = -ENOMEM;
ep = kzalloc(sizeof(*ep), GFP_KERNEL);
if (unlikely(!ep))
goto free_uid;
spin_lock_init(&ep->lock);
mutex_init(&ep->mtx);
init_waitqueue_head(&ep->wq);
init_waitqueue_head(&ep->poll_wait);
INIT_LIST_HEAD(&ep->rdllist);
ep->rbr = RB_ROOT;
ep->ovflist = EP_UNACTIVE_PTR;
ep->user = user;
*pep = ep;
return 0;
free_uid:
free_uid(user);
return error;
}
赤黒木IOイベント(つまり、準備アウトレディリストに赤黒木からのイベント)、及びその後のイベントがepoll_waitによってリターンで準備イベントリストrdlistに赤黒木のノードを除去する除去作業が存在することになる場合ユーザプロセス、またはプロセスで行われる処理ロジックやIO処理に、そのファイルディスクリプタのための準備ができていることは、単純に、関係記述子リンクリストです。投票が好きである限り、個々のIOアクティビティがコレクション全体またはリストを介して起こるFDにあるとして選択しないでください。
- epollは、インターネットでもmmapの共有ユーザ空間とカーネル空間を使用するとありますか?
前の例によるこの発見は支持できないで、我々は彼が赤黒ツリーに追加された新しい接続に赤黒のツリー構造を保存するために使用され、epollをキャッシュすることにより、ソースコードがある見ることができますユーザ空間とカーネル空間の共有メモリがある場合、ロジックでは非常に合理的なされているテストは、それは余計なことではないでしょうか?ファイルディスクリプタは、それ自体をFDやepitemが作成されたカーネルメモリの割り当てに基づいているかに関係なく、何のmmapは言わなかったがあります。
次のコードノードEPOLL_CTL_ADD赤黒木を追加する主に、readyイベントを除去する工程があります。
static int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd)
{
int error ,revents,pwake = 0;
unsigned long flags ;
struct epitem *epi;
/*
struct ep_queue{
poll_table pt;
struct epitem *epi;
} */
struct ep_pqueue epq;
//分配一个epitem结构体来保存每个加入的fd
if(!(epi = kmem_cache_alloc(epi_cache,GFP_KERNEL)))
goto error_return;
//初始化该结构体
ep_rb_initnode(&epi->rbn);
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;
epq.epi = epi;
//安装poll回调函数
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc );
/* 调用poll函数来获取当前事件位,其实是利用它来调用注册函数ep_ptable_queue_proc(poll_wait中调用)。
如果fd是套接字,f_op为socket_file_ops,poll函数是
sock_poll()。如果是TCP套接字的话,进而会调用
到tcp_poll()函数。此处调用poll函数查看当前
文件描述符的状态,存储在revents中。
在poll的处理函数(tcp_poll())中,会调用sock_poll_wait(),
在sock_poll_wait()中会调用到epq.pt.qproc指向的函数,
也就是ep_ptable_queue_proc()。 */
revents = tfile->f_op->poll(tfile, &epq.pt);
spin_lock(&tfile->f_ep_lock);
list_add_tail(&epi->fllink,&tfile->f_ep_lilnks);
spin_unlock(&tfile->f_ep_lock);
ep_rbtree_insert(ep,epi); //将该epi插入到ep的红黑树中
spin_lock_irqsave(&ep->lock,flags);
// revents & event->events:刚才fop->poll的返回值中标识的事件有用户event关心的事件发生。
// !ep_is_linked(&epi->rdllink):epi的ready队列中有数据。ep_is_linked用于判断队列是否为空。
/* 如果要监视的文件状态已经就绪并且还没有加入到就绪队列中,则将当前的
epitem加入到就绪队列中.如果有进程正在等待该文件的状态就绪,则
唤醒一个等待的进程。 */
if((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
list_add_tail(&epi->rdllink,&ep->rdllist); //将当前epi插入到ep->ready队列中。
/* 如果有进程正在等待文件的状态就绪,
也就是调用epoll_wait睡眠的进程正在等待,
则唤醒一个等待进程。
waitqueue_active(q) 等待队列q中有等待的进程返回1,否则返回0。
*/
if(waitqueue_active(&ep->wq))
__wake_up_locked(&ep->wq,TAKS_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE);
/* 如果有进程等待eventpoll文件本身(???)的事件就绪,
则增加临时变量pwake的值,pwake的值不为0时,
在释放lock后,会唤醒等待进程。 */
if(waitqueue_active(&ep->poll_wait))
pwake++;
}
spin_unlock_irqrestore(&ep->lock,flags);
if(pwake)
ep_poll_safewake(&psw,&ep->poll_wait);//唤醒等待eventpoll文件状态就绪的进程
return 0;
}
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
revents = tfile->f_op->poll(tfile, &epq.pt);
这两个函数将ep_ptable_queue_proc注册到epq.pt中的qproc。
typedef struct poll_table_struct {
poll_queue_proc qproc;
unsigned long key;
}poll_table;
执行f_op->poll(tfile, &epq.pt)时,XXX_poll(tfile, &epq.pt)函数会执行poll_wait(),poll_wait()会调用epq.pt.qproc函数,即ep_ptable_queue_proc。
ep_ptable_queue_proc函数如下:
/* 在文件操作中的poll函数中调用,将epoll的回调函数加入到目标文件的唤醒队列中。
如果监视的文件是套接字,参数whead则是sock结构的sk_sleep成员的地址。 */
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt) {
/* struct ep_queue{
poll_table pt;
struct epitem *epi;
} */
struct epitem *epi = ep_item_from_epqueue(pt); //pt获取struct ep_queue的epi字段。
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 {
/* We have to signal that an error occurred */
/*
* 如果分配内存失败,则将nwait置为-1,表示
* 发生错误,即内存分配失败,或者已发生错误
*/
epi->nwait = -1;
}
}
ファイルディスクリプタが時間複雑ep_find、ep_insert、ep_modify、赤黒木ベースのルックアップ様ep_removeを有する赤黒木に基づいているため、他の操作を挿入O(LOGN)です。だから、epollを迅速かつ効率的。
以下の図面と併せてより明確にする必要がありますがepollイベント処理プロセスでした。
ファイルディスクリプタエッジトリガの
ET
トリガレベルLT
エッジトリガ、ソケットのみIOオペレーションのために、ある状態から別の状態に変化した場合には、。たとえば:ETモードで何もないから、いくつかのプロセスに、つまり、バッファが空であったであろう、と今突然データがある、ワンタイムデータが削除、データバッファはまた、完成し得なかった場合、この時間は、ブロックモードまたは非ブロックモードに設定されたソケットを得点し、ブロックモードに設定されたソケット場合、ETモードでは、イベントトリガを読んで、一回は、それの後に、データを読み終える場合ではありませんETは別の時間をトリガしません、それはソケットを下にブロックされていたであろう、新しいデータを読み取ることができない;非ブロックに設定した場合、その後、我々は処理サイクルで読まイベントが一回トリガーされるもののつまり、一度に設定する必要がありますが、私は戻りEAGAINまでソケットを読んで私が読ん回数によって決定されます。ファイルディスクリプタを使用した場合である、非ブロックソケットセットの理由!
データのためのLTモデルが削除されていないために、これは離れて取らなかった場合、次の行に完全なテイクを取るしないことが判明戻ってきて、比較的簡単です。
ETモードそんなに手間ので、LTは心を話し、ETは、一般的にモデルを使用し、なぜ、これほど少ないですの?LTモードので、ETモード、ソケットノンブロッキングセット、処理は意志だけIO関連するシステム呼び出しで、ファイルディスクリプタに関連するシステムコールと、I / O処理に関連するシステムコールを生成し続けます。一般ユーザモードインターフェースの呼び出しのパフォーマンスが食べることが多いので、プログラムは種類のシステムコールの頻度を減らす方法を考えてみてくださいに比べて、Linuxのシステムコールでは、このプログラムは、パフォーマンスが向上します!
(ps.本段是个人见解,要是有其他见解的话,希望能分享一下)
要約IOは、これまでに終わりが来る多重化のために、我々はまた、このブログを更新していきますの背後にあるソースコードを解釈していきます。記述が間違っている場合は、共有してください私を修正!
この記事の参考資料:
http://www.pandademo.com/2016/11/the-discrimination-of-linux-kernel-epoll-et-and-lt/