linux按键驱动之poll

   上一节应用程序的死循环里的读函数是一直在读的;在实际的应用场所里,有没有那么一种情况,偶尔有数据、偶尔没有数据,答案当然是有的。--》poll机制:Poll机制实现的是一定时间如果没有按键的话就返回。以前是如果没有按键不返回  一直处于休眠状态。

poll调用(应用层) :

#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);

struct pollfd结构如下:【在源码文件poll.h文件中】

struct pollfd {
  int fd;
  short events;
  short revents;
};

 这个结构中fd表示文件描述符,events表示请求检测的事件,revents表示检测之后返回的事件,如果当某个文件描述符有状态变化时,revents的值就不为空。

  1. fds:存放需要被检测状态的Socket描述符;与select不同(select函数在调用之后,会清空检测socket描述符的数组),每当调用这个函数之后,系统不会清空这个数组,而是将有状态变化的描述符结构的revents变量状态变化,操作起来比较方便;
  2. nfds:用于标记数组fds中的struct pollfd结构元素的总数量;
  3. timeout:poll函数调用阻塞的时间,单位是MS(毫秒)

return value:

  1. 大于0:表示数组fds中有socket描述符的状态发生变化,或可以读取、或可以写入、或出错。并且返回的值表示这些状态有变化的socket描述符的总数量;此时可以对fds数组进行遍历,以寻找那些revents不空的socket描述符,然后判断这个里面有哪些事件以读取数据。
  2. 等于0:表示没有socket描述符有状态变化,并且调用超时。
  3. 小于0:此时表示有错误发生,此时全局变量errno保存错误码。

内核实现:

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调用传入的时间到达。

博客中http://blog.csdn.net/u012719256/article/details/52663292提到:

内核中poll实现非常固定,就以下两个步骤

static unsigned forth_drv_poll(struct file *file, poll_table *wait)
{
    unsigned int mask = 0;

// 1. 将button_waitq添加到等待队列    
    poll_wait(file, &button_waitq, wait); // 不会立即休眠

    if (ev_press) // 如果有按键按下了,返回的掩码表示不休眠
        mask |= POLLIN | POLLRDNORM;

// 2. 返回掩码
    return mask;
}

暂时做个标记吧,现在只知道这个格式

代码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#define DEVICE_NAME "mybutton"   /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
static struct class *button_class;
static struct class_device *button_dev_class;
int major;

static volatile int press_cnt=0;/* 按键被按下的次数(准确地说,是发生中断的次数) */


static DECLARE_WAIT_QUEUE_HEAD(button_waitq);//定义等待队列

/* 中断事件标志, 中断服务程序将它置1,s3c24xx_buttons_read将它清0 */
static volatile int ev_press = 0;

static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
    // volatile int *press_cnt = (volatile int *)dev_id;
     press_cnt =press_cnt + 1; /* 按键计数加1 */
     ev_press = 1;                /* 表示中断发生了 */
     wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
     return IRQ_RETVAL(IRQ_HANDLED);//中断处理程序应该返回一个值,用来表明是否真正处理了一个中断,如果中断例程发现其设备的确要处理,则应该返回IRQ_HANDLED, //否则应该返回IRQ_NONE,我们可以通过这个宏来产生返回值,不是本设备的中断应该返回IRQ_NONE

}
/* 应用程序对设备文件/dev/xxx 执行open(...)时,
 * 就会调用button_open函数
 * 就会调用button_open函数
 */
static int button_open (struct inode *inode, struct file *filep)  
{   
    int err;
    err=request_irq(IRQ_EINT2,buttons_interrupt,IRQF_TRIGGER_FALLING,"KEY3",NULL);
    
    if (err) {
        // 释放已经注册的中断
         free_irq(IRQ_EINT2, NULL);
        return -EBUSY;
    }
    
    return 0;  
}  

/* 应用程序对设备文件/dev/buttons执行close(...)时,
 * 就会调用buttons_close函数
 */
 static int buttons_close(struct inode *inode, struct file *file)
{
    
    free_irq(IRQ_EINT2, NULL);
    return 0;
}

