1. int select(int nfds,
  2. fd_set *restrict readfds,
  3. fd_set *restrict writefds,
  4. fd_set *restrict errorfds,
  5.   struct timeval *restrict timeout);

  1. SYSCALL_DEFINE5(select, int, n,
  2. fd_set __user *, inp,
  3. fd_set __user *, outp,
  4. fd_set __user *, exp,
  5. struct timeval __user *, tvp)
  6. {
  7. ret = core_sys_select(n, inp, outp, exp, to);
  8. ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);
  9. return ret;
  10. }

core_sys_select 主要工作:

  1. 初始化读写还有异常的bitmap
  2. 调用 do_select 实现核心的轮询工作。
  3. 把结果拷贝会用户空间
  1. int core_sys_select(int n,
  2. fd_set __user *inp,
  3. fd_set __user *outp,
  4. fd_set __user *exp,
  5. struct timespec *end_time)
  6. {
  7. fd_set_bits fds;
  8. // …
  9. if ((ret = get_fd_set(n, inp, fds.in)) ||
  10. (ret = get_fd_set(n, outp, fds.out)) ||
  11. (ret = get_fd_set(n, exp, fds.ex))) //*get_fd_set仅仅调用copy_from_user从用户空间拷贝了fd_set*/
  12. goto out;
  13. zero_fd_set(n, fds.res_in);
  14. zero_fd_set(n, fds.res_out);
  15. zero_fd_set(n, fds.res_ex);
  16. //发现do_select函数
  17. ret = do_select(n, &fds, end_time);
  18.  /*把结果集,拷贝回用户空间*/
  19. if (set_fd_set(n, inp, fds.res_in) ||
  20.         set_fd_set(n, outp, fds.res_out) ||  
  21.         set_fd_set(n, exp, fds.res_ex))  
  22.         ret = -EFAULT;  
  23. }

  1. int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
  2. {
  3. struct poll_wqueues table;
  4. poll_table *wait;
  5. poll_initwait(&table);//这个函数实现很关键,其内部的 init_poll_funcptr 初始化回调函数为 __pollwait, 后面轮询会回调这个函数,然后通过这个函数把进程添加到对应的监听文件等待队列,当有事件到来时,就会唤醒这个进程。
  6. for (;;) {
  7. //一次大循环
  8. for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
  9. // …
  10. struct fd f;
  11. f = fdget(i);
  12. if (f.file) {
  13. const struct file_operations *f_op; //每个设备拥有一个struct file_operations结构体
  14. f_op = f.file->f_op;
  15. mask = DEFAULT_POLLMASK;
  16. if (f_op->poll) { //轮询函数不为空,每当设备模块加载就自动会加载设备轮询函数,等于将轮回函数统一付给poll这个指针,以便调用
  17. wait_key_set(wait, in, out,bit, busy_flag);//检查集合
  18. // 对每个fd进行I/O事件检测 (*f_op->poll)返回当前设备fd的状态(可读可写)
  19. mask = (*f_op->poll)(f.file, wait);//将会调用poll_wait函数,检测文件设备的状态,并且将当前进程加入到设备等待队列中。并且返回掩码
  20. }
  21. fdput(f);
  22. }
  23. }
  24. // 退出循环体
  25. if (retval || timed_out || signal_pending(current))
  26. break;
  27. // 轮询一遍没有发现就绪。那就休眠
  28. if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
  29. to, slack))
  30. timed_out = 1;
  31. }
  32. }
 


  1. 这个函数实现很关键,这里 init_poll_funcptr 初始化回调函数为 __pollwait, 后面轮询会回调这个函数,然后通过这个函数把进程添加到对应的监听文件等待队列,当有事件到来时,就会唤醒这个进程。
  2. poll_initwait(&table);
  3. void poll_initwait(struct poll_wqueues *pwq){//这里p->_qproc实际就是__pollwait函数,因为p->qproc在init_poll_funcptr中被赋值为__pollwait函数指针
  4. init_poll_funcptr(&pwq->pt, __pollwait); //初始化函数指针,设置为__pollwait
  5. pwq->error = 0;
  6. pwq->table = NULL;
  7. pwq->inline_index = 0;}
  8. static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc){
  9. pt->qproc = qproc;
  10. }

