Poll源码剖析

Posted ukernel

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Poll源码剖析相关的知识,希望对你有一定的参考价值。

Poll

函数接口
#include<poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);

struct pollfd{
    int fd;
    short events;        //注册的事件
    short revents;        //实际发生的事件
};
基本操作
  • 注意事项
    • fds是描述符数组,nfds是监听描述符的个数,timeout是以毫秒为单位的超时值
    • 返回事件发生的描述符的总数。
    • 使用结束后,用户对刚刚传入的描述符数组进行轮询测试,看看那个pollfd上的事件已经改变了。
    • POLLHUPPOLLHUP不管有没有设置都是默认监听的。
  • 事件类型
    • POLLIN:数据可读
    • POLLRDNORM:普通数据可读
    • POLLRDBAND:优先数据可读(Linux不支持)
    • POLLPRI:高级有限数据可读(带外数据)
    • POLLOUT:数据可写
    • POLLWRNORM:普通数据可写
    • POLLWRBAND:优先数据可写
    • POLLRDHUP:TCP连接被对方关闭,或者对方关闭了写操作
    • POLLERR:错误,要使用getsocketopt进行清除
    • POLLHUP:挂起,比如该描述符上的写端被关闭,该端的描述符将收到该事件
    • POLLNVAL:文件描述符没有打开
  • 错误标志:
    • EBADF:一个结构体或者多个结构体中存在文件描述符
    • EFAULT:fds指向的地址超出了进程地址空间
    • EINTR:在请求事件发生时收到一个信号,可以重新发起调用
    • EINVAL:nfds参数超出了进程的软限制
    • ENOMEM:可用内存不足,无法完成请求

      性能分析
  • 时间角度:
    • 使用了两层for循环:其一是管理时间的循环,可以在在有事件发生或者定时器到期的条件下退出循环
    • 在循环之前会将进程的状态设置为TASK_INTERRUPTIBLE:进程会等待定时事件的发生,之后退出时正常
  • 空间角度:
    • 从用户的地址空间中将struct pollfd逐个拷贝进内核空间,构造成链表,然后进行轮训。有一个高级技术,使用链表混合使用堆与栈的空间。不过逐个将struct pollfd拷贝进内核地址空间,可能有点效率影响。
    • 内核为每一个关注的事件都会存在一个struct pollfd,与同一个fd存储在一个链表中,也就是说,链表的总长度= 文件描述符个数 × 关注的事件个数。将发生的事件从内核空间拷贝出来的时候也是逐个拷贝。

      源码分析
  • 基础数据结构:

    struct poll_list {
    struct poll_list *next;
    int len;
    struct pollfd entries[0];
    };
  • 通用函数:

    #define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list))  / sizeof(struct pollfd))
    #define min_t(type,x,y)  ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })
    #define max_t(type,x,y)  ({ type __x = (x); type __y = (y); __x > __y ? __x: __y; })
  • Order 0:asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,long timeout_msecs):接受用户空间传入的指针,经过拷贝后在内核空间进行操作

    asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,long timeout_msecs)
    {
    s64 timeout_jiffies;        //获得毫秒级的准确度
    int ret;
    
    if (timeout_msecs > 0) {        //根据传入的参数对数据进行处理
    #if HZ > 1000
        /* We can only overflow if HZ > 1000 */
        if (timeout_msecs / 1000 > (s64)0x7fffffffffffffffULL / (s64)HZ)
            timeout_jiffies = -1;
        else
    #endif
            timeout_jiffies = msecs_to_jiffies(timeout_msecs);
    } else {
        /* Infinite (< 0) or no (0) timeout */
        timeout_jiffies = timeout_msecs;
    }
    
    ret = do_sys_poll(ufds, nfds, &timeout_jiffies);        //做实际的操作
    if (ret == -EINTR) {        //如果被中断(也就是现实的告诉用户可以重复的进行调用),进行善后处理
        struct restart_block *restart_block;
        restart_block = &current_thread_info()->restart_block;
        restart_block->fn = do_restart_poll;
        restart_block->arg0 = (unsigned long)ufds;
        restart_block->arg1 = nfds;
        restart_block->arg2 = timeout_jiffies & 0xFFFFFFFF;
        restart_block->arg3 = (u64)timeout_jiffies >> 32;
        ret = -ERESTART_RESTARTBLOCK;
    }
    return ret;        //返回有事件发生的描述符的个数
    }
  • Order 1:int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)

    int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
    {
    struct poll_wqueues table;            //获得poll等待队列的操作
    int err = -EFAULT, fdcount, len, size;        //在还没有将用户空间的数据拷贝进来,将标志设置为默认出错
    long stack_pps[POLL_STACK_ALLOC/sizeof(long)];        //在栈上分配一点空间,POLL_STACK_ALLOC = 256
    struct poll_list *const head = (struct poll_list *)stack_pps;        //使用链表将描述pollfd的数据结构
    struct poll_list *walk = head;
    unsigned long todo = nfds;
    
    if (nfds > current->signal->rlim[RLIMIT_NOFILE].rlim_cur)        //如果注册的描述符大小超过当前的进程的软资源限制,直接返回错误标志
        return -EINVAL;
    
    len = min_t(unsigned int, nfds, N_STACK_PPS);        //获取用户传入struct pollfd的个数
    for (;;) {        //nb,挨个处理pollfd,混合栈空间和堆空间。。被秀了一脸。。
        walk->next = NULL;
        walk->len = len;
        if (!len)        //如果用户传入的len有0个,退出循环
            break;
    
        if (copy_from_user(walk->entries, ufds + nfds-todo,
                    sizeof(struct pollfd) * walk->len))        //一次从user空间中拷贝一个struct pollfd
            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_initwait(&table);        //初始化等待链表
    fdcount = do_poll(nfds, head, &table, timeout);        //获得发生事件的描述符个数
    poll_freewait(&table);        //释放等待链表
    
    for (walk = head; walk; walk = walk->next) {        //轮训所有描述符的链表
        struct pollfd *fds = walk->entries;        //获得fds数组
        int j;
    
        for (j = 0; 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);
    }
    
    return err;
    }
  • Order 2:static int do_poll(unsigned int nfds, struct poll_list *list,struct poll_wqueues *wait, s64 *timeout):在链表组成的pollfd上进行操作

    static int do_poll(unsigned int nfds,  struct poll_list *list,struct poll_wqueues *wait, s64 *timeout)
    {
    int count = 0;
    poll_table* pt = &wait->pt;
    
    if (!(*timeout))        //如果时间为0,也就是说只轮训一遍不管有没有事件发生都直接返回
        pt = NULL;
    
    for (;;) {        
        struct poll_list *walk;
        long __timeout;
    
        set_current_state(TASK_INTERRUPTIBLE);        //设置当前进程的状态
        for (walk = list; walk != NULL; walk = walk->next) {        //循环pollfd所在的链表
            struct pollfd * pfd, * pfd_end;
    
            pfd = walk->entries;
            pfd_end = pfd + walk->len;        //获得pollfd的描述
            for (; pfd != pfd_end; pfd++) {
                if (do_pollfd(pfd, pt)) {        //调用文件系统例程,对pfd进行处理
                    count++;
                    pt = NULL;
                }
            }
        }
    
        pt = NULL;
        if (!count) {        //如果没有事件发生
            count = wait->error;
            if (signal_pending(current))
                count = -EINTR;        //将设置为可以重复调用的状态
        }
        if (count || !*timeout)        //如果有事件发生或者事件用尽,打破循环,直接返回
            break;
    
        if (*timeout < 0) {        //和select一样对设置时间的处理
            __timeout = MAX_SCHEDULE_TIMEOUT;
        } else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) 
            __timeout = MAX_SCHEDULE_TIMEOUT - 1;
            *timeout -= __timeout;
        } else {
            __timeout = *timeout;
            *timeout = 0;
        }
    
        __timeout = schedule_timeout(__timeout);
        if (*timeout >= 0)
            *timeout += __timeout;
    }
    __set_current_state(TASK_RUNNING);        //将进程设置为正常的状态
    return count;        //返回准备就绪的文件描述符的个数
    }
  • Order 3:static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait):调用文件系统例程对pollfd进行处理

    static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
    {
    unsigned int mask;
    int fd;
    
    mask = 0;
    fd = pollfd->fd;
    if (fd >= 0) {
        int fput_needed;
        struct file * file;
    
        file = fget_light(fd, &fput_needed);
        mask = POLLNVAL;
        if (file != NULL) {
            mask = DEFAULT_POLLMASK;
            if (file->f_op && file->f_op->poll)
                mask = file->f_op->poll(file, pwait);        //文件系统例程
            mask &= pollfd->events | POLLERR | POLLHUP;
            fput_light(file, fput_needed);
        }
    }
    pollfd->revents = mask;
    
    return mask;
    }

以上是关于Poll源码剖析的主要内容,如果未能解决你的问题,请参考以下文章

Java Review - 并发编程_DelayQueue原理&源码剖析

《Docker 源码分析》全球首发啦!

Mybatis源码剖析:传统开发方式源码剖析

Spark源码剖析:stage划分原理与源码剖析

C语言源码剖析与实现——strtok()系列函数实现

fasttext源码剖析