ssize_t button_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
     unsigned long err;
    
    /* 如果ev_press等于0,休眠 */
    wait_event_interruptible(button_waitq, ev_press);//阻塞   
     /* 执行到这里时,ev_press等于1,将它清0 */
    ev_press = 0;
     /* 将按键状态复制给用户,并清0 */
    err = copy_to_user(buf, (const void *)&press_cnt,  count);
    //memset((void *)&press_cnt, 0, sizeof(press_cnt));
    return  err ? -EFAULT : 0;
}



static unsigned buttons_poll(struct file *file, poll_table *wait)
{
        unsigned int mask = 0;
        poll_wait(file, &button_waitq, wait); // 不会立即休眠    将进程挂接到button_waitq队列中
          /* 当没有按键按下时,即不会进入按键中断处理函数,此时ev_press = 0  
     * 当按键按下时,就会进入按键中断处理函数,此时ev_press被设置为1 
     */  
      if(ev_press)  
    {  
       mask |= POLLIN | POLLRDNORM;  /* POLLIN表示有数据可读   POLLRDNORM表示有普通数据可读*/  
    } 
 /* 如果有按键按下时,mask |= POLLIN | POLLRDNORM,否则mask = 0 */      
    return mask;
}

/* 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中指定的对应函数
 */
static struct file_operations button_ops=  
{    
    .owner    =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open     =  button_open,  
    .read      =     button_read,       
    .release  =  buttons_close, 
    .poll     =  buttons_poll,
}; 
 
/*
 * 执行insmod命令时就会调用这个函数 
 */
  
static int button_init(void)  
{  
    
    /* 注册字符设备
     * 参数为主设备号、设备名字、file_operations结构;
     * 这样,主设备号就和具体的file_operations结构联系起来了,
     * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数
     * LED_MAJOR可以设为0,表示由内核自动分配主设备号
     */
     major = register_chrdev(0, DEVICE_NAME, &button_ops);
      if (major < 0) 
      {
      printk(DEVICE_NAME  " can't register major number number::%d
",major);
      return 0;
      }
    printk(DEVICE_NAME " initialized1
");
    button_class = class_create(THIS_MODULE, "button");
    if (IS_ERR(button_class))
        return PTR_ERR(button_class);
    button_dev_class = class_device_create(button_class, NULL, MKDEV(major, 0), NULL, "my_button"); /* /dev/my_button */ 
    
    
    return 0;

}

/*
 * 执行rmmod命令时就会调用这个函数 
 */
static void button_exit(void)
{
    class_device_unregister(button_dev_class);
    class_destroy(button_class);       
     /* 卸载驱动程序 */
    unregister_chrdev(major, DEVICE_NAME);
}

/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(button_init);  
module_exit(button_exit);  

/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("http://www.100ask.net");// 驱动程序的作者
MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");// 一些描述信息
MODULE_LICENSE("GPL");   // 遵循的协议

test.c

#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdio.h> 
#include <poll.h>
 
int main(int argc,char**argv)  
{  
    int fd;  
	int cnt = 0;
	int ret;
	
	struct pollfd fds[1];/*每一个pollfd结构体指定了一个被监视的文件描述符*/
	
  //打开设备
    fd = open("/dev/my_button",O_RDWR);  
    
	if(fd<0)  
    {  
        perror("open fail 
");  
        return -1;  
    } 
   
   
   fds[0].fd=fd;
   fds[0].events=POLLIN;
   while(1)
	{
		/*poll函数返回0时,表示5s时间到了,而这段时间里,没有事件发生"数据可读" */
		ret=poll(fds,1,5000);
		
	   if (ret==0)
		{
			printf("time out
");
		}
		else /* 如果没有超时,则读出按键值 */  
		{
			read(fd,&cnt, sizeof(cnt)); //read()
			printf("button has been pressed %d timea
",cnt);
	   }
      }
	close(fd);
	return 0;
}	

  

结果:

参考:

http://blog.csdn.net/lwj103862095/article/details/17536069

http://blog.csdn.net/u012719256/article/details/52663292

http://www.cnblogs.com/mylinux/p/5090264.html

http://blog.csdn.net/ruoyunliufeng/article/details/24188693

原文地址:https://www.cnblogs.com/zhaobinyouth/p/6252511.html