epoll_create & epoll_ctl & epoll_wait Kernel实现 -- Kernel 3.0.8

 1. 相关数据结构

#define EPOLLIN          0x00000001
#define EPOLLPRI         0x00000002
#define EPOLLOUT         0x00000004
#define EPOLLERR         0x00000008
#define EPOLLHUP         0x00000010
#define EPOLLRDNORM      0x00000040
#define EPOLLRDBAND      0x00000080
#define EPOLLWRNORM      0x00000100
#define EPOLLWRBAND      0x00000200
#define EPOLLMSG         0x00000400
#define EPOLLET          0x80000000

#define EPOLL_CTL_ADD    1
#define EPOLL_CTL_DEL    2
#define EPOLL_CTL_MOD    3

typedef union epoll_data 
{
    void *ptr;
    int fd;
    unsigned int u32;
    unsigned long long u64;
} epoll_data_t;

struct epoll_event 
{
    unsigned int events;  //如EPOLLIN、EPOLLOUT
    epoll_data_t data;
};

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //op为:EPOLL_CTL_ADD、EPOLL_CTL_DEL
int epoll_wait(int epfd, struct epoll_event *events, int max, int timeout);

常用的事件类型:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;

2. epoll_create

在Bionic中的实现见:epoll_create.S,它直接进行系统调用,系统调用表见kernel:src/include/linux/syscalls.h,根据规则在Kernel中Search字符串“epoll_create”,就能找到对应的实现函数:SYSCALL_DEFINE1(epoll_create, int, size) <在Kernel的实现在文件eventpoll.c中>

SYSCALL_DEFINE1(epoll_create, int, size)
{
	if (size <= 0)
		return -EINVAL;

	return sys_epoll_create1(0);
}

看仔细了, 只要传入的参数大于0即可,它并没有别的用处。此函数功能为:

1)从当前进程的files中寻找一个空闲的fd(文件句柄)

2)创建一个struct file实例(其fops为eventpoll_fops,priv为刚为其创建的struct eventpoll对象)

3)当前进程的files->fdt->fd[fd]为新创建的struct file实例

4)返回给用户态的当然是一个fd(文件句柄)

 

3. epoll_ctl

用户态:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

Kernel态:SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,  struct epoll_event __user *, event),它主要实现eventpoll文件的控制接口,用于插入、删除、修改文件集中的文件描述符。其代码处理流程为:<epoll_event用于描述感兴趣的事件和源fd>

1)获取eventpoll文件句柄epfd对应的文件实例(struct file)

2)获取目标文件句柄fd对应的文件实例(struct file)

3)确保目标文件句柄fd对应的文件实例(struct file)支持poll操作(即:(tfile->f_op &&  tfile->f_op->poll))

4)把eventpoll文件的私有数据转换为eventpoll对象,eventpoll红黑树的key为:epoll_filefd

      struct epoll_filefd {
            struct file *file;
            int fd;
      };

5)在红黑树中查找要操作的目标fd和fie实例,从而获取一个struct epitem,红黑树中节点的数据结构

6)根据op,进行对应的INSERT、REMOVE或MODIFY操作,下面说说INSERT(当操作为EPOLL_CTL_ADD时)

7)调用int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd)

7.1)创建struct epitem对象(每一个文件描述符增加到eventpoll接口必须有一个epitem对象,且此对象被插入eventpoll的红黑树中)

7.2)初始化epi中的三个链表,保存eventpoll,目标fd,目标文件实例,epoll_event..

7.3)把回调函数注册给目标文件的f_op->poll,相关代码如下:

         struct ep_pqueue epq;
         epq.epi = epi;
         init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
         revents = tfile->f_op->poll(tfile, &epq.pt); //详情可参考pipe_poll处理方式,它最终还是调用ep_ptable_queue_proc函数来处理

       ep_ptable_queue_proc:  is used to add our wait queue to the target file wakeup lists
7.4)把此对象插入红黑树中

/*
 * This is the callback that is used to add our wait queue to the
 * target file wakeup lists.
 */
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 {
		/* We have to signal that an error occurred */
		epi->nwait = -1;
	}
}

 

/*
 * This is the callback that is passed to the wait queue wakeup
 * mechanism. It is called by the target file descriptors when they
 * have events to report.
 */
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
   ...
}

这ep_poll_callback如何被执行的呢?

