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 终止程序

相关推荐
爱倒腾的老唐3 小时前
13、Linux 基本权限
linux·运维·服务器
罗政3 小时前
CentOS 7.6 系统源码部署 HivisionIDPhotos
linux·运维·centos
cililin4 小时前
第4章 文件管理
linux·服务器·网络·操作系统·unix
薰衣草23334 小时前
linux练习-2
linux·运维·服务器
shylyly_5 小时前
Linux-> TCP 编程1
linux·网络·tcp/ip·echo·tcp编程
abcooxj6 小时前
Linux I2C 子系统
linux
无敌最俊朗@6 小时前
Qt 多线程与并发编程详解
linux·开发语言·qt
DrugOne6 小时前
Amber24 安装指南:Ubuntu 22.04 + CUDA 12.4 环境
linux·运维·ubuntu·drugone
至善迎风7 小时前
Ubuntu 24.04 SSH 多端口监听与 ssh.socket 配置详解
linux·ubuntu·ssh