高性能サーバの下部ベールの取り外し


高性能サーバの下部ベールの取り外し
一、前言
私たちはよく高性能サーバーを聞いていますが、それは高性能サーバーとは何ですか.大白話で説明すると、事件の処理が速く、効率が高く、サーバー資源の占有が少なく、多重化などが万千の寵愛を集めている.しかし、高性能を実現するには、優れたアーキテクチャと下位インタフェースが必要です.
この文章はlinuxプラットフォームに限られています.windowsプラットフォームの下で、IOCPの使い方を参考にすることができます.ここではあまり言いません.
現在主流の高性能サーバの下層はEPOLLインタフェースをカプセル化し、epollを使用してイベント処理を行うが、なぜepollは高性能サーバの下層イベント処理として使用できるのか.では、ソースコードから始めて、ベールを外しましょう.
epollソースコードを取得するには、微信公衆番号-バックグラウンドサーバで開発し、「epollソースコード」に返信して取得してください.
二、ソースコード解読
2つの重要な構造体
eventpoll構造体:
/*
 *        file->private_data 
 */
/*
    eventpoll    epoll            ,    
    1. struct rb_root rbr;           ,        ,
                  socket   ,     epoll_ctl 
    epoll      socket   ,              
    struct epitem   ,               
    2.struct list_head rdllist;        ,         
                epoll_wait             
    3.struct file *file;      ,  epoll  
    */
struct eventpoll {
     
	//    , kernel        ,       ( )          
	//      ready_list
	spinlock_t lock;
	//            eventloop             ,           
	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 user_struct *user;
	//         
	struct file *file;
	//               
	int visited;
	struct list_head visited_list_link;
};

epitem構造体
//         epoll     
struct epitem {  
    //    eventpoll         
    struct rb_node rbn;  
    //    eventpoll.rdllist      
    struct list_head rdllink;  
    //    ovflist      
    struct epitem *next;  
    /*        fd + file,     key */  
    struct epoll_filefd ffd;  
    /* Number of active wait queue attached to poll operations */  
    int nwait;  
    //          (eppoll_entry)    
    //                ,  
    //            wait_queue   
    // (            ),  
    //           
    struct list_head pwqlist;  
    //   epitem       
    struct eventpoll *ep;  
    /* List header used to link this item to the "struct file" items list */  
    struct list_head fllink;  
    /* epoll_ctl         */  
    struct epoll_event event;  
};  

int epoll_create(int size);
作用:epoll_を呼び出すcreateメソッドepollのハンドルを作成する
ソース:
SYSCALL_DEFINE1(epoll_create, int, size)
{
	if (size <= 0)
		return -EINVAL;

	return do_epoll_create(0);
}

ソースコードから見るとsizeというパラメータはあまり役に立たず、0より大きくさえあればいいのです~
私が他の場所から資料を取得したのは、以前は下層実装がハッシュ表だったが、今は赤と黒の木で、互換性のためにこのパラメータを残していて、真偽も知らないので、理解しておくべきだということだ.
次にdo_epoll_createを見てみましょう
static int do_epoll_create(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;
	}
    //      [eventpoll]   ,          ,       epoll  
	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;
    //  file                 
	fd_install(fd, file);
	return fd;

out_free_fd:
	put_unused_fd(fd);
out_free_ep:
	ep_free(ep);
	return error;
}

ここで、error = ep_alloc(&ep);は、eventpollの構造を割り当てて行う初期化動作である.
以上、epollがファイルを作成する過程で、初期化とファイル関連などを行った.
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
役割:epollのイベント登録関数
ソース:
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
		struct epoll_event __user *, event)
{
     
	struct epoll_event epds;
	
   	//    :     , epoll_event    NULL   
	//                    epoll_event  copy     
	if (ep_op_has_event(op) &&
        //            
	    copy_from_user(&epds, event, sizeof(struct epoll_event)))
		return -EFAULT;

	return do_epoll_ctl(epfd, op, fd, &epds, false);
}

