一 I/O复用概述
I/O复用使得程序能同时监听多个文件描述符。
二 常用函数
linux下实现I/O复用的系统调用有select,poll,epoll.
三 select详解
select系统调用:在一段时间内,监听用户感兴趣的文件描述符上的可读,可写,异常事件。
select API:
#include
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
参数详解:
nfds:监听的文件描述符总数,通常为监听的最大文件描述符加1
readfds,writefds,exceptfds分别指向可读、可写、异常事件对应的文件描述符。
fd_set是一个整形数组,每一位标记一个文件描述符,容量由FD_SETSIZE决定,限制了select能同时处理的文件描述符总量。可监听描述符最大为1024。
timeout超时时间
select调用返回就绪描述符总数,失败返回-1并设置error.
四 select内核实现分析
select系统调用的调用过程:
sys_select->core_sys_select->do_select->poll->sock_poll->tcp_poll
每一个函数功能的理解:
system_select:处理时间参数,从用户态获得超时时间,并做处理,返回到用户态,其中调用core_sys_select
core_sys_select:处理三个描述符集,计算出他们需要的空间并为之分配,将数据从用户空间拷贝进入内核空间,用到6个位图,三个用于输出,三个用于输入。
do_select:select核心函数
下面是do_select内核实现:
int do_select(int n, fd_set_bits *fds, long *timeout)
{
struct poll_wqueues table;
poll_table *wait;
int retval, i;
long __timeout = *timeout;
spin_lock(¤t->files->file_lock);
retval = max_select_fd(n, fds);
spin_unlock(¤t->files->file_lock);
if (retval < 0)
return retval;
n = retval;
poll_initwait(&table);
wait = &table.pt;
if (!__timeout)
wait = NULL;
retval = 0;
for (;;) {
unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
set_current_state(TASK_INTERRUPTIBLE);
inp = fds->in; outp = fds->out; exp = fds->ex;
rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
unsigned long in, out, ex, all_bits, bit = 1, mask, j;
unsigned long res_in = 0, res_out = 0, res_ex = 0;
struct file_operations *f_op = NULL;
struct file *file = NULL;
in = *inp++; out = *outp++; ex = *exp++;
all_bits = in | out | ex;
if (all_bits == 0) {
i += __NFDBITS;
continue;
}
for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {
if (i >= n)
break;
if (!(bit & all_bits))
continue;
file = fget(i);
if (file) {
f_op = file->f_op;
mask = DEFAULT_POLLMASK;
if (f_op && f_op->poll)
mask = (*f_op->poll)(file, retval ? NULL : wait);
fput(file);
if ((mask & POLLIN_SET) && (in & bit)) {
res_in |= bit;
retval++;
}
if ((mask & POLLOUT_SET) && (out & bit)) {
res_out |= bit;
retval++;
}
if ((mask & POLLEX_SET) && (ex & bit)) {
res_ex |= bit;
retval++;
}
}
cond_resched();
}
if (res_in)
*rinp = res_in;
if (res_out)
*routp = res_out;
if (res_ex)
*rexp = res_ex;
}
wait = NULL;
if (retval || !__timeout || signal_pending(current))
break;
if(table.error) {
retval = table.error;
break;
}
__timeout = schedule_timeout(__timeout);
}
__set_current_state(TASK_RUNNING);
poll_freewait(&table);
/*
* Up-to-date the caller timeout.
*/
*timeout = __timeout;
return retval;
}
具体的do_select中,存在for死循环,遍历所有指定好的描述符范围(0 - maxdfp1-1)内所有的位,每次循环获取32位的位图,然后其中如果有需要检测状态的文件描述符,就对其中每一位进行检测,即轮询,并进一步获取置1的位代表的描述符对应的file结构体,从file结构体的f_op->poll
,找到具体指定的poll,之后需要将返回的状态值与三个宏定义(可读、可写、有异常)进行比较来确定描述符读、写or异常是否就绪。