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. Unity3d 判断物体是否在可见范围内

    unity中自带两个回调函数: void OnBecameVisible()//当物体可见时,回调一次. void OnBecameInvisible()//当物体不可见时,回调一次. 在untiy编 ...

  2. 11Vim文本编辑器

    Vim文本编辑器 在Linux系统中一切都是文件,而配置一个服务就是在修改其配置文件的参数. Vim提供了三种模式:命令模式.输入模式.末行模式 1.命令模式 每次运行Vim编辑器时,默认进入命令模式 ...

  3. 【线程池】ExecutorService与quartz搭配出现的问题

    问题描述: 使用quartz定时推送微信公众号模板消息,一分钟推送一次,定时器里面使用了一个ExecutorService线程池,大小为5个. 批量获取数据之后,全部数据都被分配到n/5的线程池里面等 ...

  4. webpack4搭建Vue开发环境笔记~~持续更新

    项目git地址 一.node知识 __dirname: 获取当前文件所在路径,等同于path.dirname(__filename) console.log(__dirname); // Prints ...

  5. Linux 安装Nginx+PHP+MySQL教程

    一.安装nginx 通过yum安装openssl: yum -y install openssl openssl-devel 通过yum安装pcre: yum -y install pcre-deve ...

  6. my购物车

    sum=0 a=input("请输入“水果”或“衣服”:") if a=="手机": while True: shop = { '蓝葡萄', '水蜜桃', '草 ...

  7. 用Python实现小说中的汉字频率统计

     环境: Python 3的代码,亲测可用. 思路: 是先把每个字符提出来放在列表里:再过滤掉其中的标点符号:最后用字典对某个字出现的频率进行累加. 扩展: 用处很多,稍微改改,既可以用来统计小说或文 ...

  8. LeetCode(117) Populating Next Right Pointers in Each Node II

    题目 Follow up for problem "Populating Next Right Pointers in Each Node". What if the given ...

  9. luogu1233 木棍加工

    先排个序然后做最长上升子序列就行了. #include <algorithm> #include <iostream> #include <cstdio> usin ...

  10. webdriver高级应用- 测试HTML5语言实现的视频播放器

    能够获取HTML5语言实现的视频播放器,视频文件的地址.时长,控制播放器进行播放或暂停播放等操作. #encoding=utf-8 import unittest from selenium impo ...