博客主页:PannLZ
系列专栏:《Linux系统之路》
欢迎关注:点赞收藏✍️留言
如果需要实现被动等待(在感知字符设备时不浪费CPU周期),则必须实现poll()
函数,每当用户空间程序在与设备关联的文件上执行系统调用select()
或poll()
时都会调用poll()
函数:
unsigned int (*poll) (struct file *, struct poll_table_struct *);
这个方法核心的内核函数是poll_wait()
,它定义在
中,这个头文件应该包含在驱动代码中:
void poll_wait(struct file * filp,wait_queue_head_t * wait_address,poll_table *p)
poll_wait()
根据注册到struct poll_table
结构(作为第三个参数传递)中的事件,把与struct file
结构(作为第一个参数)关联的设备添加到可以唤醒进程的列表(由第二个参数struct wait_queue_head_t
结构指定,进程在其中处于睡眠状态)中。
用户进程可以运行poll()
、select()
或者epoll()
系统调用把需要等待的一组文件添加到等待队列上,以了解是否有相关的设备准备就绪。之后,内核将会调用与每个设备文件相关的驱动程序的poll入口。每个驱动程序的poll方法再调用poll_wait,为需要接收内核通知的进程注册事件,在这些事件发生之前把进程置于睡眠状态,并把驱动程序注册为可以唤醒进程的驱动程序。通常的方法是根据select()(或poll())系统调用支持的事件,为每个事件类型使用一个等待队列(一方面是考虑可读性,另一方面是考虑可写性,最后是考虑需要时的异常处理)。
如果有需要读取的数据(此时调用select或poll),则返回**(* poll)文件操作的返回值必须是POLLIN |POLLRDNORM**;如果设备是可写的(此时也调用select或poll),则返回POLLOUT | POLLWRNORM;如果没有新数据且设备尚不可写,则返回0。
如果驱动程序没有定义这个方法,则设备将被视为总是可读可写的,poll()或select()系统调用立即返回。
//当实现poll函数时,read或write方法可能会改变。
//(1)为每个需要实现被动等待的事件类型(读、写、异常)声明等待队列,当无数据可读或设备不可写时,把任务放入该队列:
static DECLARE_WAIT_QUEUE_HEAD(my_wq);
static DECLARE_WAIT_QUEUE_HEAD(my_rq);
//(2)像这样实现poll函数:
#include
static unsigned int eep_poll(struct file *file,poll_table *wait)
{
unsigned int reval_mask = 0;
poll_wait(file, &my_wq, wait);
poll_wait(file, &my_rq, wait);
if (new-data-is-ready)
reval_mask |= (POLLIN | POLLRDNORM);
if (ready_to_be_written)
reval_mask |= (POLLOUT | POLLWRNORM);
return reval_mask;
}
(3)当有新数据或者是设备可写入时,通知等待队列:
wake_up_interruptible(&my_rq); /* 准备读取 */
wake_up_interruptible(&my_wq); /* 准备写入 */
通知可读事件可以采用以下两种方法:
通知可写事件可以采用以下两种方法:
当用户需要读取时,如果有数据,数据会立即发送到进程,;如果没有数据可用,进程会进入睡眠状态,等待数据到达。
以下代码在指定的字符设备上执行select(),以便感知数据是否可用:
#include
#include
#include
#include
#include
#define NUMBER_OF_BYTE 100
#define CHAR_DEVICE "/dev/packt_char"
char data[NUMBER_OF_BYTE];
int main(int argc, char **argv)
{
int fd, retval;
ssize_t read_count;
fd_set readfds;
fd = open(CHAR_DEVICE, O_RDONLY);
if(fd < 0)
/* 打印一条消息并退出*/
[...]
while(1) {
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
/*
* 只需要通知读取事件,而不需要超时
* 这个调用将使进程进入休眠状态,直到通知它自己
注册的事件为止
*/
ret = select(fd + 1, &readfds, NULL,NULL, NULL);
/* 从这一行开始,进程已经得到通知 */
if (ret == -1) {
fprintf(stderr, "select call on %s:an error ocurred",CHAR_DEVICE);
break;
}
/*
* 文件描述符现在已经准备好了
* 假设我们只对一个文件感兴趣
*/
if (FD_ISSET(fd, &readfds)) {
read_count = read(fd, data,
NUMBER_OF_BYTE);
if (read_count < 0 )
/* 发生一个错误,处理这个问题*/
[...]
if (read_count != NUMBER_OF_BYTE)
/* 读取的比需要的字节还少 */
[...] /* 处理 */
else
/* 处理读取的数据*/
[...]
}
}
close(fd);
return EXIT_SUCCESS;
}