非阻塞io之 epoll内核实现


可以结合这篇文章一起看 eventfd
由于需要实现一个驱动想封装出epoll的设备,在网上找关于epoll的信息,好少所以自己看了内核的代码了解以下epoll机制,做个总结以便自己后面查看。

/*
 * 这个结构存储在`struct file`结构的 "private_data"成员中,也是eventpoll接口的主要数据结构。
 * 访问它受到 wq 内部锁的保护。
 */
struct eventpoll {
	// fs/eventpoll.c
	/*
	 * 此互斥锁用于确保在epoll使用文件时不会删除文件。
	 * 这是在事件收集循环,文件清理路径,epoll文件出口代码和ctl操作期间保留的。
	 */
	struct mutex mtx;

	/* sys_epoll_wait()使用的等待队列 */
	/* 调用epoll_wait方法的线程在被堵塞之前会放相应的信息在这个队列里
     这样当有监听事件发生时,这些线程就可以被唤醒 */
	wait_queue_head_t wq;

	/* 等待者使用的等待队列 file->poll() */
	wait_queue_head_t poll_wait;

	/*准备好的文件描述符列表*/
	/* 被监听的socket文件有对应的事件生成后,就会被放到这个队列中 */
	struct list_head rdllist;

	/* RB树根,用于存储受监视的fd结构 */
	/* 被监听的socket文件会被放到这个数据结构里,红黑树 */
	struct rb_root_cached rbr;

	/*
	 * This is a single linked list that chains all the "struct epitem" that
	 * happened while transferring ready events to userspace w/out
	 * holding ->wq.lock.
	 * 这是一个单链表,其中链接了将就绪事件转移到用户空间 w/out 
	 * 持有 ->wq.lock时发生的所有“struct epitem”。
	 */
	struct epitem *ovflist;
 
	/* ep_scan_ready_list运行时使用的wakeup_source */
	struct wakeup_source *ws;

	/* 创建事件轮询描述符的用户 */
	struct user_struct *user;

	struct file *file;

	/* 用于优化回路检测检查 */
	int visited;
	struct list_head visited_list_link;

#ifdef CONFIG_NET_RX_BUSY_POLL
	/* 用于跟踪繁忙的民意调查napi_id */
	unsigned int napi_id;
#endif
};

创建epollFd

他有俩个系统调用epoll_create1和epoll_create

  1. 调用ep_alloc方法创建一个eventpoll实例
  2. 调用get_unused_fd_flags方法找到一个未使用的fd,这个就是最终返回给我们的文件描述符。
  3. 调用anon_inode_getfile方法创建一个file实例,其类型为
// include/linux/fs.h
struct file {
  //...
  // 这个struct里存放了各种函数指针,用来指向操作文件的各种函数
  // 比如read/write等。这样不同类型的文件,就可以有不同的函数实现
  const struct file_operations  *f_op;
  //...
  // struct file 里的数据字段存放的是所有file类型通用的数据
  // 而下面这个字段存放的是和具体文件类型相关的数据
  void      *private_data;
  //...
}

调用anon_inode_getfile方法传入的参数中,eventpoll_fops最终被赋值到上面的f_op字段,ep被赋值到上面的private_data字段。
4. 调用fd_install方法在内核中建立 fd 与 file 的对应关系,这样以后就可以通过fd来找到对应的file。
5. 返回fd给用户。
至此,epoll_create1方法结束。

/*
 * 打开一个eventpoll文件描述符.
 */
static int do_epoll_create(int flags)
{
	int error, fd;
	struct eventpoll *ep = NULL;
	struct file *file;

	/* 检查EPOLL_* 常量的一致性. */
	BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);

	if (flags & ~EPOLL_CLOEXEC)
		return -EINVAL;
	/*
	 * 创建内部数据结构 ("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;
}

SYSCALL_DEFINE1(epoll_create1, int, flags)
{
	return do_epoll_create(flags);
}

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

	return do_epoll_create(0);
}

epollFD文件描述符的回调实现

/* 实现eventpoll文件行为的文件回调 */
static const struct file_operations eventpoll_fops = {
#ifdef CONFIG_PROC_FS
	.show_fdinfo	= ep_show_fdinfo,
#endif
	.release	= ep_eventpoll_release,
	.poll		= ep_eventpoll_poll,
	.llseek		= noop_llseek,
};

