epoll源碼剖析

(主要基於Linux-2.6.11.12版本進行分析。)


1. 主要數據結構

struct eventpoll {
	/* Protect the this structure access */
	rwlock_t lock;

	/*
	 * This semaphore is used to ensure that files are not removed
	 * while epoll is using them. This is read-held during the event
	 * collection loop and it is write-held during the file cleanup
	 * path, the epoll file exit code and the ctl operations.
	 */
	struct rw_semaphore sem;

	/* Wait queue used by sys_epoll_wait() */
	wait_queue_head_t wq;

	/* Wait queue used by file->poll() */
	wait_queue_head_t poll_wait;

	/* List of ready file descriptors */
	struct list_head rdllist;

	/* RB-Tree root used to store monitored fd structs */
	struct rb_root rbr;
};
struct epitem {
	/* RB-Tree node used to link this structure to the eventpoll rb-tree */
	struct rb_node rbn;

	/* List header used to link this structure to the eventpoll ready list */
	struct list_head rdllink;

	/* The file descriptor information this item refers to */
	struct epoll_filefd ffd;

	/* Number of active wait queue attached to poll operations */
	int nwait;

	/* List containing poll wait queues */
	struct list_head pwqlist;

	/* The "container" of this item */
	struct eventpoll *ep;

	/* The structure that describe the interested events and the source fd */
	struct epoll_event event;

	/*
	 * Used to keep track of the usage count of the structure. This avoids
	 * that the structure will desappear from underneath our processing.
	 */
	atomic_t usecnt;

	/* List header used to link this item to the "struct file" items list */
	struct list_head fllink;

	/* List header used to link the item to the transfer list */
	struct list_head txlink;

	/*
	 * This is used during the collection/transfer of events to userspace
	 * to pin items empty events set.
	 */
	unsigned int revents;
};


struct eppoll_entry {
	/* List header used to link this structure to the "struct epitem" */
	struct list_head llink;

	/* The "base" pointer is set to the container "struct epitem" */
	void *base;

	/*
	 * Wait queue item that will be linked to the target file wait
	 * queue head.
	 */
	wait_queue_t wait;

	/* The wait queue head that linked the "wait" wait queue item */
	wait_queue_head_t *whead;
};

文件系統結構

/**
 * 對內核支持的每一種文件系統,存在一個這樣的結構對其進行描述。
 */
struct file_system_type {
	/**
	 * 文件系統類型的名稱 
	 */
	const char *name;
	/**
	 * 此文件系統類型的屬性 
	 */
	int fs_flags;
	/**
	 * 函數指針,當安裝此類型的文件系統時,就由VFS調用此例程從設備上將此文件系統的superblock讀入內存中
	 */	
	struct super_block *(*get_sb) (struct file_system_type *, int,
				       const char *, void *);
	/**
	 * 刪除超級塊的方法。
	 */
	void (*kill_sb) (struct super_block *);
	/**
	 * 指向實現文件系統的模塊的指針。
	 */
	struct module *owner;
	/**
	 * 下一個文件系統指針。
	 */
	struct file_system_type * next;
	/**
	 * 具有相同文件系統類型的超級塊對象鏈表的頭。
	 */
	struct list_head fs_supers;
};

基本數據結構關係


2. eventpoll_init()

epoll開始的準備工作由eventpoll_init完成,

static int __init eventpoll_init(void)
{
	int error;

	init_MUTEX(&epsem);

	/* Initialize the structure used to perform safe poll wait head wake ups */
	ep_poll_safewake_init(&psw);

	/* Allocates slab cache used to allocate "struct epitem" items */
	epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),
			0, SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC,
			NULL, NULL);

	/* Allocates slab cache used to allocate "struct eppoll_entry" */
	pwq_cache = kmem_cache_create("eventpoll_pwq",
			sizeof(struct eppoll_entry), 0,
			EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL);

	/*
	 * Register the virtual file system that will be the source of inodes
	 * for the eventpoll files
	 */
	error = register_filesystem(&eventpoll_fs_type);
	if (error)
		goto epanic;

	/* Mount the above commented virtual file system */
	eventpoll_mnt = kern_mount(&eventpoll_fs_type);
	error = PTR_ERR(eventpoll_mnt);
	if (IS_ERR(eventpoll_mnt))
		goto epanic;

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: successfully initialized.\n",
			current));
	return 0;