関数do_epoll_ctlを見てみましょう.
int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds,
		 bool nonblock)
{
     
	int error;
	int full_check = 0;
	struct fd f, tf;
	struct eventpoll *ep;
	struct epitem *epi;
	struct eventpoll *tep = NULL;

    //      
	.....
    
	epi = ep_find(ep, tf.file, fd);

	error = -EINVAL;
	switch (op) {
     
            //  
	case EPOLL_CTL_ADD:
		if (!epi) {
     
			epds->events |= EPOLLERR | EPOLLHUP;
			error = ep_insert(ep, epds, tf.file, fd, full_check);
		} else
			error = -EEXIST;
		break;
            //  
	case EPOLL_CTL_DEL:
		if (epi)
			error = ep_remove(ep, epi);
		else
			error = -ENOENT;
		break;
            //  
	case EPOLL_CTL_MOD:
		if (epi) {
     
			if (!(epi->event.events & EPOLLEXCLUSIVE)) {
     
				epds->events |= EPOLLERR | EPOLLHUP;
				error = ep_modify(ep, epi, epds);
			}
		} else
			error = -ENOENT;
		break;
	}
	if (tep != NULL)
		mutex_unlock(&tep->mtx);
	mutex_unlock(&ep->mtx);

error_tgt_fput:
	if (full_check) {
     
		clear_tfile_check_list();
		mutex_unlock(&epmutex);
	}

	fdput(tf);
error_fput:
	fdput(f);
error_return:

	return error;
}

do_epoll_ctl関数では、ファイル記述子の検証を行い、入力されたfdに従って追加して監視することが多いので、ここでは増加した操作を見てみましょう.
// epollfd        fd
static int ep_insert(struct eventpoll *ep, const struct epoll_event *event,
		     struct file *tfile, int fd, int full_check)
{
     
	int error, pwake = 0;
	__poll_t revents;
	long user_watches;
	struct epitem *epi;
	struct ep_pqueue epq;

	lockdep_assert_irqs_enabled();

	user_watches = atomic_long_read(&ep->user->epoll_watches);
	if (unlikely(user_watches >= max_user_watches))
		return -ENOSPC;
    //       epi   
	if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
		return -ENOMEM;

	/* Item initialization follow here ... */
	INIT_LIST_HEAD(&epi->rdllink);
	INIT_LIST_HEAD(&epi->fllink);
	INIT_LIST_HEAD(&epi->pwqlist);
    // epoll      fd epitem   ep   
	epi->ep = ep;
    //                     epitem ffd   
	ep_set_ffd(&epi->ffd, tfile, fd);
    //  fd        
	epi->event = *event;
	epi->nwait = 0;
	epi->next = EP_UNACTIVE_PTR;
	if (epi->event.events & EPOLLWAKEUP) {
     
		error = ep_create_wakeup_source(epi);
		if (error)
			goto error_create_wakeup_source;
	} else {
     
		RCU_INIT_POINTER(epi->ws, NULL);
	}

