linux内内核核select/poll,,epoll实实现现与与区区别别
下面文章在这段时间内研究 select/poll/epoll的内核实现的一点心得体会:
select,poll,epoll都是多路 用IO 的函数,简单说就是在一个线程里,可以同时处理多个文件描述符的读写。
select/poll的实现很类似,epoll是从select/poll扩展而来,主要是为了解决select/poll天生的缺陷。
epoll在内核版本2.6 以上才出现的新的函数,而他们在linux 内核中的实现都是十分相似。
这三种函数都需要设备驱动提供poll回调函数,对于套接字而言,他们是 tcp_poll,udp_poll和datagram_poll;
对于自己开发的设备驱动而言,是自己实现的poll接口函数。
select实现 (2.6 的内核,其他版本的内核,应该都相差不多)
应用程序调用select,进入内核调用sys_select,做些简单初始化工作,接着进入 core_sys_select,
此函数主要工作是把描述符集合从用户空间 制到内核空间, 最终进入do_select,完成其主要的功能。
do_select里,调用 poll_initwait,主要工作是注册poll_wait的回调函数为__pollwait,
当在设备驱动的poll回调函数里调用poll_wait,其实就是调用__pollwait,
__pollwait的主要工作是把当前进程挂载到等待队列里,当等待的事件到来就会唤醒此进程。
接着执行for循环,循环里首先遍历每个文件描述符,调用对应描述符的poll回调函数,检测是否就绪,
遍历完所有描述符之后,只要有描述符处于就绪状态,信号中断, 出错或者超时,就退出循环,
否则会调用schedule_xxx 函数,让当前进程睡眠,一直到超时或者有描述符就绪被唤醒。
接着又会再次遍历每个描述符,调用poll再次检测。
如此循环,直到符合条件才会退出。
以下是 2.6.3 内核的有关select函数的部分片段:
他他们们调调用用关关系系::
select --> sys_select --> core_sys_select --> do_select
int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
{
ktime_t expire, *to = NULL;
struct poll_wqueues t ble;
poll_t ble *w it;
int retv l, i, timed_out = 0;
unsigned long sl ck = 0;
///这里为了获得集合中的最大描述符,这样可减少循环中遍历的次数。
///也就是为什么linux中select第一个参数为何如此重要了
rcu_re d_lock();
retv l = m x_select_fd(n, fds);
rcu_re d_unlock();
if (retv l < 0)
return retv l;
n = retv l;
初始化 poll_t ble结构,其中一个重要任务是把 __pollw it函数地址赋值给它,
poll_initw it(&t ble);
w it = &t ble.pt;
if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
w it = NULL;
timed_out = 1;
}
if (end_time && !timed_out)
sl ck = estim te_ ccur cy(end_time);
retv l = 0;
///主循环,将会在这里完成描述符的状态轮训
for (;;) {
unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
inp = fds->in; outp = fds->out; exp = fds->ex;
rinp = fds->res_in; routp = fds->re