linux select io处理
Posted osnet
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux select io处理相关的知识,希望对你有一定的参考价值。
文章目录
先看一下select机制
select需要驱动程序的支持,驱动程序实现fops内的poll函数。select通过每个设备文件对应的poll函数提供的信息判断当前是否有资源可用(如可读或写),如果有的话则返回可用资源的文件描述符个数,没有的话则睡眠,等待有资源变为可用时再被唤醒继续执行。
select系统调用
int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
参数说明:
maxfdp:被监听的文件描述符的总数,它比所有文件描述符集合中的文件描述符的最大值大1,因为文件描述符是从0开始计数的;
readfds、writefds、exceptset:分别指向可读、可写和异常等事件对应的描述符集合。
timeout:用于设置select函数的超时时间,即告诉内核select等待多长时间之后就放弃等待。timeout == NULL 表示等待无限长的时间
SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,
fd_set __user *, exp, struct timeval __user *, tvp)
struct timespec end_time, *to = NULL;
struct timeval tv;
int ret;
//保存超时等待时间,存储到end_time中
if (tvp)
if (copy_from_user(&tv, tvp, sizeof(tv)))
return -EFAULT;
to = &end_time;
if (poll_select_set_timeout(to,
tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),
(tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))
return -EINVAL;
ret = core_sys_select(n, inp, outp, exp, to);
//设置剩余超时时间,返回到用户空间
ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);
return ret;
core_sys_select
int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,
fd_set __user *exp, struct timespec *end_time)
fd_set_bits fds;
void *bits;
int ret, max_fds;
unsigned int size;
struct fdtable *fdt;
/* Allocate small arguments on the stack to save memory and be faster */
long stack_fds[SELECT_STACK_ALLOC/sizeof(long)]; //分配栈空间,一个fd描述符用一个bit表示,最多支持1024个
/* max_fds can increase, so grab it once to avoid race */
rcu_read_lock();
fdt = files_fdtable(current->files); //当前进程打开的文件描述符表
max_fds = fdt->max_fds;
rcu_read_unlock();
if (n > max_fds)
n = max_fds;
/*
* We need 6 bitmaps (in/out/ex for both incoming and outgoing),
* since we used fdset we need to allocate memory in units of
* long-words.
*/
size = FDS_BYTES(n); //用户空间调用设置的最大fd描述符
bits = stack_fds;
if (size > sizeof(stack_fds) / 6) 如果栈空间不够,需要申请空间;6个为一组,包括用户空间传进的in,out,exception,和
内核保存用的res_in,res_out,res_exception,其中存储发生变化的fd描述符
/* Not enough space in on-stack array; must use kmalloc */
bits = kmalloc(6 * size, GFP_KERNEL);
fds.in = bits;
fds.out = bits + size;
fds.ex = bits + 2*size;
fds.res_in = bits + 3*size;
fds.res_out = bits + 4*size;
fds.res_ex = bits + 5*size;
//把用户空间in,out,exception 存储到fds中
if ((ret = get_fd_set(n, inp, fds.in)) ||
(ret = get_fd_set(n, outp, fds.out)) ||
(ret = get_fd_set(n, exp, fds.ex)))
goto out;
zero_fd_set(n, fds.res_in);
zero_fd_set(n, fds.res_out);
zero_fd_set(n, fds.res_ex);
ret = do_select(n, &fds, end_time);
//设置fdsres_in,res_out,res_exception到用户空间
if (set_fd_set(n, inp, fds.res_in) ||
set_fd_set(n, outp, fds.res_out) ||
set_fd_set(n, exp, fds.res_ex))
ret = -EFAULT;
do_select
int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
ktime_t expire, *to = NULL;
struct poll_wqueues table;
poll_table *wait;
int retval, i, timed_out = 0;
unsigned long slack = 0;
//初始化poll_table 表,用于poll
poll_initwait(&table);
wait = &table.pt;
//处理边界条件,超时时间为0
if (end_time && !end_time->tv_sec && !end_time->tv_nsec)
wait->_qproc = NULL;
timed_out = 1;
//for 死循环
for (;;)
unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
inp = fds->in; outp = fds->out; exp = fds->ex;
rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
//遍历所有fd,一直到用户调用设置的最大n
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;
in = *inp++; out = *outp++; ex = *exp++;
all_bits = in | out | ex;
//如果in | out | ex 没有一设置,下一轮
if (all_bits == 0)
i += BITS_PER_LONG; //每次遍历处理BITS_PER_LONG个
continue;
//每次遍历处理BITS_PER_LONG个
for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1)
struct fd f;
if (i >= n)
break;
//如果bit没有再all_bits = in | out | ex中,继续下一个
if (!(bit & all_bits))
continue;
f = fdget(i); //获取i对应的描述符
if (f.file)
const struct file_operations *f_op;
f_op = f.file->f_op;
mask = DEFAULT_POLLMASK;
//调用对应文件的poll,把当前进程加入wait队列,等到驱动io处理时,唤醒当前进程
if (f_op && f_op->poll)
wait_key_set(wait, in, out, bit);
mask = (*f_op->poll)(f.file, wait);
fdput(f);
//如果poll返回值有,则函数返回
if ((mask & POLLIN_SET) && (in & bit))
res_in |= bit;
retval++;
wait->_qproc = NULL;
if ((mask & POLLOUT_SET) && (out & bit))
res_out |= bit;
retval++;
wait->_qproc = NULL;
if ((mask & POLLEX_SET) && (ex & bit))
res_ex |= bit;
retval++;
wait->_qproc = NULL;
//设置的res_in,res_out,res_ex
if (res_in)
*rinp = res_in;
if (res_out)
*routp = res_out;
if (res_ex)
*rexp = res_ex;
cond_resched();
wait->_qproc = NULL;
//如果设置了fd,或者超时,或者有信号pending,跳出死循环,函数返回
if (retval || timed_out || signal_pending(current))
break;
if (table.error)
retval = table.error;
break;
/*
* If this is the first loop and we have a timeout
* given, then we convert to ktime_t and set the to
* pointer to the expiry value.
*/
//计算超时时间
if (end_time && !to)
expire = timespec_to_ktime(*end_time);
to = &expire;
//当前进程让出cpu,超时返回,或者被io唤醒; 被唤醒后继续循环,调用poll看是否有fd变化
if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
to, slack))
timed_out = 1;
poll_freewait(&table);
return retval;
static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
pt->_qproc = qproc;
pt->_key = ~0UL; /* all events enabled */
void poll_initwait(struct poll_wqueues *pwq)
init_poll_funcptr(&pwq->pt, __pollwait);
pwq->polling_task = current; //设置当前进程
pwq->triggered = 0;
pwq->error = 0;
pwq->table = NULL;
pwq->inline_index = 0;
/* Add a new entry */
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
poll_table *p)
struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
struct poll_table_entry *entry = poll_get_entry(pwq); //构造一个poll_table_entry
if (!entry)
return;
entry->filp = get_file(filp);
entry->wait_address = wait_address;
entry->key = p->_key;
init_waitqueue_func_entry(&entry->wait, pollwake);
entry->wait.private = pwq; //把wait挂载都驱动的等待队列wait_address中,wait_address具体io驱动的等待队列地址
add_wait_queue(wait_address, &entry->wait);
以上是关于linux select io处理的主要内容,如果未能解决你的问题,请参考以下文章