Linux中poll的实现

一、poll相关数据结构分析

c 复制代码
struct poll_list {
        struct poll_list *next;
        int len;
        struct pollfd entries[0];
};
struct pollfd {
    int fd;
    short int events;
    short int revents;
}
struct poll_wqueues {
	poll_table pt;
	struct poll_table_page * table;
	int error;
};
typedef struct poll_table_struct poll_table;
struct poll_table_struct {
    poll_queue_proc qproc;
}
struct poll_table_page {
    struct poll_table_page *next;
    struct poll_table_entry *entry;
    struct poll_table_entry entries[0];
}
struct poll_table_entry {
    struct file *filp;
    wait_queue_t wait;
    wait_queue_head_t *wait_address;
}

1. 用户空间与内核空间的桥梁

1.1. struct pollfd - 用户空间传递的轮询描述符

c 复制代码
struct pollfd {
    int fd;           // 文件描述符
    short int events; // 等待的事件(输入)
    short int revents; // 实际发生的事件(输出)
};

作用:用户空间传递给内核的轮询请求

c 复制代码
// 用户空间使用示例
struct pollfd fds[2];
fds[0].fd = fd1;
fds[0].events = POLLIN;    // 等待可读
fds[1].fd = fd2; 
fds[1].events = POLLOUT;   // 等待可写

poll(fds, 2, 1000);        // 等待1秒

// 返回后检查结果
if (fds[0].revents & POLLIN) {
    // fd1 可读了
}

1.2. struct poll_list - 内核中的轮询描述符链表

c 复制代码
struct poll_list {
    struct poll_list *next;  // 下一个节点
    int len;                 // 本节点中的pollfd数量
    struct pollfd entries[0]; // 可变数组,存储pollfd
};

作用:在内核中管理用户空间传递的所有文件描述符

  • 使用链表是因为用户可能传递大量文件描述符
  • 柔性数组 entries[0] 允许动态分配大小

2. 轮询机制的核心结构

2.1. struct poll_table_struct / poll_table

c 复制代码
struct poll_table_struct {
    poll_queue_proc qproc;  // 关键:回调函数指针
};
typedef struct poll_table_struct poll_table;

作用:轮询操作的核心控制结构

  • qproc 指向 __pollwait 函数,负责将进程添加到设备的等待队列

2.2. poll_queue_proc 回调函数

c 复制代码
void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

工作流程

c 复制代码
// 当驱动调用 poll_wait() 时:
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
    if (p && wait_address)
        p->qproc(filp, wait_address, p); // 调用 __pollwait
}

3. 等待队列管理

3.1. struct poll_table_entry - 等待队列条目

c 复制代码
struct poll_table_entry {
    struct file *filp;           // 关联的文件
    wait_queue_t wait;           // 等待队列条目
    wait_queue_head_t *wait_address; // 设备的等待队列头
};

作用:记录进程在哪些设备的等待队列中注册了

  • 当设备就绪时,通过等待队列唤醒进程
  • 每个文件描述符对应一个 entry

3.2. struct poll_table_page - 内存管理页面

c 复制代码
struct poll_table_page {
    struct poll_table_page *next;    // 下一个页面
    struct poll_table_entry *entry;  // 当前可用的entry位置
    struct poll_table_entry entries[0]; // 存储多个entry
};

作用 :高效管理 poll_table_entry 的内存分配

  • 按页面批量分配,减少内存分配次数

4. 整体管理结构

4.1. struct poll_wqueues - 轮询等待队列

c 复制代码
struct poll_wqueues {
    poll_table pt;                  // 轮询表
    struct poll_table_page *table;  // entry页面链表
    int error;                      // 错误码
};

作用:管理整个轮询操作的所有资源

二、sys_poll系统调用

