POLL机制的作用这里就不进行介绍,根据linux man手册,解释为在一个文件描述符上等待某个事件。按照抽象一点的理解,当某个事件被触发(条件被满足),文件描述符变为有状态,那么用户空间可以根据此进行操作,结合多个文件描述符,可以实现文件描述符的无阻塞访问。其实个人感觉这里的无阻塞主要是在监听多个文件描述符的情况下,把多个文件描述符放在一起管理,哪个有状态了就处理哪个,这样好像在调用具体的处理函数前比如read调用前,加了一层管理层用于检查是否可以进行操作,当所有的文件描述符都不可用,还是要阻塞,poll函数原型如下:

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

首个参数fds是一个数组指针,指向文件描述符集合,每个文件描述符对应一个pollfd 结构,nfds表示fd的个数,timeout指定阻塞的时间,单位为毫秒。用户空间调用后该函数后,经过poll系统调用进入内核,看下内核的实现在select.c文件中

SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,int, timeout_msecs)

主要还是调用了do_sys_poll()函数

int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
struct timespec *end_time)
{
struct poll_wqueues table;
int err = -EFAULT, fdcount, len, size;
/* Allocate small arguments on the stack to save memory and be
faster - use long to make sure the buffer is aligned properly
on 64 bit archs to avoid unaligned access */
long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
struct poll_list *const head = (struct poll_list *)stack_pps;
struct poll_list *walk = head;
unsigned long todo = nfds; if (nfds > rlimit(RLIMIT_NOFILE))
return -EINVAL; len = min_t(unsigned int, nfds, N_STACK_PPS);
/*fd通过walk管理,多个walk形成链表*/
for (;;) {
walk->next = NULL;
walk->len = len;
if (!len)
break;
/*每次copy len个fd*/
if (copy_from_user(walk->entries, ufds + nfds-todo,
sizeof(struct pollfd) * walk->len))
goto out_fds; todo -= walk->len;
if (!todo)
break; len = min(todo, POLLFD_PER_PAGE);
size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;
walk = walk->next = kmalloc(size, GFP_KERNEL);
if (!walk) {
err = -ENOMEM;
goto out_fds;
}
}
/*通过一次poll提交的fd共用一个table*/
poll_initwait(&table);
fdcount = do_poll(nfds, head, &table, end_time);
poll_freewait(&table);
/*向用户空间返回监控结果,主要是当前的可用事件*/
for (walk = head; walk; walk = walk->next) {
struct pollfd *fds = walk->entries;
int j; for (j = ; j < walk->len; j++, ufds++)
/*把实际的结果返回给用户空间*/
if (__put_user(fds[j].revents, &ufds->revents))
goto out_fds;
} err = fdcount;
out_fds:
/*释放内存*/
walk = head->next;
while (walk) {
struct poll_list *pos = walk;
walk = walk->next;
kfree(pos);
}
/*正常情况下返回fd 的数目*/
return err;
}

文件描述符在内核中通过poll_list结构管理,每个poll_list管理一定数目的fd,多个poll_list形成一条链表,首个poll_list是在栈上分配的,用以加速访问。但是如果一个poll_list不够用,则必须要进行再次分配,再次就通过kmalloc进行分配了,看下代码,首先在栈上 申请了POLL_STACK_ALLOC个字节的空间,按照8个字节对齐,然后转化成了poll_list指针。如果参数中指定的nfds大于RLIMIT_NOFILE,则返回错误,否则在N_STACK_PPS和nfds中取小者作为首个poll_list的长度,即fd的个数。下面进入一个死循环,主要目的是分批次拷贝fd.没什么好说的,看下poll_list结构

struct poll_list {
  struct poll_list *next;//连接下一个poll_list
  int len;//该list中fd的数目
  struct pollfd entries[0];//每个entry对应一个fd
};

拷贝完之后,需要进行核心处理了。首先初始化一个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_entry inline_entries[N_INLINE_POLL_ENTRIES];
};