以下根据scull设备分析轮询函数
每个驱动设备对应一个fd
每个fd包含struct file_operations
struct file_operations 每个设备都对应一个这样的结构体
  1. struct file {
  2. struct path f_path;//路径
  3. struct inode *f_inode; //inode
  4. const struct file_operations *f_op; //包含各种用于操作设备的函数指针
  5. } __attribute__((aligned(4))); /* lest something weird decides that 2
  1. struct file_operations {
  2. struct module *owner;
  3. loff_t (*llseek) (struct file *, loff_t, int);
  4. ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
  5. // select()轮询设备fd的操作函数,对应一个file 跟poll_table_struct *
  6. unsigned int (*poll) (struct file *, struct poll_table_struct *); //驱动加载。一般就挂到这个地方轮询函数
  7. };
具体分析scull设备
每个设备都有一个这样的结构体。而这样的结构体基本都有一个等待队列
  1. struct scull_pipe {
  2. wait_queue_head_t inq, outq; //可读可写队列
  3. };
这个设备的轮询操作函数是scull_p_poll.驱动模块加载,这个函数就被挂到(*poll)函数指针sk;
返回当前设备的I/O状态,并且调用了poll_wait函数,将当前进程加入到等待队列,把wait_queue_head_t队列当做参数传入
  1. static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
  2. {
  3. struct scull_pipe *dev = filp->private_data;
  4. unsigned int mask = 0;
  5. mutex_lock(&dev->mutex);
  6. poll_wait(filp, &dev->inq, wait);//pollwait函数包含了__pollwait.这函数就是把当前进程添加到设备队列中
  7. poll_wait(filp, &dev->outq, wait);//等待
  8. if (dev->rp != dev->wp)
  9. mask |= POLLIN | POLLRDNORM; //可读
  10. if (spacefree(dev))
  11. mask |= POLLOUT | POLLWRNORM; //可写
  12. mutex_unlock(&dev->mutex);
  13. return mask;//返回该设备的掩码,是否就绪可读可写
  14. }
注意poll_wait函数,把设备自己的等待队列给传进去了,还传了一个poll_table
看看poll_wait函数的最主要功能就是调用__pollwait将当前进程添加到设备等待队列
  1. static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
  2. {
  3. if (p && p->_qproc && wait_address)
  4. p->_qproc(filp, wait_address, p);//这里p->_qproc实际就是__pollwait函数,因为p->qproc在do_select中被赋值为__pollwait函数指针
  5. }
poll_table结构体包含 poll_queue_proc _qproc,unsigned long _key; 2个变量,
其中第一变量是一个函数指针
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

我们找下poll_table的初始化在哪
poll_table里的函数指针,是在do_select()初始化的。
  1. int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
  2. {
  3. struct poll_wqueues table;
  4. poll_table *wait;
  5. poll_initwait(&table);//初始化
  6. }
  7. void poll_initwait(struct poll_wqueues *pwq)
  8. {
  9. // 初始化poll_table里的函数指针
  10. init_poll_funcptr(&pwq->pt, __pollwait);
  11. }
  12. EXPORT_SYMBOL(poll_initwait);
  13. static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
  14. {
  15. pt->_qproc = qproc;//将poll_table的函数指针设置为__pollwait完成初始化工作
  16. pt->_key = ~0UL; /* all events enabled */
  17. }
  1. static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
  2. poll_table *p)
  3. {
  4. // 把当前进程装到设备的等待队列
  5. add_wait_queue(wait_address, &entry->wait);
  6. }
如果当设备有数据可写的时候。将调用此函数那将此等待可写的队列中的进程唤醒
  1. static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count,
  2. loff_t *f_pos)
  3. {
  4. wake_up_interruptible(&dev->inq); //唤醒当前进程
  5. }
  1. select慢的原因
  2. 从上面看,在第一次所有监听都没有事件时,调用 select 都需要把进程挂到所有监听的文件描述符一次。
  3. 有事件到来时,不知道是哪些文件描述符有数据可以读写,需要把所有的文件描述符都轮询一遍才能知道。
  4. 通知事件到来给用户进程,需要把整个 bitmap 拷到用户空间,让用户空间去查询。
  5. select返回时,会将该进程从全部监听的fd的等待队列里移除掉,这样就需要select每次都要重新传入全部监听的fd,然后重现将本进程挂载到全部的监测fd的等待队列