c 复制代码
#define POLLFD_PER_PAGE  ((PAGE_SIZE-sizeof(struct poll_list)) / sizeof(struct pollfd))
asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds, long timeout)
{
        struct poll_wqueues table;
        int fdcount, err;
        unsigned int i;
        struct poll_list *head;
        struct poll_list *walk;

        /* Do a sanity check on nfds ... */
        if (nfds > current->files->max_fdset && nfds > OPEN_MAX)
                return -EINVAL;

        if (timeout) {
                /* Careful about overflow in the intermediate values */
                if ((unsigned long) timeout < MAX_SCHEDULE_TIMEOUT / HZ)
                        timeout = (unsigned long)(timeout*HZ+999)/1000+1;
                else /* Negative or overflow */
                        timeout = MAX_SCHEDULE_TIMEOUT;
        }

        poll_initwait(&table);

        head = NULL;
        walk = NULL;
        i = nfds;
        err = -ENOMEM;
        while(i!=0) {
                struct poll_list *pp;
                pp = kmalloc(sizeof(struct poll_list)+
                                sizeof(struct pollfd)*
                                (i>POLLFD_PER_PAGE?POLLFD_PER_PAGE:i),
                                        GFP_KERNEL);
                if(pp==NULL)
                        goto out_fds;
                pp->next=NULL;
                pp->len = (i>POLLFD_PER_PAGE?POLLFD_PER_PAGE:i);
                if (head == NULL)
                        head = pp;
                else
                        walk->next = pp;

                walk = pp;
                if (copy_from_user(pp->entries, ufds + nfds-i,
                                sizeof(struct pollfd)*pp->len)) {
                        err = -EFAULT;
                        goto out_fds;
                }
                i -= pp->len;
        }
        fdcount = do_poll(nfds, head, &table, timeout);

        /* OK, now copy the revents fields back to user space. */
        walk = head;
        err = -EFAULT;
        while(walk != NULL) {
                struct pollfd *fds = walk->entries;
                int j;

                for (j=0; j < walk->len; j++, ufds++) {
                        if(__put_user(fds[j].revents, &ufds->revents))
                                goto out_fds;
                }
                walk = walk->next;
        }
        err = fdcount;
        if (!fdcount && signal_pending(current))
                err = -EINTR;
out_fds:
        walk = head;
        while(walk!=NULL) {
                struct poll_list *pp = walk->next;
                kfree(walk);
                walk = pp;
        }
        poll_freewait(&table);
        return err;
}

函数原型和变量声明