	/* Initialize the poll table using the queue callback */
	epq.epi = epi;
    // ep_ptable_queue_proc   epq.pt 。
	init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);

	/*
	 * Attach the item to the poll hooks and get current event bits.
	 * We can safely use the file* here because its usage count has
	 * been increased by the caller of this function. Note that after
	 * this operation completes, the poll callback can start hitting
	 * the new item.
	 */
    //       ep_ptable_queue_proc,       wait queue head    
    //       ,            
	revents = ep_item_poll(epi, &epq.pt, 1);

	/*
	 * We have to check if something went wrong during the poll wait queue
	 * install process. Namely an allocation for a wait queue failed due
	 * high memory pressure.
	 */
	error = -ENOMEM;
	if (epi->nwait < 0)
		goto error_unregister;

	/* Add the current item to the list of active epoll hook for this file */
    // epitem   f_ep_links     
	spin_lock(&tfile->f_lock);
	list_add_tail_rcu(&epi->fllink, &tfile->f_ep_links);
	spin_unlock(&tfile->f_lock);

	/*
	 * Add the current item to the RB tree. All RB tree operations are
	 * protected by "mtx", and ep_insert() is called with "mtx" held.
	 */
    //   epitem   ep     
	ep_rbtree_insert(ep, epi);

	/* now check if we've created too many backpaths */
	error = -EINVAL;
	if (full_check && reverse_path_check())
		goto error_remove_epi;

	/* We have to drop the new item inside our item list to keep track of it */
	write_lock_irq(&ep->lock);

	/* record NAPI ID of new item if present */
	ep_set_busy_poll_napi_id(epi);

	/* If the file is already "ready" we drop it inside the ready list */
    //                           ,     epitem     
	if (revents && !ep_is_linked(epi)) {
     
		list_add_tail(&epi->rdllink, &ep->rdllist);
		ep_pm_stay_awake(epi);

		/* Notify waiting tasks that events are available */
		if (waitqueue_active(&ep->wq))
            //   sys_epoll_wait ,         sys_epoll_wait     
			wake_up(&ep->wq);
		if (waitqueue_active(&ep->poll_wait))
			pwake++;
	}

	write_unlock_irq(&ep->lock);

	atomic_long_inc(&ep->user->epoll_watches);

	/* We have to call this outside the lock */
	if (pwake)
		ep_poll_safewake(ep, NULL);

	return 0;

error_remove_epi:
	spin_lock(&tfile->f_lock);
	list_del_rcu(&epi->fllink);
	spin_unlock(&tfile->f_lock);

	rb_erase_cached(&epi->rbn, &ep->rbr);

error_unregister:
	ep_unregister_pollwait(ep, epi);

	/*
	 * We need to do this because an event could have been arrived on some
	 * allocated wait queue. Note that we don't care about the ep->ovflist
	 * list, since that is used/cleaned only inside a section bound by "mtx".
	 * And ep_insert() is called with "mtx" held.
	 */
	write_lock_irq(&ep->lock);
	if (ep_is_linked(epi))
		list_del_init(&epi->rdllink);
	write_unlock_irq(&ep->lock);

	wakeup_source_unregister(ep_wakeup_source(epi));

error_create_wakeup_source:
	kmem_cache_free(epi_cache, epi);

	return error;
}

ここでは、イベントのバインドとマウント操作をさらに行います.このsocketにイベントの準備ができている場合は、ep_が呼び出されます.poll_callback関数.イベントを準備キューに追加し、epollを起動します.wait;
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
役割:epollモニタリングのイベントですでに発生したイベントを待つ.
ソース
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
		int, maxevents, int, timeout)
{
     
	return do_epoll_wait(epfd, events, maxevents, timeout);
}
do_epoll_wait関数を直接見に行きましょう~
static int do_epoll_wait(int epfd, struct epoll_event __user *events,
			 int maxevents, int timeout)
{
     
	int error;
	struct fd f;
	struct eventpoll *ep;

	/* The maximum number of event must be greater than zero */
	if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
		return -EINVAL;

	/* Verify that the area passed by the user is writeable */
	if (!access_ok(events, maxevents * sizeof(struct epoll_event)))
		return -EFAULT;

	/* Get the "struct file *" for the eventpoll file */
    //  epoll struct file
	//      struct file  eventpoll 
	f = fdget(epfd);
	if (!f.file)
		return -EBADF;

	/*
	 * We have to check that the file structure underneath the fd
	 * the user passed to us _is_ an eventpoll file.
	 */
	error = -EINVAL;
	if (!is_file_epoll(f.file))
		goto error_fput;

	/*
	 * At this point it is safe to assume that the "private_data" contains
	 * our own data structure.
	 */
    //   private_data  eventpoll  
	ep = f.file->private_data;

	/* Time to fish for events ... */
    //       
	error = ep_poll(ep, events, maxevents, timeout);

error_fput:
	fdput(f);
	return error;
}