epoll_ctl 系统调用

int epoll_ctl(int epfd, intop, int fd, struct epoll_event*event);

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

/* 要发布给sys_epoll_ctl()的有效操作码  */
#define EPOLL_CTL_ADD 1
#define EPOLL_CTL_DEL 2
#define EPOLL_CTL_MOD 3

第一个参数是epoll_create()的返回值 ,
第二个参数表示动作,用三个宏来表示 :

EPOLL_CTL_ADD: 注册新的fd到epfd中;
EPOLL_CTL_MOD: 修改已经注册的fd的监听事件;
EPOLL_CTL_DEL: 从epfd中删除一个fd;

第三个参数 是需要监听的fd ,
第四个参数 是告诉内核需要监听什么事件 ,struct epoll_event结构如下:

struct epoll_event {
	__poll_t events;  
	__u64 data;
} EPOLL_PACKED;

events 有以下掩码



/* Epoll event masks */
#define EPOLLIN		(__force __poll_t)0x00000001 //触发该事件,表示对应的文件描述符上有可读数据。(包括对端SOCKET正常关闭);
#define EPOLLPRI	(__force __poll_t)0x00000002 //表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
#define EPOLLOUT	(__force __poll_t)0x00000004 //触发该事件,表示对应的文件描述符上可以写数据;
#define EPOLLERR	(__force __poll_t)0x00000008 //表示对应的文件描述符发生错误;
#define EPOLLHUP	(__force __poll_t)0x00000010 //表示对应的文件描述符被挂断;
#define EPOLLNVAL	(__force __poll_t)0x00000020
#define EPOLLRDNORM	(__force __poll_t)0x00000040
#define EPOLLRDBAND	(__force __poll_t)0x00000080
#define EPOLLWRNORM	(__force __poll_t)0x00000100
#define EPOLLWRBAND	(__force __poll_t)0x00000200
#define EPOLLMSG	(__force __poll_t)0x00000400
#define EPOLLRDHUP	(__force __poll_t)0x00002000

/* 设置目标文件描述符的独占唤醒模式 */
#define EPOLLEXCLUSIVE	((__force __poll_t)(1U << 28))

/* 
 * 请求处理系统唤醒事件,以防止在处理这些事件时发生系统挂起。
 *  
 * 假设既未设置EPOLLET也未设置EPOLLONESHOT,则在使用唤醒事件后再次调用epoll_wait之前,不会重新允许系统挂起。
 * 需要CAP_BLOCK_SUSPEND
 */
#define EPOLLWAKEUP	((__force __poll_t)(1U << 29))

/* 设置目标文件描述符的“单发”行为  */
#define EPOLLONESHOT	((__force __poll_t)(1U << 30)) //只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。

/* 为目标文件描述符设置 Edge Triggered (边缘触发)行为  */
#define EPOLLET		((__force __poll_t)(1U << 31)) //将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的

epoll_ctl的系统调用实现

/*
 * 以下功能为eventpoll文件实现了控制器接口,该接口允许集合中插入/删除/更改文件描述符。
 */
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
		struct epoll_event __user *, event)
{
	int error;
	int full_check = 0;
	struct fd f, tf;
	struct eventpoll *ep;
	struct epitem *epi;
	struct epoll_event epds;
	struct eventpoll *tep = NULL;

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

	error = -EBADF;
	f = fdget(epfd);
	if (!f.file)
		goto error_return;

	/* 获取目标文件的“ struct file * ” */
	tf = fdget(fd);
	if (!tf.file)
		goto error_fput;

	/* 目标文件描述符必须支持轮询 */
	error = -EPERM;
	if (!file_can_poll(tf.file))
		goto error_tgt_fput;

	/* 检查是否允许 EPOLLWAKEUP */
	if (ep_op_has_event(op))
		ep_take_care_of_epollwakeup(&epds);