c 复制代码
asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds, long timeout)
{
    struct poll_wqueues table;
    int fdcount, err;
    unsigned int i;
    struct poll_list *head;
    struct poll_list *walk;

参数

  • ufds:用户空间的 pollfd 数组指针
  • nfds:文件描述符数量
  • timeout:超时时间(毫秒)

1. 参数验证

c 复制代码
/* Do a sanity check on nfds ... */
if (nfds > current->files->max_fdset && nfds > OPEN_MAX)
    return -EINVAL;

作用:检查文件描述符数量是否合理

  • current->files->max_fdset:进程当前文件描述符集的最大值
  • OPEN_MAX:系统限制的最大打开文件数

2. 超时时间处理

c 复制代码
if (timeout) {
    /* Careful about overflow in the intermediate values */
    if ((unsigned long) timeout < MAX_SCHEDULE_TIMEOUT / HZ)
        timeout = (unsigned long)(timeout*HZ+999)/1000+1;
    else /* Negative or overflow */
        timeout = MAX_SCHEDULE_TIMEOUT;
}
  • 判断timeout * HZ是否会溢出

    • HZ 是系统定义的时钟频率(每秒 jiffies 数),例如 100

    • MAX_SCHEDULE_TIMEOUT 是内核允许的最大超时值(以 jiffies 为单位)

    • 如果 timeout(毫秒)小于 MAX_SCHEDULE_TIMEOUT / HZ,说明 timeout * HZ 不会溢出 unsigned long 范围

    • 否则,直接跳到 else 分支,设置为最大值

  • 将毫秒转换为 jiffies,并四舍五入到最近的 jiffies

    • timeout * HZ:将毫秒转换为 jiffies
    • + 999:实现四舍五入
    • / 1000:将毫秒转换为秒的整数部分(因为 HZ 是每秒的节拍数)
    • + 1:确保至少等待 1 个 jiffy(避免结果为 0)
  • timeout 为负数(如果 timeout 是有符号类型且传入负值)

  • timeout * HZ 会溢出(超过 unsigned long 范围)

  • 直接设置为 MAX_SCHEDULE_TIMEOUT(通常是 LONG_MAX,表示无限等待)

3. 初始化轮询结构poll_initwait

c 复制代码
poll_initwait(&table);

作用 :初始化 poll_wqueues 结构

  • 设置 poll_table 的回调函数为 __pollwait
  • 初始化错误标志
  • 准备等待队列管理

4. 分配和管理 poll_list 链表

c 复制代码
head = NULL;
walk = NULL;
i = nfds;
err = -ENOMEM;
while(i!=0) {
    struct poll_list *pp;
    pp = kmalloc(sizeof(struct poll_list)+
                    sizeof(struct pollfd)*
                    (i>POLLFD_PER_PAGE?POLLFD_PER_PAGE:i),
                            GFP_KERNEL);
    if(pp==NULL)
        goto out_fds;
    pp->next=NULL;
    pp->len = (i>POLLFD_PER_PAGE?POLLFD_PER_PAGE:i);

内存分配策略

  • POLLFD_PER_PAGE:每个页面能容纳的 pollfd 数量
  • 计算方式:(PAGE_SIZE - sizeof(struct poll_list)) / sizeof(struct pollfd)
  • 如果剩余描述符数大于一页容量,分配一页容量,否则分配剩余数量

5. 从用户空间拷贝数据copy_from_user

c 复制代码
if (head == NULL)
		head = pp;
	else
		walk->next = pp;
	walk = pp;
if (copy_from_user(pp->entries, ufds + nfds-i,
                sizeof(struct pollfd)*pp->len)) {
    err = -EFAULT;
    goto out_fds;
}
i -= pp->len;

拷贝逻辑

  • 从用户空间拷贝 pollfd 结构到内核空间

  • ufds + nfds - i

    • ufds :用户传入的 struct pollfd 数组的起始地址(用户空间指针)

    • nfds:数组的总长度(文件描述符数量)

    • i :当前循环中剩余未处理的文件描述符数量(从 nfds 递减)

    • 这是一个指针算术运算,计算当前需要拷贝的数组段的起始地址

  • 如果拷贝失败,跳转到清理代码

6. 执行轮询操作do_poll

c 复制代码
fdcount = do_poll(nfds, head, &table, timeout);

核心轮询逻辑

  • 遍历所有文件描述符
  • 调用每个文件的 f_op->poll 方法
  • 将进程添加到设备的等待队列
  • 等待事件发生或超时

7. 将结果拷贝回用户空间__put_user

c 复制代码
walk = head;
err = -EFAULT;
while(walk != NULL) {
    struct pollfd *fds = walk->entries;
    int j;

    for (j=0; j < walk->len; j++, ufds++) {
        if(__put_user(fds[j].revents, &ufds->revents))
            goto out_fds;
    }
    walk = walk->next;
}

结果回传

  • 遍历 poll_list 链表
  • 将每个 pollfdrevents 字段拷贝回用户空间
  • 使用 __put_user 确保安全的用户空间写入

8. 信号处理

c 复制代码
err = fdcount;
if (!fdcount && signal_pending(current))
    err = -EINTR;

信号中断处理

  • 如果没有就绪的文件描述符,但收到了信号
  • 返回 -EINTR 表示被信号中断

9. 资源清理poll_freewait

c 复制代码
out_fds:
    walk = head;
    while(walk!=NULL) {
        struct poll_list *pp = walk->next;
        kfree(walk);
        walk = pp;
    }
    poll_freewait(&table);
    return err;

清理操作

  • 释放所有 poll_list 节点
  • 调用 poll_freewait 清理等待队列条目
  • 返回错误码或就绪的文件描述符数量

10. 完整执行流程图

内存不足 拷贝失败 拷贝失败 用户调用poll 参数验证 超时时间转换 初始化poll_wqueues 分配poll_list链表 拷贝用户pollfd数据 调用do_poll核心逻辑 等待事件/超时/信号 拷贝结果回用户空间 处理信号中断 资源清理 返回结果 跳转out_fds 释放poll_list链表 清理等待队列 返回错误码

三、do_poll轮询循环函数

c 复制代码
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;

        if (!timeout)
                pt = NULL;

        for (;;) {
                struct poll_list *walk;
                set_current_state(TASK_INTERRUPTIBLE);
                walk = list;
                while(walk != NULL) {
                        do_pollfd( walk->len, walk->entries, &pt, &count);
                        walk = walk->next;
                }
                pt = NULL;
                if (count || !timeout || signal_pending(current))
                        break;
                count = wait->error;
                if (count)
                        break;
                timeout = schedule_timeout(timeout);
        }
        __set_current_state(TASK_RUNNING);
        return count;
}

1. 函数原型和初始化

c 复制代码
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;

    if (!timeout)
        pt = NULL;

参数说明

  • nfds:文件描述符总数
  • list:poll_list 链表,包含所有要轮询的文件描述符
  • wait:轮询等待队列结构
  • timeout:超时时间(jiffies)

关键初始化

  • count:记录就绪的文件描述符数量
  • pt:指向 poll_table 结构
  • 如果 timeout = 0(非阻塞),设置 pt = NULL,不注册等待队列

2. 主轮询循环

c 复制代码
for (;;) {
    struct poll_list *walk;
    set_current_state(TASK_INTERRUPTIBLE);

2.1. 设置进程状态

c 复制代码
set_current_state(TASK_INTERRUPTIBLE);
  • 将当前进程状态设置为可中断睡眠
  • 这样进程可以在等待时被信号唤醒
  • 为调用 schedule_timeout() 做准备

3. 遍历所有文件描述符

c 复制代码
walk = list;
while(walk != NULL) {
    do_pollfd(walk->len, walk->entries, &pt, &count);
    walk = walk->next;
}

链表遍历

  • walk 遍历整个 poll_list 链表
  • 对每个链表节点调用 do_pollfd() 检查文件描述符状态
  • do_pollfd() 会更新 count(就绪的文件描述符数量)

4. 退出条件检查

c 复制代码
pt = NULL;
if (count || !timeout || signal_pending(current))
    break;

退出循环的条件

  1. count:有就绪的文件描述符
  2. !timeout:超时时间为0(立即返回)
  3. signal_pending(current):有信号等待处理

4.1. 设置 pt = NULL 的重要性

c 复制代码
pt = NULL;
  • 只在第一次遍历时注册等待队列
  • 后续遍历只检查状态,不重复注册
  • 避免重复添加到等待队列

5. 错误检查和调度

c 复制代码
count = wait->error;
if (count)
    break;

timeout = schedule_timeout(timeout);

错误处理

  • 检查 wait->error 是否有错误发生
  • 如果有错误,立即退出循环

睡眠等待

c 复制代码
timeout = schedule_timeout(timeout);
  • 让出 CPU,进入睡眠状态
  • 会被以下情况唤醒:
    • 超时时间到达
    • 设备就绪(通过等待队列唤醒)
    • 信号中断
  • 返回剩余的超时时间

6. 循环结束处理

c 复制代码
__set_current_state(TASK_RUNNING);
return count;

恢复进程状态

  • 将进程状态从 TASK_INTERRUPTIBLE 恢复为 TASK_RUNNING
  • 返回就绪的文件描述符数量

7. 关键设计要点

7.1. 状态机设计

c 复制代码
// 第一次遍历:注册等待队列 + 检查状态
pt = &wait->pt;
do_pollfd(..., &pt, ...);  // 注册并检查

// 后续遍历:只检查状态,不注册
pt = NULL;
do_pollfd(..., &pt, ...);  // 只检查

7.2. 性能优化

  • 避免重复注册:只在第一次遍历时注册等待队列
  • 批量处理:一次性检查所有文件描述符
  • 快速路径 :有就绪fd时立即返回

7.3. 信号和超时处理

c 复制代码
if (count || !timeout || signal_pending(current))
    break;
  • 正确处理信号中断
  • 精确的超时控制,通过定时器控制
  • 立即响应就绪事件

三、do_pollfd处理一批文件描述符的轮询操作

c 复制代码
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;
                fdp = fdpage+i;
                fd = fdp->fd;
                if (fd >= 0) {
                        struct file * file = fget(fd);
                        mask = POLLNVAL;
                        if (file != NULL) {
                                mask = DEFAULT_POLLMASK;
                                if (file->f_op && file->f_op->poll)
                                        mask = file->f_op->poll(file, *pwait);
                                mask &= fdp->events | POLLERR | POLLHUP;
                                fput(file);
                        }
                        if (mask) {
                                *pwait = NULL;
                                (*count)++;
                        }
                }
                fdp->revents = mask;
        }
}

