在续1中了解中bitmap在select的用法后,下面就开始分析select
Select系统调用的调用过程:
Sys_select-->core_sys_select-->do_select-->f_op->poll
SYSCALL_DEFINE5(select,int, n, fd_set __user *, inp, fd_set __user *, outp, fd_set__user *, exp, struct timeval __user *, tvp) { structtimespec end_time, *to = NULL; structtimeval tv; intret; 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); returnret; } 这个函数先从用户空间拷贝一些数据,对select调用中的超时时间参数做一个处理检查。 我们直接看core_sys_select(n,inp, outp, exp, to); intcore_sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timespec *end_time) { fd_set_bitsfds; void*bits; intret, max_fds; unsignedint size; structfdtable *fdt; /*Allocate small arguments on the stack to save memory and be faster */ longstack_fds[SELECT_STACK_ALLOC/sizeof(long)]; ret= -EINVAL; if(n < 0) gotoout_nofds; /*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); bits= stack_fds; if(size > sizeof(stack_fds) / 6) { /*Not enough space in on-stack array; must use kmalloc */ ret= -ENOMEM; bits= kmalloc(6 * size, GFP_KERNEL); if(!bits) gotoout_nofds; } 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; ------------------------------------------------------------------------------------ 上面这一部分是分配6个bitmaps。因为要监听inout和ex,而每一个又分为 Incoming和outgoing,所以是6个。这个incoming和outgoing没想到合适的 翻译,暂且翻译为输入和输出,因为后面看到inout ex都是作为从用户空间拷贝到内核 空间的,而res_inres_out和res_ex都是从内核空间拷贝到用户控件的。 ------------------------------------------------------------------------------------ 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))) gotoout; -------------------------------------------------------------------------- 调用copy_from_user从用户空间拷贝fd_st --------------------------------------------------------------------------- zero_fd_set(n,fds.res_in); zero_fd_set(n,fds.res_out); zero_fd_set(n,fds.res_ex); ----------------------------------------------------------------------------- 将fds.res_inres_out和res_out都清为0 ----------------------------------------------------------------------------- ret= do_select(n, &fds, end_time); //关键函数 if(ret < 0) gotoout; if(!ret) { ret= -ERESTARTNOHAND; if(signal_pending(current)) gotoout; ret= 0; } 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函数中设定的res_inres_out res_ex结果集拷贝到用户空间 -------------------------------------------------------------------------------- out: if(bits != stack_fds) kfree(bits); out_nofds: returnret; }
Do_select函数的分析:
int do_select(int n, fd_set_bits *fds, struct timespec *end_time) { ktime_texpire, *to = NULL; structpoll_wqueues table; poll_table*wait; intretval, i, timed_out = 0; unsignedlong slack = 0; rcu_read_lock(); retval= max_select_fd(n, fds); rcu_read_unlock(); if(retval < 0) returnretval; n= retval; poll_initwait(&table); wait= &table.pt; if(end_time && !end_time->tv_sec &&!end_time->tv_nsec) { wait= NULL; timed_out= 1; } if(end_time && !timed_out) slack= estimate_accuracy(end_time); retval= 0; for(;;) { unsignedlong *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; for(i = 0; i < n; ++rinp, ++routp, ++rexp) { unsignedlong in, out, ex, all_bits, bit = 1, mask, j; unsignedlong res_in = 0, res_out = 0, res_ex = 0; conststruct file_operations *f_op = NULL; structfile *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) { intfput_needed; if(i >= n) break; if(!(bit & all_bits)) continue; file= fget_light(i, &fput_needed); if(file) { f_op= file->f_op; mask= DEFAULT_POLLMASK; if(f_op && f_op->poll) { wait_key_set(wait,in, out, bit); mask= (*f_op->poll)(file, wait); } fput_light(file,fput_needed); if((mask & POLLIN_SET) && (in & bit)) { res_in|= bit; retval++; wait= NULL; } if((mask & POLLOUT_SET) && (out & bit)) { res_out|= bit; retval++; wait= NULL; } if((mask & POLLEX_SET) && (ex & bit)) { res_ex|= bit; retval++; wait= NULL; } } } if(res_in) *rinp= res_in; if(res_out) *routp= res_out; if(res_ex) *rexp= res_ex; cond_resched(); } wait= NULL; 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; } if(!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE, to, slack)) timed_out= 1; } poll_freewait(&table); returnretval; }
里面有几个关键点:
poll_initwait(&table);
table是在函数开头定义的的poll_wqueues类型的结构体
poll_wqueues结构体如下:
struct poll_wqueues { poll_table pt; struct poll_table_page *table; struct task_struct*polling_task; int triggered; int error; int inline_index; struct poll_table_entryinline_entries[N_INLINE_POLL_ENTRIES]; };
这个结构体是poll实现的关键结构体,因为它包含后面所要用到的structpoll_table_struct结构体,structpoll_table_page结构体以及structpoll_table_entry结构体。
这个函数的作用就是初始化这个poll_wqueues结构体:
voidpoll_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; } 其中init_poll_funcptr(&pwq->pt,__pollwait);是初始化poll_wqueues的内嵌的poll_table结构体: typedefstruct poll_table_struct { poll_queue_procqproc; unsignedlong key; }poll_table; staticinline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc) { pt->qproc= qproc; pt->key = ~0UL; /* all events enabled */ }
做完初始化动作后wait= &table.pt; 将wait指向内嵌的这个poll_table结构体。
第二步就是for循环,根据bitmap的每一个文件描述符,获取file结构体的指针,根据file指针获取设备驱动的关键f_op:
f_op= file->f_op;
if(f_op && f_op->poll) {
wait_key_set(wait,in, out, bit); //设定检测fd的事件类型
mask= (*f_op->poll)(file, wait); //调用驱动程序构造的poll函数
}
驱动构造的poll函数如下:
staticunsigned int scull_poll(struct file *filp, poll_table *wait) { unsignedint mask = 0; structscull_dev *dev = filp->private_data; down(&dev->sem); poll_wait(filp,&dev->r_wait, wait); poll_wait(filp,&dev->w_wait, wait); if(dev->current_len!= 0) { mask|= POLLIN | POLLRDNORM; } if(dev->current_len!= SCULL_SIZE ) { mask|= POLLOUT | POLLWRNORM; } up(&dev->sem); returnmask; } staticinline void poll_wait(struct file * filp, wait_queue_head_t *wait_address, poll_table *p) { if(p && wait_address) p->qproc(filp,wait_address, p); }
这里是调用前面注册的__pollwait函数
staticvoid __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table*p) { structpoll_wqueues *pwq = container_of(p, struct poll_wqueues, pt); structpoll_table_entry *entry = poll_get_entry(pwq); if(!entry) return; get_file(filp); entry->filp= filp; entry->wait_address= wait_address; entry->key= p->key; init_waitqueue_func_entry(&entry->wait,pollwake); entry->wait.private= pwq; add_wait_queue(wait_address,&entry->wait); }
利用container_of宏定义从指向poll_table结构体的指针获取指向poll_queues的指针;
structpoll_table_entry *entry =poll_get_entry(pwq);获取表项。回顾前面的poll_wqueues的结构,它包含一个poll_table_entry的结构体数组,poll_get_entry函数就是从这个数组中得到表项,当然,这个数组大小是有限制的,如果不够用了,就要用到structpoll_table_page结构体。在此,我们忽略这个表项的多页性质,理想的认为这个数组够用。
接下来就是填充这个表项poll_table_entry结构体;
structpoll_table_entry { structfile *filp; unsignedlong key; wait_queue_twait; wait_queue_head_t*wait_address; };
表项中的structfile *filp;指向设备文件的file结构体;
表项中的wait_queue_head_t*wait_address;指向设备结构体中dev->r_wait读或写队列头;
接着初始化表项中的wait_queue_twait;等待队列项:
init_waitqueue_func_entry(&entry->wait,pollwake); staticinline void init_waitqueue_func_entry(wait_queue_t *q, wait_queue_func_tfunc) { q->flags= 0; q->private= NULL; q->func= func; }
我们注意到在后面有个entry->wait.private= pwq;
在分析阻塞IO时,等待对象项的private一般都指向任务结构体:task_struct
这里插个问题:
这里为什么指向这个structpoll_wqueues *pwq 结构体?
我们往后看:
在需要唤醒的时候,调用pollwake函数:
staticint pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key) { structpoll_table_entry *entry; entry= container_of(wait, struct poll_table_entry, wait); if(key && !((unsigned long)key & entry->key)) return0; return__pollwake(wait, mode, sync, key); }
__pollwake函数:
staticint __pollwake(wait_queue_t *wait, unsigned mode, int sync, void*key) { structpoll_wqueues *pwq = wait->private; DECLARE_WAITQUEUE(dummy_wait,pwq->polling_task); /* * Although this function is called under waitqueue lock, LOCK * doesn't imply write barrier and the users expect write * barrier semantics on wakeup functions. The following * smp_wmb() is equivalent to smp_wmb() in try_to_wake_up() * and is paired with set_mb() in poll_schedule_timeout. */ smp_wmb(); pwq->triggered= 1; /* * Perform the default wake up operation using a dummy * waitqueue. * * TODO: This is hacky but there currently is no interface to * pass in @sync. @sync is scheduled to be removed and once * that happens, wake_up_process() can be used directly. */ returndefault_wake_function(&dummy_wait, mode, sync, key); }
在函数开头:
structpoll_wqueues *pwq = wait->private;
DECLARE_WAITQUEUE(dummy_wait,pwq->polling_task);
根据wait的private获取poll_wqueues结构体指针,根据pwq->polling_task初始化的dummy_wait等待队列项的任务结构体指针private将会指向正确的任务结构体—在voidpoll_initwait(struct poll_wqueues *pwq)中设定的
pwq->polling_task= current;
pwq->triggered= 1;
这个标志标识只要select用户进程被唤醒过,就不会再次进入超时休眠。
我们将在poll_schedule_timeout函数中看到
if(!pwq->triggered)
rc= schedule_hrtimeout_range(expires, slack, HRTIMER_MODE_ABS);
最后还是调用default_wake_function函数将dummy_wait->private指向的task_struct唤醒加入到进程调度的队列中。
所以,上面entry->wait.private=pwq;的目的是既可以正确设置task_struct(从而唤醒这个task_struct),更主要的是设定triggered这个标志量。
注册该等待队列项的唤醒回调函数:pollwake;
最后,将表项中的等待队列项加到dev->r_wait读或写队列头的队列中。
回到驱动的poll函数,根据具体情况返回一个位掩码,描述可能不必阻塞就立刻进行的操作。
根据返回的位掩码,设置res_inres_out res_ex的值。
if((mask & POLLIN_SET) && (in & bit)) { res_in|= bit; retval++; wait= NULL; } if((mask & POLLOUT_SET) && (out & bit)) { res_out|= bit; retval++; wait= NULL; } if((mask & POLLEX_SET) && (ex & bit)) { res_ex|= bit; retval++; wait= NULL; }
在core_sys_select函数中将刚才获取的res_inres_out res_ex值复制到用户空间:
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))
这样,应用程序就可以使用FD_ISSET来判断某个文件描述符是否可读或者可写。
值得一提的是:驱动中的poll函数本身并不会阻塞,但是select系统调用会阻塞地等待文件描述符簇中的至少一个可访问或超时。
if(end_time && !to) { expire= timespec_to_ktime(*end_time); to= &expire; } if(!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE, to, slack)) timed_out= 1;
这一段代码用于超时后的睡眠,从而select调用阻塞了。
至于唤醒,还是与阻塞IO中讲到的唤醒相似,遍历等待队列头下的等待队列项,不过这里调用的函数是pollwake函数来做唤醒的具体操作。
这样,对于不同文件的poll函数,select系统调用可以扩充poll_table_entry表项,把各个不同表项内嵌的等待队列项挂在不同的等待队列头下。这样,select系统调用就利用了等待队列以及内核的睡眠唤醒机制实现了监听多个不同的文件IO。
最后,举一个工作中遇到的例子:
主芯片ppc下面挂五个arm11内核的编解码芯片。将这五个编解码芯片抽象为ppc下面的设备,那么ppc来接受来自编解码芯片的编解码数据流时,就用到了poll函数。
大概的讲一下:
对这个设备定义一个读等待队列头
在poll函数中调用poll_wait函数
再定义了一个读的工作队列,如果有数据就会唤醒这个读等待队列头下的等待队列项。
那么在应用程序中,就可以用select系统调用来监听这5个编解码芯片是否有数据传到主芯片(是否可读)。当某一个或多个编解码芯片可读时,就可以接收到相应的数据了。