	/*
	 * 我们必须检查用户传递给我们_is_一个eventpoll文件的文件描述符下方的文件结构。 
	 * 而且我们也不允许在其内部添加epoll文件描述符。
	 */
	error = -EINVAL;
	if (f.file == tf.file || !is_file_epoll(f.file))
		goto error_tgt_fput;

	/*
	 * epoll仅在EPOLL_CTL_ADD时间添加到唤醒队列,
	 * 因此EPOLL_CTL_MOD操作不允许使用EPOLLEXCLUSIVE。 
	 * 另外,我们目前不支持嵌套排他唤醒。
	 */
	if (ep_op_has_event(op) && (epds.events & EPOLLEXCLUSIVE)) {
		if (op == EPOLL_CTL_MOD)
			goto error_tgt_fput;
		if (op == EPOLL_CTL_ADD && (is_file_epoll(tf.file) ||
				(epds.events & ~EPOLLEXCLUSIVE_OK_BITS)))
			goto error_tgt_fput;
	}

	/*
	 * 此时,可以安全地假设“ private_data”包含我们自己的数据结构。
	 */
	ep = f.file->private_data;

	/*
	 * 当我们在另一个epoll文件描述符内插入一个epoll文件描述符时,
	 * 创建闭环的变化有所变化,与更关键的路径相比,在此处更好地进行处理。 
	 * 在检查循环时,我们还确定了可访问的文件列表,并将其挂在tfile_check_list上,
	 * 以便可以检查是否没有创建太多可能的唤醒路径。
	 *
	 * 当epoll文件描述符直接附加到唤醒源时,
	 * 我们不需要在EPOLL_CTL_ADD上使用全局'epumutex',除非epoll文件描述符是嵌套的。 
	 * 加上'epmutex'的目的是为了防止复杂的拓扑(例如循环和深度唤醒路径)
	 * 通过多个EPOLL_CTL_ADD操作并行形成。
	 */
	mutex_lock_nested(&ep->mtx, 0);
	if (op == EPOLL_CTL_ADD) {
		if (!list_empty(&f.file->f_ep_links) ||
						is_file_epoll(tf.file)) {
			full_check = 1;
			mutex_unlock(&ep->mtx);
			mutex_lock(&epmutex);
			if (is_file_epoll(tf.file)) {
				error = -ELOOP;
				if (ep_loop_check(ep, tf.file) != 0) {
					clear_tfile_check_list();
					goto error_tgt_fput;
				}
			} else
				list_add(&tf.file->f_tfile_llink,
							&tfile_check_list);
			mutex_lock_nested(&ep->mtx, 0);
			if (is_file_epoll(tf.file)) {
				tep = tf.file->private_data;
				mutex_lock_nested(&tep->mtx, 1);
			}
		}
	}

	/*
	 * 尝试在我们的RB树中查找文件,
	 * 由于我们在上面抓取了“ mtx”,
	 * 因此可以确定在释放互斥锁之前,
	 * 可以使用ep_find()查找的项目。
	 */
	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;
		if (full_check)
			clear_tfile_check_list();
		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)
		mutex_unlock(&epmutex);

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

	return error;
}
/*
 * 添加到eventpoll接口的每个文件描述符都将具有链接到“ rbr” RB树的此类型的条目。
 * 避免增加此结构的大小,服务器上可能有成千上万个这样的结构,我们不希望这占用另一个缓存行。
 */