1. 函数原型和参数

c 复制代码
static void do_pollfd(unsigned int num, struct pollfd * fdpage,
        poll_table ** pwait, int *count)

参数说明

  • num:要处理的文件描述符数量
  • fdpage:pollfd 结构数组的指针
  • pwait:指向 poll_table 指针的指针(允许修改)
  • count:就绪文件描述符计数器的指针

2. 循环处理每个文件描述符

c 复制代码
for (i = 0; i < num; i++) {
    int fd;
    unsigned int mask;
    struct pollfd *fdp;

    mask = 0;
    fdp = fdpage + i;
    fd = fdp->fd;

初始化

  • mask:事件掩码,初始为 0(无事件)
  • fdp:指向当前处理的 pollfd 结构
  • fd:文件描述符数值

3. 文件描述符有效性检查

c 复制代码
if (fd >= 0) {
    struct file * file = fget(fd);
    mask = POLLNVAL;

检查逻辑

  • fd >= 0:有效的文件描述符(负数表示忽略的fd
  • fget(fd):根据fd获取对应的 file 结构,增加引用计数
  • mask = POLLNVAL:预设文件描述符无效

4. 文件操作和轮询检查

c 复制代码
if (file != NULL) {
    mask = DEFAULT_POLLMASK;
    if (file->f_op && file->f_op->poll)
        mask = file->f_op->poll(file, *pwait);

4.1. 默认事件掩码

c 复制代码
mask = DEFAULT_POLLMASK;

DEFAULT_POLLMASK 通常定义为:

c 复制代码
#define DEFAULT_POLLMASK (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM)

4.2. 调用文件系统的 poll 操作

c 复制代码
mask = file->f_op->poll(file, *pwait);

5. 事件过滤和资源释放

c 复制代码
    mask &= fdp->events | POLLERR | POLLHUP;
    fput(file);
}

事件过滤

  • fdp->events:用户关心的事件
  • POLLERR | POLLHUP:错误和挂起事件总是报告
  • 使用按位与操作过滤出用户关心且实际发生的事件

资源管理

  • fput(file):减少文件引用计数,平衡之前的 fget

6. 就绪事件处理

c 复制代码
if (mask) {
    *pwait = NULL;
    (*count)++;
}

6.1. 设置 pwait = NULL

c 复制代码
*pwait = NULL;

重要作用

  • 当发现至少一个就绪的文件描述符时
  • pwait 设置为 NULL,防止后续文件描述符注册等待队列
  • 这是因为我们即将返回,不需要再等待其他设备

6.2. 增加就绪计数

c 复制代码
(*count)++;
  • 增加就绪文件描述符的计数器
  • 外层循环会根据这个计数决定是否立即返回

7. 设置返回事件

c 复制代码
fdp->revents = mask;

结果回填

  • 将计算得到的事件掩码设置到 revents 字段
  • 用户空间通过检查 revents 知道哪些事件发生

8. 完整执行流程图

否 是 否 是 否 是 是 否 是 否 开始循环 获取fd和pollfd指针 fd >= 0? 设置mask=0 fget获取file结构 file != NULL? 设置mask=POLLNVAL 设置默认mask 有f_op->poll? 保持默认mask 调用驱动poll方法 注册等待队列+检查状态 过滤用户关心的事件 fput释放文件 mask != 0? 设置pwait=NULL
增加count 继续处理 设置fdp->revents 还有fd? 结束

四、poll_freewait负责清理轮询操作中分配的所有资源

c 复制代码
void poll_freewait(struct poll_wqueues *pwq)
{
        struct poll_table_page * p = pwq->table;
        while (p) {
                struct poll_table_entry * entry;
                struct poll_table_page *old;

                entry = p->entry;
                do {
                        entry--;
                        remove_wait_queue(entry->wait_address,&entry->wait);
                        fput(entry->filp);
                } while (entry > p->entries);
                old = p;
                p = p->next;
                free_page((unsigned long) old);
        }
}

1. 函数原型和初始化

c 复制代码
void poll_freewait(struct poll_wqueues *pwq)
{
    struct poll_table_page * p = pwq->table;

作用:清理整个轮询操作期间分配的资源

  • p 指向 poll_table_page 链表的头部

2. 遍历页面链表

c 复制代码
while (p) {
    struct poll_table_entry * entry;
    struct poll_table_page *old;

    entry = p->entry;

链表遍历

  • 循环处理每个 poll_table_page
  • entry 指向当前页面中最后一个使用的条目位置

3. 清理等待队列条目

c 复制代码
do {
    entry--;
    remove_wait_queue(entry->wait_address, &entry->wait);
    fput(entry->filp);
} while (entry > p->entries);

3.1. 条目指针递减

c 复制代码
entry--;
  • 从最后一个条目开始向前清理
  • 因为条目在页面中是顺序分配的

3.2. 从等待队列中移除

c 复制代码
remove_wait_queue(entry->wait_address, &entry->wait);

关键操作:将进程从设备的等待队列中移除

3.3. 释放文件引用

c 复制代码
fput(entry->filp);

平衡之前的 fget

  • do_pollfd 中调用 fget(fd) 增加了文件引用计数
  • 现在调用 fput 减少引用计数
  • 如果引用计数降为0,真正释放文件资源

3.4. 循环条件

c 复制代码
} while (entry > p->entries);
  • 继续处理直到当前页面的所有条目都清理完毕
  • p->entries 指向页面中第一个条目

4. 释放页面内存

c 复制代码
old = p;
p = p->next;
free_page((unsigned long) old);

链表前进和内存释放

  • old 保存当前页面指针
  • p 移动到下一个页面
  • free_page 释放整个页面内存

5. 内存布局示例

假设有两个页面,每个页面包含3个条目:

c 复制代码
poll_table_page链表:
page1 -> page2 -> NULL

页面内存布局:
page1: [header][entry0][entry1][entry2]
                ↑              ↑
            p->entries      p->entry(指向entry2之后)

清理顺序:
entry从p->entry开始递减:
entry2 → entry1 → entry0

6. 完整执行流程图

是 是 否 否 开始poll_freewait p = pwq->table p != NULL? entry = p->entry entry-- remove_wait_queue fput释放文件引用 entry > p->entries? old = p, p = p->next free_page释放页面 结束

五、poll_initwait初始化 poll_table

c 复制代码
static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
	pt->qproc = qproc;
}
void poll_initwait(struct poll_wqueues *pwq)
{
        init_poll_funcptr(&pwq->pt, __pollwait);
        pwq->error = 0;
        pwq->table = NULL;
}

1. 函数逐行分析

1.1. init_poll_funcptr() - 初始化 poll_table 函数指针

c 复制代码
static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
    pt->qproc = qproc;
}

参数

  • pt:要初始化的 poll_table 结构指针
  • qproc:poll_queue_proc 类型的函数指针

关键类型定义

c 复制代码
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

作用

  • qproc 回调函数设置到 poll_table 结构中
  • 这个回调函数负责将进程添加到设备的等待队列

1.2. poll_initwait() - 初始化 poll 等待队列

c 复制代码
void poll_initwait(struct poll_wqueues *pwq)
{
    init_poll_funcptr(&pwq->pt, __pollwait);
    pwq->error = 0;
    pwq->table = NULL;
}

初始化三个关键字段

  1. 设置回调函数

    c 复制代码
    init_poll_funcptr(&pwq->pt, __pollwait);
    • pwq->pt.qproc 设置为 __pollwait 函数
  2. 初始化错误标志

    c 复制代码
    pwq->error = 0;
    • 表示初始状态下没有错误发生
  3. 初始化页面表

    c 复制代码
    pwq->table = NULL;
    • table 指向 poll_table_page 链表
    • 初始为空,表示还没有分配任何条目页面

六、poll_wait

c 复制代码
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
        if (p && wait_address)
                p->qproc(filp, wait_address, p);
}
void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *_p)
{
        struct poll_wqueues *p = container_of(_p, struct poll_wqueues, pt);
        struct poll_table_page *table = p->table;

        if (!table || POLL_TABLE_FULL(table)) {
                struct poll_table_page *new_table;

                new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);
                if (!new_table) {
                        p->error = -ENOMEM;
                        __set_current_state(TASK_RUNNING);
                        return;
                }
                new_table->entry = new_table->entries;
                new_table->next = table;
                p->table = new_table;
                table = new_table;
        }

        /* Add a new entry */
        {
                struct poll_table_entry * entry = table->entry;
                table->entry = entry+1;
                get_file(filp);
                entry->filp = filp;
                entry->wait_address = wait_address;
                init_waitqueue_entry(&entry->wait, current);
                add_wait_queue(wait_address,&entry->wait);
        }
}