epanic:
	panic("eventpoll_init() failed\n");
}

2.1 kmem_cache_create()

	/* Allocates slab cache used to allocate "struct epitem" items */
	epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),
			0, SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC,
			NULL, NULL);

	/* Allocates slab cache used to allocate "struct eppoll_entry" */
	pwq_cache = kmem_cache_create("eventpoll_pwq",
			sizeof(struct eppoll_entry), 0,
			EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL);

    該函數是slab分配器接口,即創建一個新的高速緩存——內存池。數據結構類型爲struct epitem和struct epoll_entry。

    epoll在被內核初始化時(操作系統啓動),同時會開闢出epoll自己的內核告訴cache區,用於安置每一個我們想監控的socket,這些socket會以紅黑樹的形式保存在內核cache張總,以支持快速的查找、插入、刪除。

    這個內核高速緩衝區,就是建立連續的物理內存頁,然後在之上建立slab層,簡單地說,就是物理上分配好你想要的size大小的內存對象,每次使用時都是使用空閒的已分配好的對象。

2.2 register_filesystem()

    註冊文件系統,將相應的file_system_type加入到鏈表中。

error = register_filesystem(&eventpoll_fs_type);

    在內核中,一切皆文件。所以,epoll向內核註冊了一個文件系統,用於存儲上述的被監控socket。

   當調用epoll_create時,就會在這個虛擬的epoll文件系統中創建一個file結點。當然這個file不是普通文件,它只服務於epoll。


3. sys_epoll_create()

asmlinkage long sys_epoll_create(int size)
{
	int error, fd;
	struct inode *inode;
	struct file *file;

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d)\n",
		     current, size));

	/* Sanity check on the size parameter */
	error = -EINVAL;
	if (size <= 0)
		goto eexit_1;

	/*
	 * Creates all the items needed to setup an eventpoll file. That is,
	 * a file structure, and inode and a free file descriptor.
	 */
	error = ep_getfd(&fd, &inode, &file);
	if (error)
		goto eexit_1;

	/* Setup the file internal data structure ( "struct eventpoll" ) */
	error = ep_file_init(file);
	if (error)
		goto eexit_2;


	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d) = %d\n",
		     current, size, fd));

	return fd;

eexit_2:
	sys_close(fd);
eexit_1:
	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d) = %d\n",
		     current, size, error));
	return error;
}

    epoll極其高效的原因:

    由於在調用epoll_create時,內核除了幫我們在epoll文件系統中創建了個file結點,在內核cache裏建了一個紅黑樹用於存儲以後epoll_ctl傳來的socket外,還會再建立一個list鏈表,用於存儲準備就緒的事件,當epoll_wait調用時,僅僅觀察這個list鏈表有沒有數據即可。有數據就返回,沒有就sleep,等到timeout時間到後,即使鏈表沒有數據也返回。

所以,epoll_wait非常高效。

3.1 ep_getfd()

     在第一次調用epoll_create時,是要創建新的inode、新的file、新的fd。

static int ep_getfd(int *efd, struct inode **einode, struct file **efile)
{
	struct qstr this;
	char name[32];
	struct dentry *dentry;
	struct inode *inode;
	struct file *file;
	int error, fd;

	/* Get an ready to use file */
	error = -ENFILE;
	file = get_empty_filp();
	if (!file)
		goto eexit_1;

	/* Allocates an inode from the eventpoll file system */
	inode = ep_eventpoll_inode();
	error = PTR_ERR(inode);
	if (IS_ERR(inode))
		goto eexit_2;

	/* Allocates a free descriptor to plug the file onto */
	error = get_unused_fd();
	if (error < 0)
		goto eexit_3;
	fd = error;

	/*
	 * Link the inode to a directory entry by creating a unique name
	 * using the inode number.
	 */
	error = -ENOMEM;
	sprintf(name, "[%lu]", inode->i_ino);
	this.name = name;
	this.len = strlen(name);
	this.hash = inode->i_ino;
	dentry = d_alloc(eventpoll_mnt->mnt_sb->s_root, &this);
	if (!dentry)
		goto eexit_4;
	dentry->d_op = &eventpollfs_dentry_operations;
	d_add(dentry, inode);
	file->f_vfsmnt = mntget(eventpoll_mnt);
	file->f_dentry = dentry;
	file->f_mapping = inode->i_mapping;

	file->f_pos = 0;
	file->f_flags = O_RDONLY;
	file->f_op = &eventpoll_fops;
	file->f_mode = FMODE_READ;
	file->f_version = 0;
	file->private_data = NULL;

	/* Install the new setup file into the allocated fd. */
	fd_install(fd, file);

	*efd = fd;
	*einode = inode;
	*efile = file;
	return 0;

eexit_4:
	put_unused_fd(fd);
eexit_3:
	iput(inode);
eexit_2:
	put_filp(file);
eexit_1:
	return error;
}