struct epitem {
	union {
		/* RB树节点将此结构链接到eventpoll RB树 */
		struct rb_node rbn;
		/* 用于释放结构表位 */
		struct rcu_head rcu;
	};
	/* 列表头,用于将此结构链接到事件轮询就绪列表 */
	struct list_head rdllink;
	/*
	 * 在保持项目的单个链接链的同时,
	 * “ struct eventpoll”-> ovflist一起工作。
	 */
	struct epitem *next;
	/* 此项所指的文件描述符信息 */
	struct epoll_filefd ffd;
	/* 附加到轮询操作的活动等待队列数 */
	int nwait;
	/* 包含轮询等待队列的列表 */
	struct list_head pwqlist;
	/* 该项目的“容器” */
	struct eventpoll *ep;
	/* 用于将此项目链接到“结构文件”项目列表的列表头 */
	struct list_head fllink;
	/*设置EPOLLWAKEUP时使用的akeupup_source */
	struct wakeup_source __rcu *ws;
	/* 描述感兴趣事件和源fd的结构 */
	struct epoll_event event;
};
static LIST_HEAD(tfile_check_list);
//具有新添加的链接的文件列表,我们可能需要在其中限制发出路径的数量。 受epmutex保护。  全局列表
  1. f = fdget(epfd);获取epfd的file指针
  2. tf = fdget(fd);获取到需要操作的file指针
  3. ep = f.file->private_data;获取eventpoll
  4. epi = ep_find(ep, tf.file, fd); 从eventpoll总查看有没有操作文件描述符的epi

EPOLL_CTL_ADD

  1. list_add(&tf.file->f_tfile_llink,
    &tfile_check_list); 将目标file结构体添加到全局tfile_check_list表中。
  2. 找eventpoll的红黑树中查找这个文件和文件描述符在不在eventpoll的rbr数中
  3. 如果存在则反错,文件存在
  4. 否则将这个文件描述符添加到eventpoll error = ep_insert(ep, &epds, tf.file, fd, full_check);

EPOLL_CTL_DEl

  1. ep_remove(ep, epi);将epi从ep中移除即可

EPOLL_CTL_MOD

  1. error = ep_modify(ep, epi, &epds); 修改相应的epi 即可

epoll_wait系统调用

do_epoll_wait

/*
 * 为eventpoll文件实现事件等待界面。 
 * 它是用户空间epoll_wait(2)的内核部分。
 */
static int do_epoll_wait(int epfd, struct epoll_event __user *events,
			 int maxevents, int timeout)
{
	int error;
	struct fd f;
	struct eventpoll *ep;

	/* 事件的最大数量必须大于零 */
	if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
		return -EINVAL;

	/* 验证用户传递的区域是否可写 */
	if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event)))
		return -EFAULT;

	/* 获取 eventpoll 文件的“ struct file * ” */
	/* 根据epfd找到对应的file */
	f = fdget(epfd);
	if (!f.file)
		return -EBADF;

	/*
	 * 我们必须检查用户传递给我们的fd下面的文件结构是一个eventpoll文件。
	 */
	error = -EINVAL;
	if (!is_file_epoll(f.file))
		goto error_fput;

	/*
	 * 此时,可以安全地假设“ private_data”包含我们自己的数据结构。
	 */
	 /* epoll_create1方法中把eventpoll实例放到了private_data字段中 */
	ep = f.file->private_data;

	/* 是时候钓鱼了 ... */
	error = ep_poll(ep, events, maxevents, timeout);

error_fput:
	fdput(f);
	return error;
}

SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
		int, maxevents, int, timeout)
{
	return do_epoll_wait(epfd, events, maxevents, timeout);
}

epfd为epoll_create1方法返回的fd,events为用户提供的 struct epoll_event 类型的数组,用于存放有监听事件发生的那些监听对象,maxevents 表示这个数组的长度,也表示epoll_wait方法最多可返回maxevents个事件就绪的监听对象。

ep_poll

/** fs/eventpoll.c
  * ep_poll-检索就绪事件,并将其传递给调用方提供的事件缓冲区。
  *
  * @ep:指向eventpoll上下文的指针。
  * @events:指向应该将就绪事件存储在其中的用户空间缓冲区的指针。
  * @maxevents:调用者事件缓冲区的大小(根据事件数)。
  * @timeout:准备事件获取操作的最大超时时间(以毫秒为单位)。 
  * 如果@timeout为零,则函数不会阻塞,而如果@timeout小于零,
  * 则函数将阻塞,直到至少检索到一个事件(或发生错误)。
  *
  *返回:返回已获取的就绪事件的数量,如果发生错误,则返回错误代码。
 */
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();

	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) {
		/*
		 * 如果调用方指定了非阻塞操作,则避免不必要的跳入等待队列循环。
		 */
		timed_out = 1;
		spin_lock_irq(&ep->wq.lock);
		goto check_events;
	}

