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. php error_log错误信息写入文件

  2. u3d调用c++ dll的DllNotFoundExceion 问题

    原文地址:http://blog.csdn.net/boren31/article/details/8778504 问题年年有,今年特别多. 开发环境: Windows  XP sp3 Visual  ...

  3. Spring-MVC案例:Spitter的笔记

    源码地址:https://github.com/Young4Dream/yan/tree/master/Maven_spittr 笔记: 1.当DispatcherServlet启动时,会创建Spri ...

  4. 【ML】概率图模型

    http://wenku.baidu.com/link?url=-Fa32JAnvwS8fyWgdPjYLNGvmor42lWCT6N7TehNQAnx4ZVmJtC0L0SgnaLtEFMB9Gzw ...

  5. DOTween中的Time.Scale

    因为在做游戏暂停的时候通常会使用Time.Scale = 0 ,可是暂停的时候UI如果需要继续有动画怎么办呢?在DoTween中只需要设置         tweener.SetUpdate(true ...

  6. can not connect to MySQL server on "10.30.48.153"(13)

    国庆节前好好的程序,完了回来愣是不能跑了! 真是纳闷了,而且邮件别人都发出去了,等于这跟别人一边使用一遍救火一样子了.     查了一下午,一直以为是机器mysql服务的问题,或者是我代码的问题.该找 ...

  7. EOF ---shell编程

    转自:http://blog.163.com/njut_wangjian/blog/static/1657964252013112152418345/ 在shell编程中,”EOF“通常与”<& ...

  8. 关于C中I/O缓冲区的解释

    用户程序调用C标准I/O库函数读写文件或设备,而这些库函数要通过系统调用把读写请求传给内核,最终由内核驱动磁盘或设备完成I/O操作.C标准库为每个打开的文件分配一个I/O缓冲区以加速读写操作,通过文件 ...

  9. ecplise部署gradle web项目

    gradle项目结构图: build.gradle apply plugin: 'java' apply plugin: 'war' //用来生成war apply plugin: 'eclipse- ...

  10. CocoaPods的 安装 /卸载/升级

    CocoaPods用来管理第三方框架 Mac 安装 Cocoapods 导引如果你的 Mac OSX 升级到 10.11.x+, 并且需要安装 Cocoapods, 可以参考本博客.安装 rubyMa ...