前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >io复用之poll源码分析(基于linux2.6.13.1)

io复用之poll源码分析(基于linux2.6.13.1)

作者头像
theanarkh
发布2020-04-14 17:23:23
6450
发布2020-04-14 17:23:23
举报
文章被收录于专栏:原创分享原创分享

poll系统调用是io复用早期的实现,和select、epoll类似。今天来分析一下他的原理。先看一下poll的声明。

代码语言:javascript
复制
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

再看一下相关的数据结构。

代码语言:javascript
复制
struct pollfd {
    int   fd;        
    short events;     /* 用户感兴趣的事件 */
    short revents;    /* 系统触发的事件 */
};

下面我们开始分析sys_poll函数(poll函数对应的系统调用)。

代码语言:javascript
复制
struct poll_wqueues table;
table->pt->qproc = __pollwait;
table->error = 0;
table->table = NULL

首先初始化一个结构体。poll_wqueues 定义如下。

在这里插入图片描述 接着申请内存把用户传递的数据复制到内核。

代码语言:javascript
复制
    head = NULL;
    walk = NULL;
    i = nfds;
    err = -ENOMEM;
    while(i!=0) {
        struct poll_list *pp;
        // 申请一页大小的内存,保存一个poll_list结构体和多个pollfd结构体,大于一页的,再循环这个过程
        pp = kmalloc(sizeof(struct poll_list)+
                sizeof(struct pollfd)*
                (i>POLLFD_PER_PAGE?POLLFD_PER_PAGE:i),
                    GFP_KERNEL);
        pp->next=NULL;
        // 记录本次复制的pollfd结构体个数
        pp->len = (i>POLLFD_PER_PAGE?POLLFD_PER_PAGE:i);
        // 构造链表
        if (head == NULL)
            head = pp;
        else
            walk->next = pp;
        // 执行当前的poll_list,poll_list形成一个链表
        walk = pp;
        // 复制用户的数据到内核
        copy_from_user(pp->entries, ufds + nfds-i, sizeof(struct pollfd)*pp->len);
        // 剩下待复制的个数
        i -= pp->len;
    }

复制完成后结构如下。

在这里插入图片描述 接着开始poll文件描述符,看是否准备好了。

代码语言:javascript
复制
// nfds文件描述符个数,head保存了文件描述符和事件的链表头指针,table用于挂起进程,timeout最多poll多久
fdcount = do_poll(nfds, head, &table, timeout);

下面看一下do_poll的实现(省略部分代码)。

代码语言:javascript
复制
static int do_poll(unsigned int nfds,  struct poll_list *list,
            struct poll_wqueues *wait, long timeout)
{
    int count = 0;
    poll_table* pt = &wait->pt;
    // timeout为空,说明即使没有就绪事件也不需要阻塞
    if (!timeout)
        pt = NULL;

    for (;;) {
        struct poll_list *walk;
        walk = list;
        while(walk != NULL) {
            do_pollfd( walk->len, walk->entries, &pt, &count);
            walk = walk->next;
        }
        // count代表有没有就绪事件,timeout 说明没有设置超时或者已经超时,signal_pending代表有信号需要处理
        if (count || !timeout || signal_pending(current))
            break;
        // 挂起进程,timeout后被唤醒
        timeout = schedule_timeout(timeout);
    }
    return count;
}

就是遍历刚才构造的链表,如果没有就绪的时候,并且设置了超时,也没有信号需要处理,则挂起进程,等待唤醒(有就绪事件或者超时都会被唤醒)。我们看遍历的时候,对每个pollfd结构体做了什么。

代码语言:javascript
复制
static void do_pollfd(unsigned int num, struct pollfd * fdpage,
    poll_table ** pwait, int *count)
{
    int i;

    for (i = 0; i < num; i++) {
        int fd;
        unsigned int mask;
        struct pollfd *fdp;

        mask = 0;
        // 当前当处理的pollfd结构体
        fdp = fdpage+i;
        // 待处理文件描述符
        fd = fdp->fd;
        if (fd >= 0) {
            // 获取fd对应的file结构体 
            struct file * file = fget(fd);
            mask = POLLNVAL;
            if (file != NULL) {
                mask = DEFAULT_POLLMASK;
                if (file->f_op && file->f_op->poll)
                    // mask记录就绪的事件
                    mask = file->f_op->poll(file, *pwait);
                // 过滤掉不感兴趣的
                mask &= fdp->events | POLLERR | POLLHUP;
                fput(file);
            }
            // 有就绪事件,记录
            if (mask) {
                *pwait = NULL;
                (*count)++;
            }
        }
        // 记录就绪的事件
        fdp->revents = mask;
    }
}

就是调用各个功能实现的poll函数。判断是否有事件就绪。我们以pipe为例看一下poll函数的大致实现。

代码语言:javascript
复制
    static unsigned int
pipe_poll(struct file *filp, poll_table *wait)
{
    ...
    /* Reading only -- no need for acquiring the semaphore.  */
    nrbufs = info->nrbufs;
    mask = 0;
    if (filp->f_mode & FMODE_READ) {
        mask = (nrbufs > 0) ? POLLIN | POLLRDNORM : 0;
        if (!PIPE_WRITERS(*inode) && filp->f_version != PIPE_WCOUNTER(*inode))
            mask |= POLLHUP;
    }

    if (filp->f_mode & FMODE_WRITE) {
        mask |= (nrbufs < PIPE_BUFFERS) ? POLLOUT | POLLWRNORM : 0;
        if (!PIPE_READERS(*inode))
            mask |= POLLERR;
    }

    return mask;
}

判断一下是否有事件就绪了。如果没有就绪事件,系统会做两件事情。 1 把进程加入到inode的等待队列。 2 定时挂起进程,等待超时唤醒。 如果在超时之前,就有就绪事件触发,那进程会被唤醒。如果一直没有事件触发,直到超时,进程被唤醒,这时候sys_poll函数返回。sys_poll大致的逻辑就是这样,整个流程比这个复杂,尤其是加入到等待队列的逻辑。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档