fetch_events:

	if (!ep_events_available(ep))
		ep_busy_loop(ep, timed_out);

	spin_lock_irq(&ep->wq.lock);

	if (!ep_events_available(ep)) {
		/*
		 * 繁忙的轮询超时。 现在删除NAPI ID,
		 * 我们可以在将具有有效NAPI ID的套接字移至就绪列表后重新添加。
		 */
		ep_reset_busy_poll_napi_id(ep);

		/*
		 * 我们没有任何可用的事件可返回给调用者。 
		 * 我们需要在这里休眠,当事件可用时,我们会被ep_poll_callback()唤醒。
		 */
		init_waitqueue_entry(&wait, current);
		__add_wait_queue_exclusive(&ep->wq, &wait);

		for (;;) {
			/*
			 * 如果ep_poll_callback()在这之间给我们唤醒,我们就不休眠。
			 * 这就是为什么我们在执行检查之前
			 * 将任务状态设置为TASK_INTERRUPTIBLE的原因。
			 */
			set_current_state(TASK_INTERRUPTIBLE);
			/*
			 * 始终使致命信号短路,以使线程及时退出,
			 * 而不会发现更多可用事件并重复获取。
			 */
			if (fatal_signal_pending(current)) {
				res = -EINTR;
				break;
			}
			if (ep_events_available(ep) || timed_out)
				break;
			if (signal_pending(current)) {
				res = -EINTR;
				break;
			}

			spin_unlock_irq(&ep->wq.lock);
			if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))
				timed_out = 1;

			spin_lock_irq(&ep->wq.lock);
		}

		__remove_wait_queue(&ep->wq, &wait);
		__set_current_state(TASK_RUNNING);
	}
check_events:
	/* Is it worth to try to dig for events ? */
	eavail = ep_events_available(ep);

	spin_unlock_irq(&ep->wq.lock);

	/*
	 * 尝试将事件转移到用户空间。 万一我们收到0个事件,并且还有超时时间,
	 * 我们将再次尝试寻找更多的运气。
	 */
	if (!res && eavail &&
	    !(res = ep_send_events(ep, events, maxevents)) && !timed_out)
		goto fetch_events;

	return res;
}
  1. 判断是否有监听事件就绪,如果有则直接调用ep_send_events方法把就绪对象拷贝到events里,然后返回。
  2. 如果没有,则先调用 init_waitqueue_entry 方法初始化wait变量,其中current参数为线程私有变量,线程相关的数据会放到这个变量中,同时,通过这个变量也能找到相应的线程。
struct wait_queue_entry {
	unsigned int		flags;
	void			*private;
	wait_queue_func_t	func;
	struct list_head	entry;
};
static inline void init_waitqueue_entry(struct wait_queue_entry *wq_entry, struct task_struct *p)
{
	wq_entry->flags		= 0;
	wq_entry->private	= p;
	wq_entry->func		= default_wake_function;
}
static inline void
__add_wait_queue_exclusive(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
	wq_entry->flags |= WQ_FLAG_EXCLUSIVE;
	__add_wait_queue(wq_head, wq_entry);
}

wq_entry->func的 default_wake_function 方法就是用来唤醒当前进程current对应的线程的。
3. 初始化完wait变量之后,把它放到eventpoll的wq队列中,这个上面我们也有提到过。
4. 然后进入for循环,其逻辑为,检查是否有监听事件就绪,如果没有,则调用 schedule_hrtimeout_range 方法,使当前线程进入休眠状态。
5. 当各种情况,比如signal、timeout、监听事件发生,导致该线程被唤醒,则会再进入下一次for循环,并检查监听事件是否就绪,如果就绪了,则跳出for循环,同时把wait变量从eventpoll的wq队列中移除。
6. 调用 ep_send_events 方法把就绪事件的对象拷贝到用户提供的events数组中,然后返回。

ep_send_events

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;
}

ep_scan_ready_list的ep_send_events_proc参数是一个回调方法,在ep_scan_ready_list中调用

ep_scan_ready_list