1. poll_wait - 设备驱动的接口函数

c 复制代码
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
        if (p && wait_address)
                p->qproc(filp, wait_address, p);
}

参数

  • filp:文件结构指针
  • wait_address:设备的等待队列头
  • p:poll_table 结构指针

作用:设备驱动的统一接口

  • 检查参数有效性
  • 调用注册的回调函数(通常是 __pollwait

2. __pollwait - 核心的回调函数

c 复制代码
void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *_p)
{
        struct poll_wqueues *p = container_of(_p, struct poll_wqueues, pt);
        struct poll_table_page *table = p->table;

关键转换

c 复制代码
struct poll_wqueues *p = container_of(_p, struct poll_wqueues, pt);
  • 通过 container_of 宏从 poll_table 找到包含它的 poll_wqueues

3. 内存管理:页面分配

c 复制代码
if (!table || POLL_TABLE_FULL(table)) {
        struct poll_table_page *new_table;

        new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);
        if (!new_table) {
                p->error = -ENOMEM;
                __set_current_state(TASK_RUNNING);
                return;
        }
        new_table->entry = new_table->entries;
        new_table->next = table;
        p->table = new_table;
        table = new_table;
}

3.1. 内存分配条件

  1. !table:第一个页面尚未分配
  2. POLL_TABLE_FULL(table):当前页面已满

