epoll
创建epollFdepollFD文件描述符的回调实现
epoll_ctl 系统调用epoll_ctl的系统调用实现EPOLL_CTL_ADDEPOLL_CTL_DElEPOLL_CTL_MOD
epoll_wait系统调用do_epoll_waitep_pollep_send_eventsep_scan_ready_listep_send_events_procep_item_pollvfs_poll
可以结合这篇文章一起看
eventfd
由于需要实现一个驱动想封装出epoll的设备,在网上找关于epoll的信息,好少所以自己看了内核的代码了解以下epoll机制,做个总结以便自己后面查看。
struct eventpoll
{
struct mutex mtx
;
wait_queue_head_t wq
;
wait_queue_head_t poll_wait
;
struct list_head rdllist
;
struct rb_root_cached rbr
;
struct epitem
*ovflist
;
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
unsigned int napi_id
;
#endif
};
创建epollFd
他有俩个系统调用epoll_create1和epoll_create
调用ep_alloc方法创建一个eventpoll实例调用get_unused_fd_flags方法找到一个未使用的fd,这个就是最终返回给我们的文件描述符。调用anon_inode_getfile方法创建一个file实例,其类型为
struct file
{
const struct file_operations
*f_op
;
void *private_data
;
}
调用anon_inode_getfile方法传入的参数中,eventpoll_fops最终被赋值到上面的f_op字段,ep被赋值到上面的private_data字段。 4. 调用fd_install方法在内核中建立 fd 与 file 的对应关系,这样以后就可以通过fd来找到对应的file。 5. 返回fd给用户。 至此,epoll_create1方法结束。
static int do_epoll_create(int flags
)
{
int error
, fd
;
struct eventpoll
*ep
= NULL;
struct file
*file
;
BUILD_BUG_ON(EPOLL_CLOEXEC
!= O_CLOEXEC
);
if (flags
& ~EPOLL_CLOEXEC
)
return -EINVAL
;
error
= ep_alloc(&ep
);
if (error
< 0)
return error
;
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文件描述符的回调实现
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()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
#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 有以下掩码
#define EPOLLIN (__force __poll_t)0x00000001
#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))
#define EPOLLWAKEUP ((__force __poll_t)(1U << 29))
#define EPOLLONESHOT ((__force __poll_t)(1U << 30))
#define EPOLLET ((__force __poll_t)(1U << 31))
epoll_ctl的系统调用实现
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
;
tf
= fdget(fd
);
if (!tf
.file
)
goto error_fput
;
error
= -EPERM
;
if (!file_can_poll(tf
.file
))
goto error_tgt_fput
;
if (ep_op_has_event(op
))
ep_take_care_of_epollwakeup(&epds
);
error
= -EINVAL
;
if (f
.file
== tf
.file
|| !is_file_epoll(f
.file
))
goto error_tgt_fput
;
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
;
}
ep
= f
.file
->private_data
;
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);
}
}
}
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
;
}
struct epitem
{
union {
struct rb_node rbn
;
struct rcu_head rcu
;
};
struct list_head rdllink
;
struct epitem
*next
;
struct epoll_filefd ffd
;
int nwait
;
struct list_head pwqlist
;
struct eventpoll
*ep
;
struct list_head fllink
;
struct wakeup_source __rcu
*ws
;
struct epoll_event event
;
};
static LIST_HEAD(tfile_check_list
);
f = fdget(epfd);获取epfd的file指针tf = fdget(fd);获取到需要操作的file指针ep = f.file->private_data;获取eventpollepi = ep_find(ep, tf.file, fd); 从eventpoll总查看有没有操作文件描述符的epi
EPOLL_CTL_ADD
list_add(&tf.file->f_tfile_llink, &tfile_check_list); 将目标file结构体添加到全局tfile_check_list表中。找eventpoll的红黑树中查找这个文件和文件描述符在不在eventpoll的rbr数中如果存在则反错,文件存在否则将这个文件描述符添加到eventpoll error = ep_insert(ep, &epds, tf.file, fd, full_check);
EPOLL_CTL_DEl
ep_remove(ep, epi);将epi从ep中移除即可
EPOLL_CTL_MOD
error = ep_modify(ep, epi, &epds); 修改相应的epi 即可
epoll_wait系统调用
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
;
if (maxevents
<= 0 || maxevents
> EP_MAX_EVENTS
)
return -EINVAL
;
if (!access_ok(VERIFY_WRITE
, events
, maxevents
* sizeof(struct epoll_event
)))
return -EFAULT
;
f
= fdget(epfd
);
if (!f
.file
)
return -EBADF
;
error
= -EINVAL
;
if (!is_file_epoll(f
.file
))
goto error_fput
;
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
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
)) {
ep_reset_busy_poll_napi_id(ep
);
init_waitqueue_entry(&wait
, current
);
__add_wait_queue_exclusive(&ep
->wq
, &wait
);
for (;;) {
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
:
eavail
= ep_events_available(ep
);
spin_unlock_irq(&ep
->wq
.lock
);
if (!res
&& eavail
&&
!(res
= ep_send_events(ep
, events
, maxevents
)) && !timed_out
)
goto fetch_events
;
return res
;
}
判断是否有监听事件就绪,如果有则直接调用ep_send_events方法把就绪对象拷贝到events里,然后返回。如果没有,则先调用 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
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();
if (!ep_locked
)
mutex_lock_nested(&ep
->mtx
, depth
);
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
);
for (nepi
= ep
->ovflist
; (epi
= nepi
) != NULL;
nepi
= epi
->next
, epi
->next
= EP_UNACTIVE_PTR
) {
if (!ep_is_linked(epi
)) {
list_add_tail(&epi
->rdllink
, &ep
->rdllist
);
ep_pm_stay_awake(epi
);
}
}
ep
->ovflist
= EP_UNACTIVE_PTR
;
list_splice(&txlist
, &ep
->rdllist
);
__pm_relax(ep
->ws
);
if (!list_empty(&ep
->rdllist
)) {
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
);
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);
for (esed
->res
= 0, uevent
= esed
->events
;
!list_empty(head
) && esed
->res
< esed
->maxevents
;) {
epi
= list_first_entry(head
, struct epitem
, rdllink
);
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);
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
)) {
list_add_tail(&epi
->rdllink
, &ep
->rdllist
);
ep_pm_stay_awake(epi
);
}
}
}
return 0;
}
遍历head就绪列表中的所有对象,对其调用 ep_item_poll 方法,真正的去检查我们关心的那些事件是否存在。
如果有我们感兴趣的事件,则将该事件拷贝到用户event中。
如果该监听对象是 level-triggered 模式,则会把该对象再加入到就绪列表中,这样下次再调用 epoll_wait 方法,还会检查这些对象。 这也是 level-triggered 和 edge-triggered 在代码上表现出来的本质区别。
所有监听对象检查完毕后,此时满足条件的对象已经被拷贝到用户提供的events里,到这里方法就可以返回了。
ep_item_poll
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相关的内容