3.2 ep_file_init()

    設置文件內部數據結構,即struct eventpoll

static int ep_file_init(struct file *file)
{
	struct eventpoll *ep;

	if (!(ep = kmalloc(sizeof(struct eventpoll), GFP_KERNEL)))
		return -ENOMEM;

	memset(ep, 0, sizeof(*ep));
	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;

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_file_init() ep=%p\n",
		     current, ep));
	return 0;
}
    file->private_data = ep,這裏進行初始化,爲了可以在函數sys_epoll_ctl直接獲取eventpoll文件中的私有數據。


4. sys_epoll_ctl()

    在函數sys_epoll_ctl中,如果增加socket句柄,則檢查在紅黑樹中是否存在,存在就立即返回;不存在則添加到樹幹上,然後向內核註冊回調函數,用於當中斷事件來臨時向準備就緒鏈表中插入數據。

asmlinkage long
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;
	struct epoll_event epds;

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p)\n",
		     current, epfd, op, fd, event));

	error = -EFAULT;
	if (EP_OP_HASH_EVENT(op) &&
	    copy_from_user(&epds, event, sizeof(struct epoll_event)))
		goto eexit_1;

	/* Get the "struct file *" for the eventpoll file */
	error = -EBADF;
	file = fget(epfd);
	if (!file)
		goto eexit_1;

	/* Get the "struct file *" for the target file */
	tfile = fget(fd);
	if (!tfile)
		goto eexit_2;

	/* The target file descriptor must support poll */
	error = -EPERM;
	if (!tfile->f_op || !tfile->f_op->poll)
		goto eexit_3;

	/*
	 * We have to check that the file structure underneath the file descriptor
	 * the user passed to us _is_ an eventpoll file. And also we do not permit
	 * adding an epoll file descriptor inside itself.
	 */
	error = -EINVAL;
	if (file == tfile || !IS_FILE_EPOLL(file))
		goto eexit_3;

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

	down_write(&ep->sem);

	/* Try to lookup the file inside our hash table */
	epi = ep_find(ep, tfile, fd);

	error = -EINVAL;
	switch (op) {
	case EPOLL_CTL_ADD:
		if (!epi) {
			epds.events |= POLLERR | POLLHUP;

			error = ep_insert(ep, &epds, tfile, fd);
		} 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) {
			epds.events |= POLLERR | POLLHUP;
			error = ep_modify(ep, epi, &epds);
		} else
			error = -ENOENT;
		break;
	}

	/*
	 * The function ep_find() increments the usage count of the structure
	 * so, if this is not NULL, we need to release it.
	 */
	if (epi)
		ep_release_epitem(epi);

	up_write(&ep->sem);

eexit_3:
	fput(tfile);
eexit_2:
	fput(file);
eexit_1:
	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p) = %d\n",
		     current, epfd, op, fd, event, error));

	return error;
}

4.1 ep = file->private_data;

    獲取eventpoll文件中的私有數據,該數據在event_create中創建。


4.2 ep_find()

     在eventpoll中存儲文件描述符信息的紅黑樹中查找指定fd對應的epitem實例。

    一個新創建的epoll文件帶有一個struct eventpoll結構,這個結構再掛一個紅黑樹,而這個紅黑樹就是每次epoll_ctl時fd存放的地方。

    ep_find的實現,是struct eventpoll的rbr成員(strut rb_root),原來就是一個紅黑樹的根。而紅黑樹上掛的是struct epitem。


4.3 ep_insert()

    首先,進行ep_find,

如果找到了struct epitem而用戶操作是ADD,那麼返回-EEXIST;

如果是DEL,則ep_remove;

