poll/select轮询机制让等待队列wait_queue有了超时机制,如果阻塞一定时间后会直接返回。例如当我们实现一个中断方式的按键驱动时,虽然中断式的驱动,效率是蛮高的,但是大家有没有发现,应用程序的死循环里的读函数是一直在读的;在实际的应用场所里,有没有那么一种情况,偶尔有数据、偶尔没有数据,答案当然是有的。我们期望要做到的就是,当有数据的时候,我们才去读它,没数据的时候我们就不去读它,这样就避免了做无用功。
所以我们在中断的基础上添加poll机制来实现有数据的时候就去读,没数据的时候,自己规定一个时间,如果还没有数据,就表示超时时间。
所以Poll就是监控文件是否可读的一种轮询机制,作用与select一样。
应用程序的调用函数如下:
int poll(struct pollfd *fds,nfds_t nfds, int timeout);
Poll机制会判断fds中的文件是否可读,如果可读则会立即返回,返回的值就是可读fd的数量,如果不可读,那么进程就会休眠timeout这么长的时间,然后再来判断是否有文件可读,如果有,返回fd的数量,如果没有,则返回0。
poll机制总结(韦东山老师的总结):
poll > sys_poll > do_sys_poll >poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。
接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数
它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;
它还判断一下设备是否就绪。
如果设备未就绪,do_sys_poll里会让进程休眠一定时间
进程被唤醒的条件有2:一是上面说的“一定时间”到了,二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。
如果驱动程序没有去唤醒进程,那么chedule_timeout(__timeou)超时后,会重复2、3动作,直到应用程序的poll调用传入的时间到达。
poll机制中几个重要的函数:
设备驱动中
file_operations中要添加对应的.poll
头文件
#include
添加到等待队列头
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
poll_wait() 函数的名称非常容易让人产生误会,以为它和 wait_event() 等一样,会阻塞地等待某事件的发生,其实这个函数并不会引起阻塞。poll_wait() 函数所做的工作是把当前进程添加到 wait 参数指定的等待列表(poll_table)中,实际作用是让唤醒参数 queue 对应的等待队列可以唤醒因 poll/select() 而睡眠的进程。
应用程序中
头文件
#include
poll文件集合
struct pollfd fds[1];
/*
struct pollfd {
int fd; //文件
short events; //事件
short revents;
};
events:
#define POLLIN 0x0001 //可以无阻塞的读
#define POLLPRI 0x0002
#define POLLOUT 0x0004 //可以无阻塞的写
#define POLLERR 0x0008
#define POLLHUP 0x0010
#define POLLNVAL 0x0020
*/
poll函数
int poll(struct pollfd fd[], nfds_t nfds, int timeout);
/*
nfds: pollfd的个数
timeout: 超时时间
*/
下面是一个驱动实例:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //要添加的头文件
//gpio pin可以根据不同平台自行定义
#define IMX_GPIO_NR(bank, nr) (((bank) - 1) * 32 + (nr))
#define CYNO_GPIO_BEEP_NUM IMX_GPIO_NR(6,10)
int char_poll_major = 0;
dev_t char_devno;
struct char_poll_dev{
struct cdev cdev;
};
//引脚描述结构体
static struct pin_desc{
int irq;
unsigned char *name;
unsigned int pin;
};
static struct pin_desc beep_desc = {
0,
"beep_num",
CYNO_GPIO_BEEP_NUM
};
struct char_poll_dev *char_poll_devp;
struct class *char_poll_class;
static DECLARE_WAIT_QUEUE_HEAD(wq); //定义一个等待队列头
static int condition = 0; //等待队列条件,中断事件标识,中断服务程序beep_interrupt_handler将它置为1,char_poll_read将它清零
//按键中断执行程序,设置等待条件condition为真,并且唤醒挂起的进程
static irqreturn_t beep_interrupt_handler(int irq, void *dev_id)
{
//printk("%s\n", __func__);
condition = 1; //设置等待条件condition为真
wake_up_interruptible(&wq); //唤醒挂起的进程
return IRQ_HANDLED;
}
static int char_poll_open (struct inode *inode, struct file *filp)
{
printk(KERN_INFO "%s\n", __func__);
return 0;
}
//用户执行read,阻塞,有按键按下时唤醒
static ssize_t char_poll_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
char *data = "button falling\n\n\n";
//printk(KERN_INFO "%s\n", __func__);
/* 如果没有按键动作, 休眠 */
wait_event_interruptible(wq,condition); //如果没有按键动作,休眠,让出CPU
condition = 0;
/* 如果有按键动作, 返回键值 */
//printk(KERN_INFO "%s : %s\n", __func__, data);
copy_to_user(buf,data,strlen(data));
return strlen(data);
}
static ssize_t char_poll_write (struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
char data[30];
//printk(KERN_INFO "%s\n", __func__);
memset(data, 0, 30);
copy_from_user(data, buf, count);
printk(KERN_INFO "%s : data is %s\n", __func__, data);
return strlen(data);
}
//poll机制,底层设备驱动实现,加入等待队列头wq中,如果有按键按下,返回成功事件
unsigned int char_poll_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
/* 当没有按键按下时,即不会进入按键中断处理函数,此时condition = 0
* 当按键按下时,就会进入按键中断处理函数,此时condition被设置为1
*/
poll_wait(filp, &wq , wait); //添加一个等待队列,该函数,只是将进程挂在wq队列上,而不是立即休眠
if(condition){
mask |= POLLIN | POLLRDNORM; //根据返回的位掩码,标识该设备可以读取了
}
return mask; //返回设备文件现在的状态
}
static struct file_operations char_poll_fops = {
.owner = THIS_MODULE,
.open = char_poll_open,
.read = char_poll_read,
.write = char_poll_write,
.poll = char_poll_poll, //添加poll实现
};
static int char_poll_init(void)
{
int err, ret = -ENODEV;
struct device *dev_temp;
printk(KERN_INFO "%s\n", __func__);
ret = alloc_chrdev_region(&char_devno, 0, 1, "char_poll");
char_poll_major = MAJOR(char_devno);
if(ret){
printk(KERN_ERR "%s : chrdev_region fail\n", __func__);
goto chrdev_region_fail;
}
char_poll_devp = kmalloc(sizeof(struct char_poll_dev), GFP_KERNEL);
if(char_poll_devp == NULL){
printk(KERN_ERR "%s : kmalloc is fail\n", __func__);
goto kmalloc_fail;
}
memset(char_poll_devp, 0, sizeof(struct char_poll_dev));
cdev_init(&char_poll_devp->cdev, &char_poll_fops);
char_poll_devp->cdev.owner = THIS_MODULE;
char_poll_devp->cdev.ops = &char_poll_fops;
err = cdev_add(&char_poll_devp->cdev, char_devno, 1);
if(err){
printk(KERN_ERR "%s : cdev_add fail\n", __func__);
goto cdev_add_fail;
}
//start create class point
char_poll_class = class_create(THIS_MODULE, "char_poll");
if(IS_ERR(char_poll_class)){
printk(KERN_ERR "%s : class_create fail\n", __func__);
goto class_create_fail;
}
dev_temp = device_create(char_poll_class, NULL, char_devno, NULL, "char_poll");
if(IS_ERR(dev_temp)){
printk(KERN_ERR "%s : device_create fail\n", __func__);
goto device_create_fail;
}
//interrupt init
if(gpio_request(beep_desc.pin ,beep_desc.name)){ //申请GPIO,获取一个GPIO并声明标签
printk(KERN_ERR "%s : request gpio %d error\n", __func__, beep_desc.pin);
goto err_gpio_request;
}
gpio_direction_input(beep_desc.pin); //设置GPIO的方向,这里设置为GPIO输入
beep_desc.irq = gpio_to_irq(beep_desc.pin); //将GPIO映射为IRQ中断,得到中断号
printk(KERN_INFO "%s : the irq num is %d\n", __func__, beep_desc.irq);
ret = request_threaded_irq(beep_desc.irq, NULL, beep_interrupt_handler , IRQF_ONESHOT | IRQF_TRIGGER_FALLING, beep_desc.name , &beep_desc); //注册中断
if(ret){
printk(KERN_ERR "%s : request_irq is error\n", __func__);
goto err_request_irq;
}
printk(KERN_INFO "%s : init end\n", __func__);
return 0;
err_request_irq:
free_irq(beep_desc.irq, &beep_desc);
err_gpio_request:
gpio_free(beep_desc.pin);
device_create_fail:
class_destroy(char_poll_class);
class_create_fail:
cdev_del(&char_poll_devp->cdev);
cdev_add_fail:
kfree(char_poll_devp);
kmalloc_fail:
chrdev_region_fail:
unregister_chrdev_region(char_devno,1);
return -1;
}
static void char_poll_exit(void)
{
printk(KERN_INFO "%s\n", __func__);
free_irq(beep_desc.irq, &beep_desc);
gpio_free(beep_desc.pin);
device_destroy(char_poll_class, char_devno);
class_destroy(char_poll_class);
cdev_del(&char_poll_devp->cdev);
kfree(char_poll_devp);
unregister_chrdev_region(char_devno,1);
}
module_init(char_poll_init);
module_exit(char_poll_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("linux char driver base");
MODULE_AUTHOR("CZD");
当poll返回0就代表没有按键事件发生,否则就代表按了按键,并且返回了数据。下面是一个应用测试程序:
#include
#include
#include
#include
#include
int main(void)
{
int fd, ret;
char data[20];
struct pollfd fds[1]; //poll文件集合, 这里只检测一个文件
fd = open("/dev/char_poll", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
write(fd, "czd_write", strlen("czd_write"));
fds[0].fd = fd;
fds[0].events = POLLIN;
while(1){
ret = poll(fds, 1, 5000); //poll函数的调用,当返回0时,表示5s时间到了,而这段时间里,没有事件发生"数据可读"
if(ret == 0){
printf("---czd--- timeout\n");
}
else{ //如果没有超时,则读出按键值
read(fd, data, 1);
printf("---czd--- read data is %s", data);
}
}
close(fd);
return 0;
select与poll机制的设备驱动实现是一样的,仅仅上应用层的使用不同。
应用层的select函数会调用到内核函数do_select,do_select调用驱动的poll函数,若poll函数返回的掩码不可读写,那么do_select进入睡眠阻塞。要从睡眠中醒来并且跳出,有两种情况:a、超时跳出;b、驱动中唤醒等待队列,这时do_select再次调用poll函数,如果poll函数返回的掩码可读写,那么就跳出阻塞,否则继续睡眠。注意:上述是在select函数设成阻塞的情况,select函数可以设置成非阻塞的(将select函数的timeout参数设置成0)。
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
/*
其中 readfds、writefds、exceptfds 分别是被 select() 监视的读、写和异常处理的文件描述符集合,numfds 的值是需要检查的号码最高的 fd 加 1。readfds 文件集中的任何一个文件变得可读,select() 返回;同理,writefds 文件集中的任何一个文件变得可写,select 也返回。
struct timeval {
int tv_sec;
int tv_usec;
};
*/
清除一个文件集合
FD_ZERO(fd_set *set)
将一个文件描述符添加到文件描述符集合
FD_SET(int fd,fd_set *set)
将一个文件描述符从文件描述符集合中删除
FD_CLR(int fd,fd_set *set)
判断文件是否被置位
FD_ISSET(int fd,fd_set *set)
select应用示例代码如下
#include
#include
#include
#include
#include
#include
#include
int main()
{
int fd, i = 10;
char data_read[20];
fd_set rfds;
memset(data_read, 0, sizeof(data_read));
fd = open("/dev/char_poll", O_RDWR | O_NONBLOCK);
if (fd < 0)
{
printf("can't open!\n");
}
while(1)
{
FD_ZERO(&rfds); //清除一个文件描述符集合
FD_SET(fd, &rfds); //将fd加入rfds文件描述符集合
select(fd + 1, &rfds, NULL, NULL, NULL); //阻塞,直到有可读才返回
if(FD_ISSET(fd, &rfds)){ //判断文件描述符是否被置位
printf("select : the file can be read\n");
read(fd, data_read, 1);
printf("data_read = %s\n", data_read);
}
}
close(fd);
return 0;
}