Select函数实现的更多相关文章

  1. (十二)select()函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET

    select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型,原型:int select(int maxfd,fd_set *rdset ...

  2. select 函数1

    Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect.accept.recv或recvfrom这样的阻塞程序( ...

  3. select()函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET

    http://hi.baidu.com/%B1%D5%C4%BF%B3%C9%B7%F0/blog/item/e7284ef16bcec3c70a46e05e.html select函数用于在非阻塞中 ...

  4. PHP Socket实现websocket(四)Select函数

    int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout); /* ...

  5. IO复用与select函数

    socket select函数的详细讲解 select函数详细用法解析      http://blog.chinaunix.net/uid-21411227-id-1826874.html linu ...

  6. I/O多路复用——select函数与poll函数

    1 区别 同:(1)机制类似,本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理.(2)包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就 ...

  7. select函数

    select函数: http://baike.baidu.com/view/3421856.htm select函数   目录 概况 操作程序 宏解释 socket读写 概况 select()的机制中 ...

  8. select()函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET(转)

    select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型, 原型: int select(int maxfd,fd_set *rds ...

  9. 阻塞、非阻塞的概念和select函数的阻塞功能

    其它文档: http://www.cnitblog.com/zouzheng/archive/2010/11/25/71711.html (1)阻塞block     所谓阻塞方式block,顾名思义 ...

  10. select()函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET (转)

    select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型,原型: #include <sys/time.h>       ...

随机推荐

  1. ios 设计模式总结

    设计模式:备注:消息传递模型(Message Passing)是Objective-C语言的核心机制.在Objective-C中,没有方法调用这种说法,只有消息传递.在C++或Java中调用某个类的方 ...

  2. bp神经网络原理

    bp(back propagation)修改每层神经网络向下一层传播的权值,来减少输出层的实际值和理论值的误差 其实就是训练权值嘛 训练方法为梯度下降法 其实就是高等数学中的梯度,将所有的权值看成自变 ...

  3. Laravel核心解读--Console内核

    Console内核 上一篇文章我们介绍了Laravel的HTTP内核,详细概述了网络请求从进入应用到应用处理完请求返回HTTP响应整个生命周期中HTTP内核是如何调动Laravel各个核心组件来完成任 ...

  4. 转载:将画布(canvas)图像保存成本地图片的方法

    之前我曾介绍过如何将HTML5画布(canvas)内容转变成图片形式,方法十分简单.但后来我发现只将canvas内容转变成图片输出还不够,如何能将转变后的图片保存到本地呢? 其实,这个方法也是非常简单 ...

  5. setup/hold 分析

    分析说明:D2:目的寄存器:D1:源寄存器: edge2:下一个时钟上升沿:edge1:当前时钟上升沿:edge0:当前时钟上升沿的前一个时钟沿:如下图: 建立时间:触发器D2(数据要到达目的的地方) ...

  6. 【js】【vue】获取当前dom层

    多层嵌套,$event.currentTarget 指当前点击层

  7. 《零基础入门学习Python》【第一版】视频课后答案第002讲

    测试题答案: 0. 什么是BIF?BIF 就是 Built-in Functions,内置函数.为了方便程序员快速编写脚本程序(脚本就是要编程速度快快快!!!),Python 提供了非常丰富的内置函数 ...

  8. Hibernate知识梳理

    一.SessionFactory接口 是单个数据库映射关系(ORM)经过编译后的内存镜像.SessionFactory(的实例)作为应用中的一个全局对象(工厂),可以随处打开/创建一个session, ...

  9. leetcode-11-dfs

    DFS算法: explore(G, v) visited(v) = trueprevisit(v) for each edge(v, u) in E: if not visited(u): explo ...

  10. poj 3190 奶牛挤奶问题 贪心算法

    题意:奶牛挤奶问题,每只奶牛在[a,b]的时间内挤奶,要求挤奶的过程中不能不打扰,且只能自己一个人独享挤奶的机器.问最少需要多少个挤奶的机器? 思路: 对奶牛挤奶开始的时间从小到大开始排序. 将正在工 ...