如果找不到struct epitem而用戶操作是ADD,就ep_insert創建並插入一個。

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_pqueue epq;

	error = -ENOMEM;
	if (!(epi = EPI_MEM_ALLOC()))
		goto eexit_1;

	/* Item initialization follow here ... */
	EP_RB_INITNODE(&epi->rbn);
	INIT_LIST_HEAD(&epi->rdllink);
	INIT_LIST_HEAD(&epi->fllink);
	INIT_LIST_HEAD(&epi->txlink);
	INIT_LIST_HEAD(&epi->pwqlist);
	epi->ep = ep;
	EP_SET_FFD(&epi->ffd, tfile, fd);
	epi->event = *event;
	atomic_set(&epi->usecnt, 1);
	epi->nwait = 0;

	/* Initialize the poll table using the queue callback */
	epq.epi = epi;
	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.
	 */
	revents = tfile->f_op->poll(tfile, &epq.pt);/////
	........
		
}


4.3.1 EPI_MEM_ALLOC()

    首先,申請一個epi空間。

2.3.2 進行初始化

EP_RB_INITNODE(&epi->rbn);
	INIT_LIST_HEAD(&epi->rdllink);
	INIT_LIST_HEAD(&epi->fllink);
	INIT_LIST_HEAD(&epi->txlink);
	INIT_LIST_HEAD(&epi->pwqlist);
	epi->ep = ep;

2.3.3 ep_ptable_queue_proc()

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 = PWQ_MEM_ALLOC())) {
		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 */
		epi->nwait = -1;
	}
}

  函數init_waitqueue_func_entry()中定義等待隊列上的喚醒函數爲ep_poll_callback,並對等待隊列進行初始化。

ep_poll_callback()

    把紅黑樹上收到event的epitem(代表每個fd)插入ep->rdlist中,

這樣,當epoll_wait返回時,rdlist裏就都是就緒的fd了。

static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
	int pwake = 0;
	unsigned long flags;
	struct epitem *epi = EP_ITEM_FROM_WAIT(wait);
	struct eventpoll *ep = epi->ep;

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: poll_callback(%p) epi=%p ep=%p\n",
		     current, epi->file, epi, ep));

	write_lock_irqsave(&ep->lock, flags);

	/*
	 * If the event mask does not contain any poll(2) event, we consider the
	 * descriptor to be disabled. This condition is likely the effect of the
	 * EPOLLONESHOT bit that disables the descriptor when an event is received,
	 * until the next EPOLL_CTL_MOD will be issued.
	 */
	if (!(epi->event.events & ~EP_PRIVATE_BITS))
		goto is_disabled;

	/* If this file is already in the ready list we exit soon */
	if (EP_IS_LINKED(&epi->rdllink))
		goto is_linked;

	list_add_tail(&epi->rdllink, &ep->rdllist);

is_linked:
	/*
	 * Wake up ( if active ) both the eventpoll wait list and the ->poll()
	 * wait list.
	 */
	if (waitqueue_active(&ep->wq))
		wake_up(&ep->wq);
	if (waitqueue_active(&ep->poll_wait))
		pwake++;

is_disabled:
	write_unlock_irqrestore(&ep->lock, flags);

	/* We have to call this outside the lock */
	if (pwake)
		ep_poll_safewake(&psw, &ep->poll_wait);

	return 1;
}

EP_PRIVATE_BITS,即宏替換爲(EPOLLONESHOT | EPOLLET).


list_add_tail(&epi->rdlink, &ep->rdlist); 

    epi->rdlink插入到ep->rdlist之前; struct epitem放到放到struct eventpoll的rdlist中去。


4. sys_epoll_wait()

asmlinkage long sys_epoll_wait(int epfd, struct epoll_event __user *events,
			       int maxevents, int timeout)
{
	int error;
	struct file *file;
	struct eventpoll *ep;

	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_wait(%d, %p, %d, %d)\n",
		     current, epfd, events, maxevents, timeout));

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

	/* Verify that the area passed by the user is writeable */
	if ((error = verify_area(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))))
		goto eexit_1;

	/* Get the "struct file *" for the eventpoll file */
	error = -EBADF;
	file = fget(epfd);
	if (!file)
		goto eexit_1;

	/*
	 * 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(file))
		goto eexit_2;

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

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

eexit_2:
	fput(file);
eexit_1:
	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_wait(%d, %p, %d, %d) = %d\n",
		     current, epfd, events, maxevents, timeout, error));

	return error;
}

4.1 maxevents

    事件個數一定大於0,否則返回-EINVAL。

4.2 verify_area()

/**
 * 函數verify_area執行與access_ok宏類似的檢查,雖然它被認爲是陳舊過時的
 * 但是在源代碼中仍然被廣泛使用。
 */
