linux驱动之poll/select轮询机制实例分析(结合驱动中断使用)

poll/select轮询机制让等待队列wait_queue有了超时机制,如果阻塞一定时间后会直接返回。例如当我们实现一个中断方式的按键驱动时,虽然中断式的驱动,效率是蛮高的,但是大家有没有发现,应用程序的死循环里的读函数是一直在读的;在实际的应用场所里,有没有那么一种情况,偶尔有数据、偶尔没有数据,答案当然是有的。我们期望要做到的就是,当有数据的时候,我们才去读它,没数据的时候我们就不去读它,这样就避免了做无用功。
所以我们在中断的基础上添加poll机制来实现有数据的时候就去读,没数据的时候,自己规定一个时间,如果还没有数据,就表示超时时间。

所以Poll就是监控文件是否可读的一种轮询机制,作用与select一样。

应用程序的调用函数如下:

int poll(struct pollfd *fds,nfds_t nfds, int timeout);

Poll机制会判断fds中的文件是否可读,如果可读则会立即返回,返回的值就是可读fd的数量,如果不可读,那么进程就会休眠timeout这么长的时间,然后再来判断是否有文件可读,如果有,返回fd的数量,如果没有,则返回0。

poll机制总结(韦东山老师的总结):

  1. poll > sys_poll > do_sys_poll >poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。

  2. 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数

    它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;

    它还判断一下设备是否就绪。

  3. 如果设备未就绪,do_sys_poll里会让进程休眠一定时间

  4. 进程被唤醒的条件有2:一是上面说的“一定时间”到了,二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。

  5. 如果驱动程序没有去唤醒进程,那么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;
}

你可能感兴趣的:(Android驱动开发)