Linux学习笔记--POLL_SELECT方式读取输入数据
头文件和变量
#include <poll.h> // poll 系统调用struct pollfd fds[1]; // poll 文件描述符数组
nfds_t nfds = 1; // 要监视的文件描述符数量
poll 的工作原理
poll
允许程序同时监视多个文件描述符,等待其中任何一个变为可读、可写或有异常条件。
核心思想
避免忙等待:进程不需要主动轮询每个文件描述符
事件驱动:内核在事件发生时通知应用程序
批量监视:一次系统调用可以监视多个文件描述符
#include <poll.h>struct pollfd {int fd; /* 文件描述符 */short events; /* 请求监视的事件 */short revents; /* 实际发生的事件 */
};int poll(struct pollfd *fds, nfds_t nfds, int timeout);
应用程序设置监视参数
struct pollfd fds[2];
fds[0].fd = fd1; // 第一个文件描述符
fds[0].events = POLLIN; // 监视可读事件
fds[0].revents = 0; // 初始化为0fds[1].fd = fd2; // 第二个文件描述符
fds[1].events = POLLIN | POLLOUT; // 监视可读和可写事件
fds[1].revents = 0;int ret = poll(fds, 2, 5000); // 监视2个fd,超时5秒
内核内部处理
当应用程序调用 poll()
时:
注册监视:内核将应用程序要监视的文件描述符和事件类型记录下来
进程挂起:调用进程被放入等待队列,进入睡眠状态
内核监视:内核持续监视所有注册的文件描述符
事件触发:当任何被监视的文件描述符就绪时,内核:
设置对应的
revents
字段唤醒等待的进程
超时处理:如果指定的超时时间到达,内核也会唤醒进程
事件类型
常用的事件标志:
POLLIN
:数据可读POLLOUT
:可写入数据POLLERR
:发生错误POLLHUP
:连接挂起POLLNVAL
:文件描述符未打开
poll 与其他 I/O 多路复用技术的对比
1. 与 select 的比较
select 的缺点:
文件描述符数量有限(通常 1024)
每次调用需要重置 fd_set
效率随 fd 数量增加而下降
poll 的优点:
无文件描述符数量限制
更清晰的接口
更好的性能
2. 与 epoll 的比较
epoll 的优势:
更高效的处理大量文件描述符
边缘触发模式
不需要每次重新注册
while (1)
{// 设置 poll 监视参数fds[0].fd = fd; // 要监视的文件描述符fds[0].events = POLLIN; // 监视可读事件fds[0].revents = 0; // 实际发生的事件(由内核填充)// 调用 poll,超时时间 5000ms (5秒)ret = poll(fds, nfds, 5000);if (ret > 0) // 有事件发生{if (fds[0].revents == POLLIN) // 输入设备有数据可读{// 读取所有可用事件(非阻塞)while (read(fd, &event, sizeof(event)) == sizeof(event)){printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);}}}else if (ret == 0) // 超时{printf("time out\n");}else // 错误{printf("poll err\n");}
}
poll 调用参数
ret = poll(fds, nfds, 5000);
fds
: 指向pollfd
结构体数组的指针nfds
: 监视的文件描述符数量(这里是 1)5000
: 超时时间(毫秒),5秒后如果没有事件则返回
poll 返回值处理
情况 1: ret > 0
- 有事件发生
if (fds[0].revents == POLLIN)
{while (read(fd, &event, sizeof(event)) == sizeof(event)){// 处理每个事件}
}
检查
revents
字段确认是读取事件使用
while
循环读取所有可用事件(因为设备以非阻塞模式打开)当
read
返回 -1 且errno
为EAGAIN
时,表示没有更多数据
情况 2: ret == 0
- 超时
printf("time out\n");
情况 3: ret < 0
- 错误
printf("poll err\n");
poll
系统调用出错。
使用示例
# 编译
gcc -o input_monitor input_monitor.c# 运行(监控键盘事件)
sudo ./input_monitor /dev/input/event1
输出
bustype = 0x3
vendor = 0x46d
product = 0xc52b
version = 0x111
support ev type: EV_SYN EV_KEY EV_MSC EV_LED get event: type = 0x1, code = 0x1c, value = 0x1 # 回车键按下
get event: type = 0x0, code = 0x0, value = 0x0 # 同步事件
get event: type = 0x1, code = 0x1c, value = 0x0 # 回车键释放
get event: type = 0x0, code = 0x0, value = 0x0 # 同步事件
time out # 5秒内无事件
time out # 再过5秒无事件
get event: type = 0x1, code = 0x2, value = 0x1 # 数字1键按下
...
poll其他应用场景
1. 网络服务器
// 同时监视监听socket和所有客户端连接
struct pollfd fds[MAX_CLIENTS + 1];
fds[0].fd = listen_sock;
fds[0].events = POLLIN;// 添加客户端连接
for (int i = 1; i <= client_count; i++) {fds[i].fd = client_sockets[i-1];fds[i].events = POLLIN;
}
2. 多输入源应用
// 同时监视键盘、鼠标、网络等多个输入
struct pollfd fds[3];
fds[0].fd = keyboard_fd; // 键盘设备
fds[1].fd = mouse_fd; // 鼠标设备
fds[2].fd = network_fd; // 网络套接字
3. 超时处理
// 实现带超时的操作
ret = poll(&fds, 1, 1000); // 等待1秒
if (ret == 0) {// 超时处理handle_timeout();
}