pt是一个poll_table_struct结构,该结构内容如下,前者_qproc是一个函数指针,在驱动的poll函数中会对齐进行调用,主要是负责吧当前进程加入到等待队列中,key是请求的事件掩码,默认请求所有可用事件。

typedef struct poll_table_struct {
poll_queue_proc _qproc;//函数指针,一般负责吧当前进程加入到等待队列
unsigned long _key;//请求的事件掩码
} poll_table;

table指向poll_table_page结构,该结构管理poll_table_entry,每一个entry对应一个fd,所有的poll_table_page形成链表,不过在poll_table_page结构之前,优先使用的是poll_wqueues结构中的inline_entries,该数组是伴随poll_wqueues一起申请,inline_index指向其中的最后一个已经使用的元素,用以加速分配,OK结构描述就到这里,看函数代码。对poll_wqueues初始化后就调用了do_poll,该函数的核心功能就是对每一个fd,调用驱动的poll函数,从而获取各个fd的状态。代码简单列举下

for (;;) {
struct poll_list *walk; for (walk = list; walk != NULL; walk = walk->next) {
struct pollfd * pfd, * pfd_end; pfd = walk->entries;
pfd_end = pfd + walk->len;
for (; pfd != pfd_end; pfd++) {
/*
* Fish for events. If we found one, record it
* and kill poll_table->_qproc, so we don't
* needlessly register any other waiters after
* this. They'll get immediately deregistered
* when we break out and return.
*/
if (do_pollfd(pfd, pt)) {
count++;
pt->_qproc = NULL;
}
}
}
/*
* All waiters have already been registered, so don't provide
* a poll_table->_qproc to them on the next loop iteration.
*/
pt->_qproc = NULL;
if (!count) {
count = wait->error;
if (signal_pending(current))
count = -EINTR;
}
if (count || timed_out)
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(wait, TASK_INTERRUPTIBLE, to, slack))
timed_out = ;
}

核心业务都在这一个for循环中实现,前半部分正是针对每一个pfd调用do_pollfd函数,该函数是一个内敛函数,用以调用具体的poll函数来获取fd状态,并记录到pollfd->revents中。需要注意的是,虽然对各个fd均调用了poll函数,但是并不是所有的调用都会把进程加到等待队列,只要找到任何一个有状态的fd,即do_pollfd返回非0,则之后的poll调用均不会把进程加入到等待队列。因为加入到等待队列的本质目的是让该进程在阻塞的时候可以被正常唤醒。但是现在既然存在fd有状态,那么本次调用就不会阻塞,所以就不用在加入等待队列了。count记录有状态的fd的个数。如果count为0,就有三种可能性

1、如果有信号需要处理,则返回处理信号

2、如果超时了,则返回

3、阻塞,这是会触发调度器,下次调度还是会挨个检查fd状态,不过不会加入等待队列了。

而如果count不为0,则直接返回了。

以马内利

参考资料

linux3.10.1内核源码