3.2. 页面分配流程

c 复制代码
// 分配新页面
new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);

// 初始化新页面
new_table->entry = new_table->entries;  // 指向第一个条目位置
new_table->next = table;                // 插入链表头部
p->table = new_table;                   // 更新链表头
table = new_table;                      // 使用新页面

4. 创建等待队列条目

c 复制代码
{
        struct poll_table_entry * entry = table->entry;
        table->entry = entry+1;
        get_file(filp);
        entry->filp = filp;
        entry->wait_address = wait_address;
        init_waitqueue_entry(&entry->wait, current);
        add_wait_queue(wait_address,&entry->wait);
}
  1. 获取条目指针

    c 复制代码
    struct poll_table_entry * entry = table->entry;
    table->entry = entry+1;
    • 获取当前可用的条目位置
    • 移动条目指针到下一个位置
  2. 管理文件引用

    c 复制代码
    get_file(filp);
    entry->filp = filp;
    • 增加文件的引用计数
    • 保存文件指针到条目中
  3. 初始化等待队列条目

    c 复制代码
    init_waitqueue_entry(&entry->wait, current);
    • 将等待队列条目与当前进程关联
    • 设置默认的回调函数
  4. 添加到设备等待队列

    c 复制代码
    add_wait_queue(wait_address, &entry->wait);
    • 将当前进程添加到设备的等待队列
    • 当设备就绪时,通过这个队列唤醒进程