static inline int verify_area(int type, const void __user * addr, unsigned long size)
{
	return access_ok(type,addr,size) ? 0 : -EFAULT;
}

    對系統調用所傳遞地址的檢查是通過access_ok宏實現的。

· 它由兩個分別爲addr和size的參數。

· 該宏檢查addr到addr+size-1之間的地址區間。

4.3 file = fget(epfd)

    獲取epfd對應的file實例。

然後接着調用IS_FILE_EPOLL(file),判斷是否爲eventpoll的file,

即(f)->f_op == &eventpoll_fops.


4.4 ep_poll

這個函數是epoll的核心函數,接下來進行分析。

static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
		   int maxevents, long timeout)
{
	int res, eavail;
	unsigned long flags;
	long jtimeout;
	wait_queue_t wait;

	/*
	 * Calculate the timeout by checking for the "infinite" value ( -1 )
	 * and the overflow condition. The passed timeout is in milliseconds,
	 * that why (t * HZ) / 1000.
	 */
	jtimeout = timeout == -1 || timeout > (MAX_SCHEDULE_TIMEOUT - 1000) / HZ ?
		MAX_SCHEDULE_TIMEOUT: (timeout * HZ + 999) / 1000;

retry:
	write_lock_irqsave(&ep->lock, flags);

	res = 0;
	if (list_empty(&ep->rdllist)) {
		/*
		 * We don't have any available event to return to the caller.
		 * We need to sleep here, and we will be wake up by
		 * ep_poll_callback() when events will become available.
		 */
		init_waitqueue_entry(&wait, current);
		add_wait_queue(&ep->wq, &wait);

		for (;;) {
			/*
			 * We don't want to sleep if the ep_poll_callback() sends us
			 * a wakeup in between. That's why we set the task state
			 * to TASK_INTERRUPTIBLE before doing the checks.
			 */
			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);
		}
		remove_wait_queue(&ep->wq, &wait);

		set_current_state(TASK_RUNNING);
	}

	/* Is it worth to try to dig for events ? */
	eavail = !list_empty(&ep->rdllist);

	write_unlock_irqrestore(&ep->lock, flags);

	/*
	 * 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.
	 */
	if (!res && eavail &&
	    !(res = ep_events_transfer(ep, events, maxevents)) && jtimeout)
		goto retry;

	return res;
}

首先,調用list_empty(&ep->rdlist),判斷ep->rdlist是否爲NULL。

eventpoll下的struct list_head rdlist,雙鏈表中存放着將要通過epoll_wait返回給用戶的滿足條件的事件。

而struct rb_root rbr,是紅黑樹的根結點,樹中存儲所有添加到epoll中的需要監控的事件。

    如果沒有事件到來,不會返回給調用方;

    一直在這裏睡眠,直到事件發生,被ep_poll_callback()喚醒。

init_waitqueue_entry()

    初始化wait_queue_t結構的變量。

add_wait_queue()

    將wait進程插入等待隊列鏈表的第一個位置。


4.4.1 set_current_state()

    for循環中,設置TASK_INTERRUPTIBLE狀態,其原因是:如果ep_poll_callback()發生喚醒,不會去休眠。

4.4.2 signal_pending()

     如果進程描述符所表示的進程有非阻塞的掛起信號,就返回1。否則返回0。
    該函數只是通過檢查進程的TIF_SIGPENDING標誌。

static inline int signal_pending(struct task_struct *p)
{
	return unlikely(test_tsk_thread_flag(p,TIF_SIGPENDING));
}

4.4.3 remove_wait_queue()

    將wait進程從等待隊列鏈表中刪除。

4.4.4 ep_event_transfer()

    把rdlist中的fd拷貝到用戶空間。

static int ep_events_transfer(struct eventpoll *ep,
			      struct epoll_event __user *events, int maxevents)
{
	int eventcnt = 0;
	struct list_head txlist;

	INIT_LIST_HEAD(&txlist);

	/*
	 * We need to lock this because we could be hit by
	 * eventpoll_release_file() and epoll_ctl(EPOLL_CTL_DEL).
	 */
	down_read(&ep->sem);