/**
 *
 * ep_scan_ready_list-以一种可能的扫描代码方式扫描就绪列表,
 *    以调用f_op->poll()。 还允许O(NumReady)性能。
 * @ep:指向epoll私有数据结构的指针。
 * @sproc:指向扫描回调的指针。
 * @priv:传递给@sproc回调的私有不透明数据。
 * @depth:递归f_op-> poll调用的当前深度。
 * @ep_locked:呼叫者已经持有ep-> mtx 
 * 返回:由@sproc回调返回的相同整数错误代码。
 */
static __poll_t ep_scan_ready_list(struct eventpoll *ep,
			      __poll_t (*sproc)(struct eventpoll *,
					   struct list_head *, void *),
			      void *priv, int depth, bool ep_locked)
{
	__poll_t res;
	int pwake = 0;
	struct epitem *epi, *nepi;
	LIST_HEAD(txlist);

	lockdep_assert_irqs_enabled();

	/*
	 * We need to lock this because we could be hit by
	 * eventpoll_release_file() and epoll_ctl().
	 * 我们需要锁定它,因为我们可能会被eventpoll_release_file()
	 * 和epoll_ctl()击中。
	 */

	if (!ep_locked)
		mutex_lock_nested(&ep->mtx, depth);

	/*
	 * 偷走准备好的列表,然后将原始列表重新初始化为空白列表。
	 * 另外,将ep-> ovflist设置为NULL,
	 * 以使在丢失w / out锁时发生的事件不会丢失。 
	 * 我们不能使poll回调直接在ep-> rdllist上排队,
	 * 因为我们希望“ sproc”回调能够以无锁方式进行。
	 */
	spin_lock_irq(&ep->wq.lock);
	list_splice_init(&ep->rdllist, &txlist);
	ep->ovflist = NULL;
	spin_unlock_irq(&ep->wq.lock);

	/*
	 * 现在调用回调函数。
	 */
	res = (*sproc)(ep, &txlist, priv);

	spin_lock_irq(&ep->wq.lock);
	/*
	 * 在我们使用“ sproc”回调的过程中,
	 * 其他一些事件可能已由poll回调排队。
	 *  我们在这里将它们重新插入到主要准备列表中。
	 */
	for (nepi = ep->ovflist; (epi = nepi) != NULL;
	     nepi = epi->next, epi->next = EP_UNACTIVE_PTR) {
		/* 
		 * 我们需要检查项目是否已经在列表中。
		 *  在“ sproc”回调执行期间,项目会排队进入->ovflist,
		 * 但“ txlist”可能已包含它们,并且下面的list_splice()会照顾它们。
		 */
		if (!ep_is_linked(epi)) {
			list_add_tail(&epi->rdllink, &ep->rdllist);
			ep_pm_stay_awake(epi);
		}
	}
	/*
	 * 我们需要将ep-> ovflist设置回EP_UNACTIVE_PTR,
	 * 以便在释放锁定后,事件将以常规方式在ep-> rdllist内部排队。
	 */
	ep->ovflist = EP_UNACTIVE_PTR;

	/*
	 * 快速重新注入留在“ txlist”上的项目。
	 */
	list_splice(&txlist, &ep->rdllist);
	__pm_relax(ep->ws);

	if (!list_empty(&ep->rdllist)) {
		/*
		 * 唤醒(如果激活)eventpoll等待列表和->poll()等待列表(在释放锁定后延迟)。
		 */
		if (waitqueue_active(&ep->wq))
			wake_up_locked(&ep->wq);
		if (waitqueue_active(&ep->poll_wait))
			pwake++;
	}
	spin_unlock_irq(&ep->wq.lock);

	if (!ep_locked)
		mutex_unlock(&ep->mtx);

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

	return res;
}

ep_scan_ready_list是将eventpoll中的rdllist列表内容转移到txlist列表中,同时把rdllist列表置为空,现在txlist就持有了所有有就绪事件的对象。
然后调用上面的回调方法 ep_send_events_proc,将该列表传入其中。