linux IO多路复用POLL机制深入分析的更多相关文章

  1. Linux IO多路复用 poll

    Linux IO多路复用 poll 之前曾经提到过 select poll 跟select类似,poll改进了select的一个确定,就是poll没有监听上限 不过poll还是需要遍历以及频繁的把数组 ...

  2. IO多路复用——poll

    1.基本知识 poll是Linux中的字符设备驱动中的一个函数.Linux 2.5.44版本后,poll被epoll取代.和select实现的功能差不多,poll的作用是把当前的文件指针挂到等待队列. ...

  3. 【知乎网】Linux IO 多路复用 是什么意思?

    提问一: Linux IO多路复用有 epoll, poll, select,知道epoll性能比其他几者要好.也在网上查了一下这几者的区别,表示没有弄明白. IO多路复用是什么意思,在实际的应用中是 ...

  4. Linux IO多路复用

    监听文件描述符的状态来进行相应的读写操作,3个函数: 123 selectpollepoll 123456789 int (int nfds, fd_set *readfds, fd_set *wri ...

  5. Linux IO多路复用 select

    Linux IO多路复用 select 之前曾经写过简单的服务器,服务器是用多线程阻塞,客户端每一帧是用非阻塞实现的 后来发现select可以用来多路IO复用,就是说可以把服务器这么多线程放在一个线程 ...

  6. IO多路复用的机制:select、poll、epoll

    select.poll.epoll之间的区别总结[整理] IO多路复用之epoll总结 我读过的最好的epoll讲解--转自”知乎“

  7. Linux驱动之poll机制的理解与简单使用

    之前在Linux驱动之按键驱动编写(中断方式)中编写的驱动程序,如果没有按键按下.read函数是永远没有返回值的,现在想要做到即使没有按键按下,在一定时间之后也会有返回值.要做到这种功能,可以使用po ...

  8. Linux通信之poll机制分析

    poll机制分析 韦东山 2009.12.10 所有的系统调用,基于都可以在它的名字前加上“sys_”前缀,这就是它在内核中对应的函数.比如系统调用open.read.write.poll,与之对应的 ...

  9. Linux IO多路复用 select/poll/epoll

    Select -- synchronius I/O multiplexing select, FS_SET,FD_CLR,FD_ISSET,FD_ZERO #include <sys/time. ...

随机推荐

  1. Unity脚本中各函数成员的生命周期

    在学习Unity时,掌握如何编写脚本是必须掌握的一项基本技能.但是关于Unity的游戏脚本中各函数的生命周期是怎样开始和结束的,它们的执行顺序是如何安排的?这一点我们要清楚的了解. 我们知道Unity ...

  2. 【Java面试题】59 Math.round(11.5)等於多少? Math.round(-11.5)等於多少?

    Math类中提供了三个与取整有关的方法:ceil.floor.round,这些方法的作用与它们的英文名称的含义相对应,例如,ceil的英文意义是天花板,该方法就表示向上取整,Math.ceil(11. ...

  3. php中常用$_SERVER的用法

    #测试网址: http://localhost/blog/testurl.php?id=5 //获取域名或主机地址 echo $_SERVER['HTTP_HOST']."<br> ...

  4. 有限状态机FSM详解及其实现

    有限状态机,也称为FSM(Finite State Machine),其在任意时刻都处于有限状态集合中的某一状态.当其获得一个输入字符时,将从当前状态转换到另一个状态,或者仍然保持在当前状态.任何一个 ...

  5. Windows中目录及文件路径太长无法删除的解决方法

    用windows自带的命令解决  win7以上的系统有 robocopy 命令 http://www.jianshu.com/p/95a269951a1b 导致目录太深的原因就是用node中的node ...

  6. 【ExtJs】 ExtJs4.2 基本表单组件的使用

    包含ExtJs 基本的组件radioGroup,ComboBox,File,NumberField... <%-- Created by IntelliJ IDEA. User: Adminis ...

  7. fork函数和vfork函数的区别--19

    fork()与vfock()都是创建一个进程,那他们有什么区别呢?总结有以下三点区别: 1.  fork  ():子进程拷贝父进程的数据段,代码段     vfork ( ):子进程与父进程共享数据段 ...

  8. linux--解决oracle sqlplus 中上下左右backspace不能用

    1.  解决不能backspace 方法1: stty erase ^h 在oracle用户下:在用户环境配置文件.bash_profile中加入如下语句 stty erase ^h 方法2:在sec ...

  9. ajax的原理及实现方式

    Ajax:Asynchronous javascript and xml,实现了客户端与服务器进行数据交流过程同时是异步发送请求.使用技术的好处是:不用页面刷新,并且在等待页面传输数据的同时可以进行其 ...

  10. 数据库客户端快捷键(oracle+sybase)

    PL/SQL: 选中单行:鼠标三连击某行,那么这一行即被选中. 执行脚本:F8