	/* Collect/extract ready items */
	if (ep_collect_ready_items(ep, &txlist, maxevents) > 0) {
		/* Build result set in userspace */
		eventcnt = ep_send_events(ep, &txlist, events);

		/* Reinject ready items into the ready list */
		ep_reinject_items(ep, &txlist);
	}

	up_read(&ep->sem);

	return eventcnt;
}

4.4.4.1 ep_collect_ready_items()

    把rdlist裏的fd挪到txlist中(挪完後rdlist就空了)。

static int ep_collect_ready_items(struct eventpoll *ep, struct list_head *txlist, int maxevents)
{
	int nepi;
	unsigned long flags;
	struct list_head *lsthead = &ep->rdllist, *lnk;
	struct epitem *epi;

	write_lock_irqsave(&ep->lock, flags);

	for (nepi = 0, lnk = lsthead->next; lnk != lsthead && nepi < maxevents;) {
		epi = list_entry(lnk, struct epitem, rdllink);

		lnk = lnk->next;

		/* If this file is already in the ready list we exit soon */
		if (!EP_IS_LINKED(&epi->txlink)) {
			/*
			 * This is initialized in this way so that the default
			 * behaviour of the reinjecting code will be to push back
			 * the item inside the ready list.
			 */
			epi->revents = epi->event.events;

			/* Link the ready item into the transfer list */
			list_add(&epi->txlink, txlist);
			nepi++;

			/*
			 * Unlink the item from the ready list.
			 */
			EP_LIST_DEL(&epi->rdllink);
		}
	}

	write_unlock_irqrestore(&ep->lock, flags);

	return nepi;
}

4.4.4.2 ep_send_events()

    把txlist中的fd拷貝到用戶空間。

static int ep_send_events(struct eventpoll *ep, struct list_head *txlist,
			  struct epoll_event __user *events)
{
	int eventcnt = 0;
	unsigned int revents;
	struct list_head *lnk;
	struct epitem *epi;

	/*
	 * We can loop without lock because this is a task private list.
	 * The test done during the collection loop will guarantee us that
	 * another task will not try to collect this file. Also, items
	 * cannot vanish during the loop because we are holding "sem".
	 */
	list_for_each(lnk, txlist) {
		epi = list_entry(lnk, struct epitem, txlink);

		/*
		 * Get the ready file event set. We can safely use the file
		 * because we are holding the "sem" in read and this will
		 * guarantee that both the file and the item will not vanish.
		 */
		revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL);

		/*
		 * Set the return event set for the current file descriptor.
		 * Note that only the task task was successfully able to link
		 * the item to its "txlist" will write this field.
		 */
		epi->revents = revents & epi->event.events;

		if (epi->revents) {
			if (__put_user(epi->revents,
				       &events[eventcnt].events) ||
			    __put_user(epi->event.data,
				       &events[eventcnt].data))
				return -EFAULT;
			if (epi->event.events & EPOLLONESHOT)
				epi->event.events &= EP_PRIVATE_BITS;
			eventcnt++;
		}
	}
	return eventcnt;
}

在ep_send_events()中,

    revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL);

調用函數scull_p_poll,也就是其中的poll_wait()函數,


POLL方法是poll、epoll和select這三個系統調用的後端實現。可用來查詢某個或多個文件描述符上的讀取或寫入是否會被阻塞。

poll方式返回一個位掩碼mask,用來指出非阻塞的讀取或寫入是否可能。並且會向內核提供將調用進程置於休眠狀態直到IO變爲可能時的信息,並且驅動程序中將POLL方法定義爲NULL,則設備會被認爲既可讀也可寫,並且不會阻塞。

設備先要把current(當前進程)掛在inq和outq兩個隊列上(這個“掛”操作是wait回調函數指針做的),然後等設備喚醒,喚醒後就能通過mask拿到事件掩碼了。

這裏的mask參數就是負責事件掩碼。


4.4.4.3 ep_reinject_items()

    把一部分fd從txlist裏“返還”給rdlist以便下次還能從rdlist裏發現它。