コアはep_poll関数にあるようですね~行ってみましょう
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
		   int maxevents, long timeout)
{
     
	int res = 0, eavail, timed_out = 0;
	u64 slack = 0;
	wait_queue_entry_t wait;
	ktime_t expires, *to = NULL;

	lockdep_assert_irqs_enabled();
	//             timeout
	if (timeout > 0) {
     
		struct timespec64 end_time = ep_set_mstimeout(timeout);

		slack = select_estimate_accuracy(&end_time);
		to = &expires;
		*to = timespec64_to_ktime(end_time);
	//   
	} else if (timeout == 0) {
     
		/*
		 * Avoid the unnecessary trip to the wait queue loop, if the
		 * caller specified a non blocking operation. We still need
		 * lock because we could race and not see an epi being added
		 * to the ready list while in irq callback. Thus incorrectly
		 * returning 0 back to userspace.
		 */
		timed_out = 1;

		write_lock_irq(&ep->lock);
		eavail = ep_events_available(ep);
		write_unlock_irq(&ep->lock);

		goto send_events;
	}

fetch_events:
	//       ,       eventpoll  rdllist  
	if (!ep_events_available(ep))
		ep_busy_loop(ep, timed_out);

	eavail = ep_events_available(ep);
	if (eavail)
		goto send_events;

	/*
	 * Busy poll timed out.  Drop NAPI ID for now, we can add
	 * it back in when we have moved a socket with a valid NAPI
	 * ID onto the ready list.
	 */
	ep_reset_busy_poll_napi_id(ep);

	do {
     
		/*
		 * Internally init_wait() uses autoremove_wake_function(),
		 * thus wait entry is removed from the wait queue on each
		 * wakeup. Why it is important? In case of several waiters
		 * each new wakeup will hit the next waiter, giving it the
		 * chance to harvest new event. Otherwise wakeup can be
		 * lost. This is also good performance-wise, because on
		 * normal wakeup path no need to call __remove_wait_queue()
		 * explicitly, thus ep->lock is not taken, which halts the
		 * event delivery.
		 */
		init_wait(&wait);

		write_lock_irq(&ep->lock);
		/*
		 * Barrierless variant, waitqueue_active() is called under
		 * the same lock on wakeup ep_poll_callback() side, so it
		 * is safe to avoid an explicit barrier.
		 */
        //  ep_poll_callback()              ,
		//             TASK_INTERRUPTIBLE   。
		__set_current_state(TASK_INTERRUPTIBLE);

		/*
		 * Do the final check under the lock. ep_scan_ready_list()
		 * plays with two lists (->rdllist and ->ovflist) and there
		 * is always a race when both lists are empty for short
		 * period of time although events are pending, so lock is
		 * important.
		 */
		eavail = ep_events_available(ep);
		if (!eavail) {
     
			if (signal_pending(current))
				res = -EINTR;
			else
				__add_wait_queue_exclusive(&ep->wq, &wait);
		}
		write_unlock_irq(&ep->lock);

		if (eavail || res)
			break;

		if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS)) {
     
			timed_out = 1;
			break;
		}

		/* We were woken up, thus go and try to harvest some events */
		eavail = 1;

	} while (0);
	//  
	__set_current_state(TASK_RUNNING);

	if (!list_empty_careful(&wait.entry)) {
     
		write_lock_irq(&ep->lock);
		__remove_wait_queue(&ep->wq, &wait);
		write_unlock_irq(&ep->lock);
	}

send_events:
	if (fatal_signal_pending(current)) {
     
		/*
		 * Always short-circuit for fatal signals to allow
		 * threads to make a timely exit without the chance of
		 * finding more events available and fetching
		 * repeatedly.
		 */
		res = -EINTR;
	}
	/*
	 * Try to transfer events to user space. In case we get 0 events and
	 * there's still timeout left over, we go trying again in search of
	 * more luck.
	 */
    /*       ,  event  ,        copy      ... */
	if (!res && eavail &&
	    !(res = ep_send_events(ep, events, maxevents)) && !timed_out)
		goto fetch_events;

	return res;
}
ep_send_events()関数は、ユーザが入力したメモリをep_send_events_data構造に単純にカプセル化し、ep_scan_ready_list()を呼び出して、準備キュー内のイベントをユーザ空間のメモリに入力する.ユーザ空間はこの結果にアクセスし,処理を行う.
static int ep_send_events(struct eventpoll *ep,
			  struct epoll_event __user *events, int maxevents)
{
     