ep_send_events_proc

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;
	struct epoll_event __user *uevent;
	struct wakeup_source *ws;
	poll_table pt;

	init_poll_funcptr(&pt, NULL);

	/*
	 * 我们可以无锁循环,因为我们传递了任务私有列表。 
	 * 在此循环期间,项目无法消失,
	 * 因为ep_scan_ready_list()在此调用期间保持“ mtx”。
	 */
	for (esed->res = 0, uevent = esed->events;
	     !list_empty(head) && esed->res < esed->maxevents;) {
		epi = list_first_entry(head, struct epitem, rdllink);

		/*
		 * 在停用epi-> ws之前先激活ep-> ws,
		 *  以防止在此处触发自动暂停(以防我们在下面对epi-> ws进行反应)。
		 * 
		 * 可以重新安排它以延迟epi->ws的停用,
		 *  但是epi->ws会暂时与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);

		revents = ep_item_poll(epi, &pt, 1);

		/*
		 * 如果事件掩码与调用方请求的掩码相交,请将事件传递到用户空间。 
		 * 同样,ep_scan_ready_list()持有“ mtx”,因此,
		 * 来自用户空间的任何操作都不能更改该项目。
		 */
		if (revents) {
			if (__put_user(revents, &uevent->events) ||
			    __put_user(epi->event.data, &uevent->data)) {
				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.
				 * 如果已使用“水平触发”模式添加了此文件,
				 * 则需要重新插入就绪列表中,
				 * 以便对epoll_wait()的下一次调用将再次检查事件的可用性。
				 * 在这一点上,除了我们之外,没有人可以插入ep-> rdllist。
				 * 持有“ mtx”的ep_scan_ready_list()
				 * 将epoll_ctl()调用者锁定,
				 * 轮询回调将它们排队在ep->ovflist中。
				 * 
				 * 如果是 level-triggered,该对象还会被添加到就绪列表里
				 * 这样下次调用 epoll_wait 还会检查这个对象
				 */
				list_add_tail(&epi->rdllink, &ep->rdllist);
				ep_pm_stay_awake(epi);
			}
		}
	}

	return 0;
}
  1. 遍历head就绪列表中的所有对象,对其调用 ep_item_poll 方法,真正的去检查我们关心的那些事件是否存在。

  2. 如果有我们感兴趣的事件,则将该事件拷贝到用户event中。

  3. 如果该监听对象是 level-triggered 模式,则会把该对象再加入到就绪列表中,这样下次再调用 epoll_wait 方法,还会检查这些对象。
    这也是 level-triggered 和 edge-triggered 在代码上表现出来的本质区别。

  4. 所有监听对象检查完毕后,此时满足条件的对象已经被拷贝到用户提供的events里,到这里方法就可以返回了。

ep_item_poll

/*
 * 与ep_eventpoll_poll()的不同之处在于内部调用者已经具有ep-> mtx,
 * 因此我们需要从depth = 1开始,以便正确注释mutex_lock_nested()。
 */
static __poll_t ep_item_poll(const struct epitem *epi, poll_table *pt,
				 int depth)
{
	struct eventpoll *ep;
	bool locked;

	pt->_key = epi->event.events;
	if (!is_file_epoll(epi->ffd.file))
		return vfs_poll(epi->ffd.file, pt) & epi->event.events;

	ep = epi->ffd.file->private_data;
	poll_wait(epi->ffd.file, &ep->poll_wait, pt);
	locked = pt && (pt->_qproc == ep_ptable_queue_proc);

	return ep_scan_ready_list(epi->ffd.file->private_data,
				  ep_read_events_proc, &depth, depth,
				  locked) & epi->event.events;
}

如果是epoll 则进行poll_wait即可

vfs_poll

static inline __poll_t vfs_poll(struct file *file, struct poll_table_struct *pt)
{
	if (unlikely(!file->f_op->poll))
		return DEFAULT_POLLMASK;
	return file->f_op->poll(file, pt);
}

如果是我们自定义或者是socket则执行vfs_poll
对于tcp socket对象,这个方法最终会调用 tcp_poll 方法,由于该方法涉及的都是tcp相关的内容

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山羊哥-老宋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值