5. 关键数据结构关系

复制代码
poll_wqueues
├── pt (poll_table)
│   └── qproc = __pollwait
├── table → poll_table_page链表
│   ├── poll_table_page1
│   │   ├── entries[0] → poll_table_entry
│   │   ├── entries[1] → poll_table_entry  
│   │   └── ...
│   └── poll_table_page2
│       └── ...

6. 完整工作流程

用户空间 sys_poll poll_initwait 设备驱动 __pollwait poll(fds, nfds, timeout) poll_initwait(&table) pt->>qproc = __pollwait error=0, table=NULL 遍历调用f_op->>poll poll_wait(filp, wait_queue, &table.pt) 调用table.pt.qproc (__pollwait) 创建poll_table_entry 添加到设备等待队列 用户空间 sys_poll poll_initwait 设备驱动 __pollwait

七、测试poll的简单例子

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/poll.h>
#include <fcntl.h>

char buffer[4096];

int main(int argc, char **argv)
{
    struct pollfd pfd;
    int n;

    fcntl(0, F_SETFL, fcntl(0,F_GETFL) | O_NONBLOCK); /* stdin */
    pfd.fd = 0;  /* stdin */
    pfd.events = POLLIN;

    while (1) {
        n=read(0, buffer, 4096);
        if (n >= 0)
            write(1, buffer, n);
		n = poll(&pfd, 1, -1);
		if (n < 0)
			break;
    }
    perror( n<0 ? "stdin" : "stdout");
    exit(1);
}