	struct ep_send_events_data esed;

	esed.maxevents = maxevents;
	esed.events = events;

	ep_scan_ready_list(ep, ep_send_events_proc, &esed, 0, false);
	return esed.res;
}
static __poll_t ep_send_events_proc(struct eventpoll *ep, struct list_head *head,
			       void *priv)
{
     
	struct ep_send_events_data *esed = priv;
	__poll_t revents;
	struct epitem *epi, *tmp;
	struct epoll_event __user *uevent = esed->events;
	struct wakeup_source *ws;
	poll_table pt;

	init_poll_funcptr(&pt, NULL);
	esed->res = 0;

	/*
	 * We can loop without lock because we are passed a task private list.
	 * Items cannot vanish during the loop because ep_scan_ready_list() is
	 * holding "mtx" during this call.
	 */
	lockdep_assert_held(&ep->mtx);

	list_for_each_entry_safe(epi, tmp, head, rdllink) {
     
		if (esed->res >= esed->maxevents)
			break;

		/*
		 * Activate ep->ws before deactivating epi->ws to prevent
		 * triggering auto-suspend here (in case we reactive epi->ws
		 * below).
		 *
		 * This could be rearranged to delay the deactivation of epi->ws
		 * instead, but then epi->ws would temporarily be out of sync
		 * with ep_is_linked().
		 */
		ws = ep_wakeup_source(epi);
		if (ws) {
     
			if (ws->active)
				__pm_stay_awake(ep->ws);
			__pm_relax(ws);
		}

		list_del_init(&epi->rdllink);

		/*
		 * If the event mask intersect the caller-requested one,
		 * deliver the event to userspace. Again, ep_scan_ready_list()
		 * is holding ep->mtx, so no operations coming from userspace
		 * can change the item.
		 */
		revents = ep_item_poll(epi, &pt, 1);
		if (!revents)
			continue;
		//             copy     
		if (__put_user(revents, &uevent->events) ||
		    __put_user(epi->event.data, &uevent->data)) {
     
            //     epi     ready  
			list_add(&epi->rdllink, head);
			ep_pm_stay_awake(epi);
			if (!esed->res)
				esed->res = -EFAULT;
			return 0;
		}
		esed->res++;
		uevent++;
		if (epi->event.events & EPOLLONESHOT)
			epi->event.events &= EP_PRIVATE_BITS;
		else if (!(epi->event.events & EPOLLET)) {
     
			/*
			 * If this file has been added with Level
			 * Trigger mode, we need to insert back inside
			 * the ready list, so that the next call to
			 * epoll_wait() will check again the events
			 * availability. At this point, no one can insert
			 * into ep->rdllist besides us. The epoll_ctl()
			 * callers are locked out by
			 * ep_scan_ready_list() holding "mtx" and the
			 * poll callback will queue them in ep->ovflist.
			 */
			list_add_tail(&epi->rdllink, &ep->rdllist);
			ep_pm_stay_awake(epi);
		}
	}

	return 0;
}
__put_userを見れば分かるように、カーネルコピーデータからユーザ空間に使用されています.put_user関数は、いわゆる共有メモリとは少しも関係ありませんが、現在ブログには多くのエラーがありますので、修正してください.
三、まとめ
私のレベルと時間が限られていることをお許しください.今回epollのソースコードを読むのは、カーネルとユーザー状態のデータコピーの使用方法がネット上で議論されていることから始まったので、epollのソースコードを探して大雑把に読んで、後で時間を割いて精読しますが、今回はepollのソースコードを大雑把に読んだのですが、収穫も多く、次に簡単にまとめてみます~(このまとめは自分でソースコードを見て得たものもあれば、ネットから集めた資料もありますが、間違っていたら教えてください)
epoll_create
  • epoll_createがパラメータを入力する場合、パラメータが0より大きいことを保証すればよいが、このパラメータの場合は役に立たない
  • 初期化待ち行列と初期化準備チェーンテーブル、および初期化赤黒ツリーのヘッダノード
  • は、eventpoll構造を割り当て、初期化動作を行う.

  • epoll_ctl
  • epoll_event構造はカーネル空間にコピーする、加えられたfdがpoll接合(epoll,poll,selectI/O多重化はpoll動作をサポートする必要がある)をサポートするか否かを判断する.
  • ep = f.file->private_data; event_を取得pollオブジェクト;
  • は、opによりイベントの修正、追加、削除操作
  • を判断する.
  • まずeventpoll構造の赤黒ツリーで対応するfdがすでに存在するかどうかを検索し、見つからない場合は挿入操作をサポートします.そうしないと、重複するエラーを報告し、修正、削除操作があります.
  • 挿入操作時にfdに対応するepitem構造が作成され、関連メンバーが初期化され、poll_が呼び出されるように指定されます.wait時のコールバック関数は、データ準備完了時にプロセスを起動するために使用され、(その内部では、デバイスの待機キューを初期化し、そのプロセスを待機キューに登録する)このステップを完了し、
  • .
  • epitemはこのsocketに関連しており、状態が変化するとep_を通過します.poll_callback()が知らせに来た.
  • は、登録操作を完了するために、追加されたfdのfileoperation->poll関数(最後にpoll_wait操作を呼び出す)を最後に呼び出し、epitem構造を赤黒ツリーに追加する.

  • epoll_wait
  • eventpollオブジェクトのチェーンテーブルが空であるか、操作が必要であるかを判断する.待ち行列を初期化し、自分を掛け、自分のプロセス状態を設定する
  • が睡眠可能な状態である.信号が来たかどうかを判断し(あると中断して目が覚める)、なければschedule_を呼び出すtimeout睡眠、
  • タイムアウトまたは起動された場合、まず自分が初期化する待機キューから削除し、その後、リソースのユーザー空間へのコピーを開始する
  • .
  • コピーリソースは、まず準備イベントチェーンテーブルを中間チェーンテーブルに移行し、次にユーザ空間に1つずつ遍歴コピーし、水平トリガであるか否かを1つずつ判断し、そうであれば再び準備チェーンテーブル
  • に挿入する.
    ユーザ状態とカーネル状態のデータコピー方式
  • ユーザ状態はカーネル状態にデータをコピーし、関数を呼び出した:copy_from_user
  • カーネル状態データがユーザ状態にコピーされ、関数が呼び出される:__put_user
  • ここで注意して、多くのブログの上でデータをコピーして使うのは共有メモリで、間違いで、くれぐれも信じないでください~~~~
    ETとLTモードの異なる原理
    else if (!(epi->event.events & EPOLLET)) {
         
     
    				/*
    				* If this file has been added with Level
    				* Trigger mode, we need to insert back inside
    				* the ready list, so that the next call to
    				* epoll_wait() will check again the events
    				* availability. At this point, no one can insert
    				* into ep->rdllist besides us. The epoll_ctl()
    				* callers are locked out by
    				* ep_scan_ready_list() holding "mtx" and the
    				* poll callback will queue them in ep->ovflist.
    				*/
    				list_add_tail(&epi->rdllink, &ep->rdllist);
    				ep_pm_stay_awake(epi);
    			}
    

    ここではイベントタイプにEPOLLETビットが含まれているかどうかを判断し、含まれていない場合はそのイベントに対応するepitemオブジェクトをepollのrdllistチェーンテーブルに再追加し、ユーザ状態プログラムは次回epoll_を呼び出すwait()が戻るとまたこのepitemを取得できます.次のepollまで待ってwaitすると、すぐに戻り、ユーザースペースに通知されます.
    epollはなぜ効率的なのか(selectより)
    ソース:https://www.cnblogs.com/apprentice89/p/3234677.html
  • は、上記の呼び出し方式のみからepollselect/pollの利点を見ることができる:select/pollは、呼び出すたびに監視するすべてのfdをselect/pollシステム呼び出しに伝達する(これは、呼び出すたびにfdリストをユーザ状態からカーネル状態にコピーし、fdの数が多い場合、これは低効率になることを意味する).epoll_を呼び出すたびにwaitの場合(select/pollを呼び出すのと同じ役割を果たす)、fdリストをカーネルに渡す必要はありません.epoll_ctlでは、監視が必要なfdがカーネルに通知される(epoll_ctlは、毎回すべてのfdをコピーする必要はなく、インクリメンタル操作のみを行う必要がある).だから、epoll_を呼び出してcreateの後、カーネルはカーネル状態で監視するfdを格納するデータ構造の準備を開始した.毎回epoll_ctlはこのデータ構造を簡単に維持するだけです.
  • また、カーネルはslabメカニズムを使用し、epollに高速なデータ構造を提供しています.カーネルでは、すべてファイルがあります.従って、epollは、上述した監視されたfdを格納するためのファイルシステムをカーネルに登録する.epollを呼び出すとcreateの場合、この仮想epollファイルシステムにfileノードが作成されます.もちろんこのfileは普通のファイルではなく、epollにしかサービスしません.epollは、カーネルによって初期化されると(オペレーティングシステムが起動する)、同時にepoll独自のカーネル高速cache領域を開き、監視したいfdを配置するために使用されます.これらのfdは、カーネルcacheに赤と黒の木の形で保存され、迅速な検索、挿入、削除をサポートします.このカーネル高速cache領域は、連続した物理メモリページを作成し、上にslabレイヤを作成することです.簡単に言えば、物理的に希望するsizeのメモリオブジェクトを割り当て、使用するたびに空き割り当てられたオブジェクトを使用します.
  • epollの3つ目の利点は、epoll_を呼び出すとctlが100万個のfdを詰め込むとepoll_waitは依然として速く戻ることができ、イベントが発生したfdを有効にユーザーに与えることができます.epollを呼び出しているからですcreateの時、カーネルは私たちにepollファイルシステムの中でfileノードを建てて、カーネルcacheの中で赤い黒い木を建てて後でepoll_を保存しますctlから送信されたfdに加えて、epoll_wait呼び出しの場合は,このリストチェーンテーブルにデータがあるかどうかを観察するだけでよい.データがあれば返し、データがなければsleep、timeout時間になったらチェーンテーブルにデータがなくても返します.だから、epoll_waitは非常に効率的です.また、通常、百万計のfdを監視しても、一度に少量の準備完了fdしか返さないことが多いので、epoll_waitはカーネル状態copyの少量のfdからユーザ状態に至るだけである.では、この準備リストチェーンテーブルはどのようにメンテナンスされていますか?epoll_を実行するとctlの場合、fdをepollファイルシステムのfileオブジェクトに対応する赤と黒のツリーに置くほか、カーネル割り込みハンドラにコールバック関数を登録し、このfdが割り込んだら、準備リストチェーンテーブルに入れるようカーネルに伝える.したがって、1つのfd(例えばsocket)にデータが到着すると、カーネルはデバイス(例えばネットワークカード)上のデータcopyをカーネルに挿入した後、fd(socket)を準備リストチェーンテーブルに挿入する.

  • 四、過去のすばらしいまとめ
    GDBマルチスレッドの旅
    肝臓!ダイナミックプランニング
    C++ロック使用上の注意
    心血を注いだ再帰
    ***
    公衆番号-バックグラウンドサーバーの開発に注目してください.もっと素晴らしいので、見に来てください.