一、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
链表 - 将每个
pollfd
的revents
字段拷贝回用户空间 - 使用
__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;
退出循环的条件:
count
:有就绪的文件描述符!timeout
:超时时间为0(立即返回)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;
}
初始化三个关键字段:
-
设置回调函数:
cinit_poll_funcptr(&pwq->pt, __pollwait);
- 将
pwq->pt.qproc
设置为__pollwait
函数
- 将
-
初始化错误标志:
cpwq->error = 0;
- 表示初始状态下没有错误发生
-
初始化页面表:
cpwq->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. 内存分配条件
!table
:第一个页面尚未分配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);
}
-
获取条目指针:
cstruct poll_table_entry * entry = table->entry; table->entry = entry+1;
- 获取当前可用的条目位置
- 移动条目指针到下一个位置
-
管理文件引用:
cget_file(filp); entry->filp = filp;
- 增加文件的引用计数
- 保存文件指针到条目中
-
初始化等待队列条目:
cinit_waitqueue_entry(&entry->wait, current);
- 将等待队列条目与当前进程关联
- 设置默认的回调函数
-
添加到设备等待队列:
cadd_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 终止程序