您的位置:首页 > 运维架构 > Linux

LINUX非阻塞访问机制POLL SELECT EPOLL原理分析

2016-11-29 10:55 253 查看


LINUX非阻塞访问机制POLL SELECT EPOLL原理分析

相关源码版本:
LINUX内核源码版本:linux-3.0.86
UBOOT版本:uboot-2010.12.
Android系统源码版本:Android-5.0.2】
 
Linux系统提供几种多种实现非阻塞访问机制(read write操作时不会阻塞,但对于POLL
SELECT EPOLL的操作在驱动层代码也是不阻塞的,而是在系统调用sys_poll等下面有休眠功能):POLL SELECT EPOLL等机制;应用层和内核层的调用关系,既实现原理如下图:
A:Poll(API层)--->__ppoll()[#define
__NR_ppoll (__NR_SYSCALL_BASE+336)
]->---syscall(kernel
层)--->sys_ppoll--->do_sys_poll--->do_poll--->do_pollfd--->Poll(file_operations.poll)
 
B:Select(API层)--->__pselect6[#define
__NR_pselect6 (__NR_SYSCALL_BASE+335)
]->----syscall(kernel
层)--->sys_pselect6--->do_pselect--->do_select--->Poll((file_operations.poll)
 
C:Epoll([分三个函数:epoll_create epoll_ctl和epoll_wait(api层)->
->---syscall(kernel
层)--->Poll((file_operations.poll)[这种方式没有去分析  具体细节不清楚,这儿列出的调用方式也不一定对]
以上几种在本质上原理是一样的,本文着重分析B:Select(API层)->----syscall(kernel
层)--->Poll((file_operations.poll)的实现原理。我们从应用层到内核层一步步分析处理过程来理解POLL机制,平常所说的POLL机制是内核驱动接口fops->poll函数是固定的来称它的,因为无论应用层是由SELECT还是POLL调用都到内核层再到驱动接口最后都是fops-poll函数,中间只是针对这两种函数的封装和一些差异化处理。
安桌层及应用层:
int select(int fd_count, fd_set* read_fds, fd_set* write_fds, fd_set* error_fds, timeval* tv)

下面分析他的每一个参数,理解了参数才能应用自如。
fd_count:要监控的所有文件描述符中最大那一个文件描述符加1。
read_fds:要监控的可读的文件描述集。FD_SET宏来设置可读文件描述集。
write_fds:要监控的可写的文件描述集。FD_SET宏来设置可写文件描述集。
error_fds:要监控的有文件异常的描述集。FD_SET来设置它。
Tv:如果为NULL则一直休眠直到有第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述
 
FD_ISSET:宏用于判断select函数执行之后,是否有可读可以写或异常文件。应用层根据这个宏来判断得到状态,继而继续处理相关逻辑。
 
下面网友分析的的fd_set原理,觉得写的很易懂,就直接COPY了感谢此大神。
 


 
内核层。
Sys_pselect6->do_pselect->core_sys_select->do_select->poll_initwait->f_op->poll进入驱动注册的poll函数
。每个poll函数里面都会调用poll_wait函数。它的作用就是用poll_initwait里面初始化的一个__pollwait函数来把当前进程挂载进poll_wait传进来的等待队列头中。流程是:poll_wait->pt->qproc=__pollwait----->
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
poll_table
*p)---->add_wait_queue(wait_address, &entry->wait);当前进程加入待待队列头中--->这样驱动函数中poll执行完之后返回内核层根据TV值来决定是否休眠。
然后驱动中根据状态来唤醒等待队列头中的等待队列。这样我们前面休眠的等待队列就可以根据驱动唤醒函数唤醒它了。状态满足了就唤醒继而继续执行,可以把状态返回给应用层。这样就实现了一个select函数监控了多个文件,也就是IO多路复用的体现。真正休眠是在do_select函数的
for (;;) {

mask = (*f_op->poll)(file, wait);
if (retval || timed_out || signal_pending(current))
break;
if (table.error) {
retval = table.error;

break;
 
}if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
   to,
slack))
timed_out = 1;
//执行完这个函数进入休眠,当有状态满足时唤醒。然后继续执行上面的for函数,再次进入poll函数,由于唤醒发生则里面一定有状态满足了,因此mask
= (*f_op->poll)(file, wait);应该有满足条件的。上面BREAK处就会退出for循环了,然后就继续执行for循环后的代码然后把结果返回给应层,既监控层,这样就完成了一次监控,实现了的我们目标。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: