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. 简单html弹窗

    css: <style type="text/css"> .moneyrecord { display:none; border:0.5em solid #00AAEE ...

  2. MathType中常遇到的几个问题

    每次打开别人的论文,发现公式文字都排版得非常整齐,公式也编辑得非常漂亮,看起来就非常得赏心悦目.再打开自己的论文,一片凌乱,自己不想都再看,公式编辑得乱七八糟,符号不够规范,大小不够统一,你自己都觉得 ...

  3. SQLServer------Sql Server性能优化辅助指标SET STATISTICS TIME ON和SET STATISTICS IO ON

    转载: http://www.cnblogs.com/xqhppt/p/4041799.html

  4. 高级类特性----final关键字

    final 关键字 在Java中声明类.属性和方法时,可使用关键字final来修饰. final标记的变量(成员变量或局部变量)即成为常量,只能赋值一次. final标记的类不能被继承.提高安全性,提 ...

  5. swift--获取window

    有时候,我们需要频繁的调用界面,然后给当前页面加一个跟视图,这个时候就需要找windown, 代码如下: let rootVC = UIApplication.shared.delegate as! ...

  6. ios button标记

    在写项目的时候,for循环创建多个button,在需要设置背景图片和,需要标记所选中的button的需求, 在这里提供两种方法: 一: 1:把for循环创建的button全部装到一个新建的数组中,把他 ...

  7. Bitmap之安卓手机壁纸的设置

    MainActivity: package com.hyzhou.imagedemo; import java.io.File; import android.os.Bundle; import an ...

  8. ssh通过密钥进行连接

    sshd服务提供两种安全验证的方法: 基于口令的安全验证:经过验证帐号与密码即可登陆到远程主机. 基于密钥的安全验证:需要在本地生成"密钥对"后将公钥传送至服务端,进行公共密钥的比 ...

  9. TextSwitcher实现文本自动垂直滚动

    实现功能:用TextSwitcher实现文本自动垂直滚动,类似淘宝首页广告条. 实现效果: 注意:由于网上横向滚动的例子比较多,所以这里通过垂直的例子演示. 实现步骤:1.extends TextSw ...

  10. iOS 9 分屏多任务:入门(中文版)

    本文转载至 http://www.cocoachina.com/ios/20150714/12555.html 本文由钢铁侠般的卿哥(微博)翻译自苹果官方文档:Adopting Multitaskin ...