static void ep_reinject_items(struct eventpoll *ep, struct list_head *txlist)
{
	int ricnt = 0, pwake = 0;
	unsigned long flags;
	struct epitem *epi;

	write_lock_irqsave(&ep->lock, flags);

	while (!list_empty(txlist)) {
		epi = list_entry(txlist->next, struct epitem, txlink);

		/* Unlink the current item from the transfer list */
		EP_LIST_DEL(&epi->txlink);

		/*
		 * If the item is no more linked to the interest set, we don't
		 * have to push it inside the ready list because the following
		 * ep_release_epitem() is going to drop it. Also, if the current
		 * item is set to have an Edge Triggered behaviour, we don't have
		 * to push it back either.
		 */
		if (EP_RB_LINKED(&epi->rbn) && !(epi->event.events & EPOLLET) &&
		    (epi->revents & epi->event.events) && !EP_IS_LINKED(&epi->rdllink)) {
			list_add_tail(&epi->rdllink, &ep->rdllist);
			ricnt++;
		}
	}

	if (ricnt) {
		/*
		 * Wake up ( if active ) both the eventpoll wait list and the ->poll()
		 * wait list.
		 */
		if (waitqueue_active(&ep->wq))
			wake_up(&ep->wq);
		if (waitqueue_active(&ep->poll_wait))
			pwake++;
	}

	write_unlock_irqrestore(&ep->lock, flags);

	/* We have to call this outside the lock */
	if (pwake)
		ep_poll_safewake(&psw, &ep->poll_wait);
}

函數中進行判斷時,

    EP_RB_LINKED(epi->rbn) && !(epi->event.events & EPOLLET) && (epi->revents & epi->event.events) && IEP_IS_LINKED(&epi->rdlink),

    是哪些“沒有標上EPOLLET”(標紅代碼)且“事件被關注”(標藍代碼)的fd重新被放回了rdlist。

LT模式下,只要一個句柄上事件一次沒有處理完,會在以後調用epoll_wait時此次返回這個句柄,從txlist拷貝到用戶空間後,會返還給rdlist。

ET模式下,僅在第一次返回。


--------------------------------------------------------------------

總結

  1. select和poll每次調用這些函數的時候都需要將監控的fd和需要監控的事件從用戶空間拷貝到內核空間,非常影響效率。而epoll就是自己保存用戶空間拷入的fd和需要監控的事件,只需在調用epoll_ctl的時候就把所有的fd和需要監控的事件只進行一次從用戶空間到內核空間的拷貝。

  2. poll和select類似,每次調用都返回整個用戶註冊的事件集合(包括就緒的和未就緒的),應用程序索引就緒文件描述符的時間複雜度爲O(n)。而epoll是在內核中維護一個事件表,epoll_wait的events參數返回就緒的事件,時間複雜度爲O(1).

 3. poll和epoll_wait分別用nfds和maxevents參數指定最多監聽多少個文件描述符和事件個數,即65535(cat/proc/sys/fs/file-max)。而select允許監聽的最大文件描述符個數爲1024.

併發支持完美,不會隨着socket的增加而降低效率,也不用在內核空間和用戶空間之間做無效的copy操作。

 4. poll只能工作在相對低效的LT模式(電平觸發),而epoll支持LT和ET模式。

 ET 邊沿觸發:只觸發一次,無論緩衝區中是否還有剩餘數據,直到有新的數據到達纔會被觸發,再去讀取緩衝區裏面的數據。

 LT 水平觸發(默認): LT(level triggered)是缺省的工作方式,並且同時支持block和no-block socket,每次緩衝區都有數據都要觸發。

    epoll可以監控管道文件,任意文件,不僅僅是socket文件.


 5. poll採用輪詢方式,即每次調用都要掃描整個註冊文件描述符集合,並將其中就緒的文件描述符返回個用戶,因此檢測就緒事件的時間複雜度是O(n)。epoll則採用回調方式。內核檢測到就緒的文件描述符,將觸發回調函數,回調函數將該文件描述符上對應的事件插入內核就緒事件隊列。內核最後將該就緒事件隊列的內容拷貝到用戶空間。時間複雜度爲O(1).

 6. 能處理EPOLLONESHOT事件


----------------------------------

應用場景

1. epoll_wait適用於I/O密集型,即連接數量多,但活動連接較少的情況。因爲epoll則採用回調方式。內核檢測到就緒的文件描述符,將觸發回調函數,回調函數將該文件描述符上對應的事件插入內核就緒事件隊列。內核最後將該就緒事件隊列的內容拷貝到用戶空間。

    但是,當活動連接較多時,epoll_wait的效率未必比select和poll高,因爲此時回調函數被觸發的過於頻繁。


 2. 併發支持完美,不會隨着socket的增加而降低效率,也不用在內核空間和用戶空間之間做無效的copy操作。













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