下面以pipe为例,假设上面的是检测从pipe中读取数据,哪么写数据时将调用此函数。

init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
static inline void init_waitqueue_func_entry(wait_queue_t *q,
					wait_queue_func_t func)
{
	q->flags = 0;
	q->private = NULL;
	q->func = func;
}


ep_poll_callback被保存在q->func中。

下面看此q->func的调用流程:

static ssize_t
pipe_write(struct kiocb *iocb, const struct iovec *_iov,
	    unsigned long nr_segs, loff_t ppos)
{ 
        ...
	if (do_wakeup) {
		wake_up_interruptible_sync_poll(&pipe->wait, POLLIN | POLLRDNORM);
		kill_fasync(&pipe->fasync_readers, SIGIO, POLL_IN);
		do_wakeup = 0;
	}
        ...
}

#define wake_up_interruptible_sync_poll(x, m)				\
	__wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))

/**
 * __wake_up_sync_key - wake up threads blocked on a waitqueue.
 */
void __wake_up_sync_key(wait_queue_head_t *q, unsigned int mode,
			int nr_exclusive, void *key)
{
        ...
	__wake_up_common(q, mode, nr_exclusive, wake_flags, key);
        ...

}
/*
 * The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just
 * wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve
 * number) then we wake all the non-exclusive tasks and one exclusive task.
 *
 * There are circumstances in which we can try to wake a task which has already
 * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns
 * zero in this (rare) case, and we handle it by continuing to scan the queue.
 */
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
			int nr_exclusive, int wake_flags, void *key)
{
	wait_queue_t *curr, *next;

	list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
		unsigned flags = curr->flags;

		if (curr->func(curr, mode, wake_flags, key) &&
				(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
			break;
	}
}



总结一下:目标文件以pipefd为例。为新的目标文件生成一个epitem包含 pipe fd和需要监听的事件,并将epitem与函数ep_ptable_queue_proc的地址捆绑成一个ep_pqueue结构,然后用结构中的函数地址字段作为参数执行pipe fd对应的poll函数(pipe_poll),在pipe_poll执行时函数ep_ptable_queue_proc被执行,同时函数体中可以根据传入的函数地址计算偏移来得到epitem指针,函数ep_ptable_queue_proc将epoll回调函数ep_poll_callback函数与epitem指针捆绑成另一个结构eppoll_entry,然后把eppoll_entry中的函数地址生成一个wait_queue_t,插入到目标pipe fd的wait queue中,当pipe由于状态改变而触发激活wait_queue时<在pipe_write中调用wake_up_interruptible_sync_poll(&pipe->wait, POLLIN | POLLRDNORM);它将wake up threads blocked on the waitqueue(pipe->wait)>,包含在队列中的ep_poll_callback函数就会被调用,同时根据其函数地址参数,用偏移量来得到epitem,回调函数在调用时会再执行pipe_poll函数,来明确是不是指定的关注事件发生,若成立则将 epitem插入到eventpoll中的rdlist,并激活在epoll fd上wait的进程,并将事件回传至用户态.这样就能实现对目标fd的事件监听.

 

4. epoll_wait

从epfd中读取epoll_event并保存到events数组中。

用户态:int epoll_wait(int epfd, struct epoll_event *events, int max, int timeout);

Kernel态:SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events, int, maxevents, int, timeout)

1)获取epfd对应的文件实例

2)把eventpoll文件的私有数据转换为eventpoll对象

3)调用int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
     int maxevents, long timeout)获取epoll_event。此函数获取已经准备好的事件,然后把他们保存到调用都提供的events buffer中。

3.1)ep_poll中调用hrtimer来实现其超时功能

3.2)调用int ep_events_available(struct eventpoll *ep)来check是否有事件

3.3)调用int ep_send_events(struct eventpoll *ep,struct epoll_event __user *events, int maxevents)来真正地获取事件,并copy到用户空间的events buffer中

3.3.1)调用ep_scan_ready_list(ep, ep_send_events_proc, &esed);

3.3.2)在回调函数中,获取epoll_event,epoll_event两个域的数据来源如下:

  uevent是用户提供的epoll_event,

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

  if (revents) {
   if (__put_user(revents, &uevent->events) ||
       __put_user(epi->event.data, &uevent->data)) {
    list_add(&epi->rdllink, head);
    return eventcnt ? eventcnt : -EFAULT;
   }

   ...

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

阅读更多
个人分类: Linux Kernel
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