zoukankan      html  css  js  c++  java
  • Select函数实现

    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的等待队列













  • 相关阅读:
    thinkphp使用ajax程序报500错误
    非隐藏转发和隐藏转发的区别及选择
    表单文件(图片)上传到服务器,权限自动变成363,无法访问
    我收到了互联网应急中心的通报!记sqlmap的正确打开方式。
    css字体可以小于12px!被小米官网打脸
    阿里云CDN添加域名80端口和443端口什么区别?
    网站使用海外服务器,国内访问很慢的解决方案
    linux下设置php文件不区分大小写
    国际化
    Spring boot2.0学习笔记(一)
  • 原文地址:https://www.cnblogs.com/zengyiwen/p/5755205.html
Copyright © 2011-2022 走看看