深入理解Linux驱动:探讨poll驱动的实现原理 (linux 驱动poll)
Linux驱动是连接用户空间和内核空间的重要桥梁,它实现了对硬件设备的读写操作,并将设备的状态、控制信息等反馈给用户。其中,poll驱动是一个常用的输入/输出多路复用的方法,它允许一个进程等待并同时监视多个文件描述符(fd)。在本文中,我们将深入探讨poll驱动的实现原理,以更好地理解Linux系统的工作原理。
一、poll驱动概述
poll驱动是Linux内核提供的多路复用驱动,它允许一个进程等待并同时监视多个文件描述符(fd)。在常见的网络编程中,经常会使用到poll驱动,以实现事件驱动IO模型,同时管理多个socket连接。
poll驱动的实现原理是基于select系统调用的,由于select系统调用有诸多限制,例如需要轮询所有文件描述符、不能同时监听太多文件描述符等,因此Linux内核采用了poll驱动的方式。与select系统调用相比,poll驱动可以监听更多的文件描述符和更多的事件类型,并且使用起来更为方便。
二、poll驱动的使用方法
poll驱动的使用方法与select类似,只不过它支持更多的事件类型和更多的文件描述符。下面是poll系统调用的一般用法:
“`
#include
int poll(struct pollfd * fds, nfds_t nfds, int timeout);
“`
其中,pollfd结构体定义如下:
“`
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 监控的事件 */
short revents; /* 实际发生的事件 */
};
“`
示例代码:
“`
#include
#include
int mn(void)
{
struct pollfd fds[2];
int ret;
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
fds[1].fd = STDOUT_FILENO;
fds[1].events = POLLOUT;
ret = poll(fds, 2, 5000); /* 等待 5 秒钟 */
if (ret
perror(“poll error”);
return -1;
}
if (ret == 0) {
printf(“timeout\n”);
return 0;
}
if (fds[0].revents & POLLIN) {
printf(“input event\n”);
}
if (fds[1].revents & POLLOUT) {
printf(“output event\n”);
}
return 0;
}
“`
以上示例代码定义了两个结构体,一个用于监听标准输入文件描述符,一个用于监听标准输出文件描述符。在poll系统调用返回时,程序会根据各个文件描述符的实际情况来更新结构体中的revents字段,从而判断当前所发生的事件类型。
三、poll驱动的实现原理
poll驱动的核心实现是基于Linux内核的定时器和位图机制。在调用poll系统调用时,内核会为当前进程创建一个poll_table结构,用于管理需要监视的文件描述符和相应的事件类型。poll_table结构定义如下:
“`
struct poll_table_struct {
poll_queue_proc qproc; /* 描述符/指针的回调函数 */
wt_queue_head_t wt; /* 等待队列头 */
struct poll_table_page * entry; /* 事件表的起始页 */
unsigned long * bitset; /* 位图指针 */
unsigned long * page; /* 当前页指针 */
unsigned int offset; /* 当前页位图的偏移量 */
unsigned int max_index; /* 更大位图下标 */
unsigned long mark; /* 活动文件描述符上的标记 */
};
“`
其中,bitset指向位图指针,而page则指向事件表的当前页。在poll驱动的实现过程中,内核会将所有需要监听的文件描述符的位图标记为1,然后将其注册到poll_table结构的等待队列中。此时,如果发生了需要监听的事件,内核就会把等待队列中所有标记为1的描述符从位图中删除,并且向poll_table结构的等待队列头中添加一个唤醒poll系统调用的wt_queue_entry_t。
四、poll驱动的优化思路
poll驱动作为一个常用的多路复用驱动,其实现方式也开始逐步优化。目前较为流行的优化方法主要有两种:边缘触发和水平触发。边缘触发与水平触发的主要区别在于数据可读可写时处理的方式不同。
边缘触发:仅当fd发生“边缘事件”(如读写事件)时才返回,需要立刻处理,否则就会“错过”该事件。对于一个可读的fd,连续多次调用poll或者select,如果没有读取掉且fd变为不可读就会出现“错过”的情况。
水平触发:只要fd是可读可写的就会不断地触发,需要反复处理。