epoll源碼剖析

epoll多路實現模型相比selecte、poll避免了出現文件描述符越多性能越差的情況,並且省去了大量內核/用戶空間的拷貝和輪詢所有文件描述符的系統消耗。爲了理解epoll實現高併發,本文從源碼瞭解epoll的實現機制。

epoll的用法:

int epoll_create(int size);//創建一個epoll fd
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);//添加事件
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);//監控文件描述符

epoll_create()


創建一系列數據結構並建立的聯繫。

int fd;//文件描述符
struct inode* inode;//內核用該結構在內部表示一個文件
struct file* file;//代表一個打開的文件
struct eventpoll;//epoll的描述符

源碼摘錄

sys_epoll_create(int size);
>>	int error, fd;
	struct inode *inode;
	struct file *file;
	error = ep_getfd(&fd, &inode, &file);
		>>  file = get_empty_filp();//分配一個文件索引節點並初始化
			inode = ep_eventpoll_inode();//從eventpoll文件系統分配一個inode
			dentry = d_alloc(eventpoll_mnt->mnt_sb->s_root, &this);
			d_add(dentry, inode);
			>>	d_instantiate(entry, inode);
				>>	list_add(&entry->d_alias, &inode->i_dentry);//file.inode指向inode
			fd_install(fd, file);
			>>	files->fd[fd] = file;//將文件索引點添加到進程中
			error = ep_file_init(file);//設置文件內部數據結構(“struct eventpoll”)
			>>		memset(ep, 0, sizeof(*ep));//初始化epoll描述符
					rwlock_init(&ep->lock);
					init_rwsem(&ep->sem);
					init_waitqueue_head(&ep->wq);
					init_waitqueue_head(&ep->poll_wait);
					INIT_LIST_HEAD(&ep->rdllist);
					ep->rbr = RB_ROOT;

					file->private_data = ep;//file.private_data指向epoll 描述符
return fd;//epfd
創建struct file、strcut inode 、struct eventpoll等結構體並初始化,建立file與inode的聯繫,task_struct與file之間的聯繫,

file與eventpolld之間的聯繫。


epoll_ctl()


插入、刪除、更改epoll描述符中的內核文件描述符,下面以插入爲例:


源碼摘錄:

sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event __user *event)
>>	int error;
	struct file *file, *tfile;
	struct eventpoll *ep;
	struct epitem *epi;//當前節點
	file = fget(epfd);//根據進程文件描述符獲得文件對象的地址。並增加其引用計數。
	tfile = fget(fd);
	ep = file->private_data;
	epi = ep_find(ep, tfile, fd);//在eventpoll.rbr中查找fd

	ep_insert(ep, &epds, tfile, fd);//給fd註冊事件
	>>		struct epitem *epi;
			struct ep_pqueue epq;
			/*struct ep_pqueue {
					poll_table pt;//poll_queue_proc qproc;
					struct epitem *epi;
					};
			*/
			epi->ep = ep;
			epi->event = *event;
			epq.epi = epi;
			init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
			>>	pt->qproc = qproc;
				/*
				static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt)	//監聽的fd生改變時,指定的調用函數會被調用
				unsigned int (*poll) (struct file *, struct poll_table_struct *);
				*/
			revents = tfile->f_op->poll(tfile, &epq.pt);//回調函數
			ep_rbtree_insert(ep, epi);//添加當前項到eventpoll.rbr
				/* If the file is already "ready" we drop it inside the ready list */
				if ((revents & event->events) && !EP_IS_LINKED(&epi->rdllink)) {
				list_add_tail(&epi->rdllink, &ep->rdllist);//如果當前事件就緒,把他放到就緒隊列

				/* Notify waiting tasks that events are available */
				if (waitqueue_active(&ep->wq))
					wake_up(&ep->wq);
				if (waitqueue_active(&ep->poll_wait))
					pwake++;
				}
將用戶空間的epoll_event拷貝到內核,只拷貝一次;

在eventpoll->rbr中查找fd是否存在;

設置回調函數並且插入節點。


epoll_wait()


向用戶空間拷貝就緒描述符。

源碼摘錄:
sys_epoll_wait()
	struct file *file;
	struct eventpoll *ep;
	file = fget(epfd);
	ep = file->private_data;
	ep_epoll(ep,events,maxevents,timeout);

	>>	if (list_empty(&ep->rdllist)) {//如果就緒隊列爲空
		init_waitqueue_entry(&wait, current);//初始化等待隊列
		add_wait_queue(&ep->wq, &wait);

		for (;;) {
			set_current_state(TASK_INTERRUPTIBLE);
			if (!list_empty(&ep->rdllist) || !jtimeout)//如果超時
				break;
			if (signal_pending(current)) {//如果有信號產生
				res = -EINTR;
				break;
			}

			write_unlock_irqrestore(&ep->lock, flags);
			jtimeout = schedule_timeout(jtimeout);//如果此時回調函數被調用,直接被喚醒
			write_lock_irqsave(&ep->lock, flags);
		}
		>>		if (ep_collect_ready_items(ep, &txlist, maxevents) > 0) {//收集就緒的描述符
				eventcnt = ep_send_events(ep, &txlist, events);
						revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL);
						epi->revents = revents & epi->event.events;//只返回相關的fd
				if (!res && eavail &&!(res = ep_events_transfer(ep, events, maxevents)) && jtimeout)//向用戶空間拷貝


如果epi->event.events 是 ET,epitem就不會在進入到就緒隊列,除非fd再次發生了改變ep_poll_callback被調用
如果是LT,無論是否狀態發生改變都會重新插入就緒隊列,下次調用返回值爲0。


參考資料:

epoll內核源碼詳解+自己總結的流程_技術交流_牛客網


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章