1. 程序功能概述

这个程序演示了如何使用 poll() 来监控标准输入,当有数据可读时立即读取并回显到标准输出

2. 代码逐部分分析

2.1. 头文件和全局变量

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/poll.h>
#include <fcntl.h>

char buffer[4096];
  • 包含必要的头文件,特别是 <sys/poll.h> 用于 poll 系统调用
  • 定义 4KB 的缓冲区用于数据读取

2.2. 主函数初始化

c 复制代码
int main(int argc, char **argv)
{
    struct pollfd pfd;
    int n;

    fcntl(0, F_SETFL, fcntl(0,F_GETFL) | O_NONBLOCK); /* stdin */

关键设置

  • struct pollfd pfd:定义 poll 结构体
  • fcntl(0, F_SETFL, fcntl(0,F_GETFL) | O_NONBLOCK):将标准输入设置为非阻塞模式

2.3. 配置 poll 结构

c 复制代码
    pfd.fd = 0;  /* stdin */
    pfd.events = POLLIN;

pollfd 结构配置

  • fd = 0:监控标准输入(文件描述符 0)
  • events = POLLIN:关注"数据可读"事件

2.4. 主循环

c 复制代码
    while (1) {
        n=read(0, buffer, 4096);
        if (n >= 0)
            write(1, buffer, n);

读取现有数据

  • 在进入 poll 等待之前,先尝试读取可能已经到达的数据
  • 如果 read() 成功(n >= 0),立即将数据回显到标准输出

2.5. Poll 系统调用

c 复制代码
		n = poll(&pfd, 1, -1);
		if (n < 0)
			break;
    }

关键调用

c 复制代码
poll(&pfd, 1, -1)

参数说明:

  • &pfd:pollfd 结构数组的指针
  • 1:监控的文件描述符数量
  • -1:无限超时(永远等待)

返回值处理

  • n > 0:有文件描述符就绪,继续循环
  • n < 0:发生错误,退出循环

2.6. 错误处理和退出

c 复制代码
    perror( n<0 ? "stdin" : "stdout");
    exit(1);
}
  • 打印错误信息
  • 以错误状态退出

3. 关键特性分析

3.1. 非阻塞模式的重要性

c 复制代码
fcntl(0, F_SETFL, fcntl(0,F_GETFL) | O_NONBLOCK);

为什么需要非阻塞模式?

  • 在调用 poll() 之前,程序先尝试 read()
  • 如果没有数据,非阻塞 read() 立即返回 -1(设置 errno = EAGAIN
  • 避免在初始读取时阻塞程序

4. 编译执行测试

c 复制代码
gcc -o polltest polltest.c
./polltest

正常输入:输入文字,立即回显

管道输入echo "hello" | ./polltest

信号中断:按 Ctrl+C 终止程序

相关推荐
小杰帅气25 分钟前
进程优先级与切换调度
linux·运维·服务器
方便面不加香菜26 分钟前
Linux基本指令(1)
linux
济61728 分钟前
linux(第十四期)--Uboot移植(1)-- Ubuntu20.04
linux
奋斗的阿狸_198629 分钟前
键盘组合键监听与 xterm 唤醒程序
linux·运维·服务器
小张成长计划..32 分钟前
【linux】2:linux权限的概念
linux·运维·服务器
马踏岛国赏樱花34 分钟前
Windows与Ubuntu双系统,挂载D/E盘到Ubuntu下时只能读的问题
linux·windows·ubuntu
ben9518chen34 分钟前
Linux操作系统基本使用
linux·运维·服务器
一个平凡而乐于分享的小比特36 分钟前
CPU上电启动到程序运行全流程详解
linux·uboot·根文件系统·cpu上电到启动
不像程序员的程序媛42 分钟前
Linux开机自启动systemd配置
linux·运维·服务器
GREGGXU1 小时前
Could not load the Qt platform plugin “xcb“ in ““ even though it was found.
linux·qt