针对上一篇按键中断驱动中一些关键函数分析

来源:互联网 发布:java冒泡排序代码 编辑:程序博客网 时间:2024/06/16 22:10

上一篇实现按键采用了中断加延时消抖和进程休眠的方法实现,现在对里面的一些重要函数分析。

注册中断函数!

 ret = request_irq(key_irqs[i].irq, key_interrupt, IRQF_DISABLED, key_irqs[i].name, (void  *)i);//最后一个参数为设备id

              //申请中断,申请成功后返回0  

函数原型:

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);

我们来看参数的意义

在发生对应于第 1个参数 irq 的中断时,则调用第 2 个参数 handler 指定的中断服务函数(也就是把 handler() 中断服务函数注册到内核中 )。
第 3 个参数 flags 指定了快速中断或中断共享等中断处理属性。在2.6内核里对它的描述如下:

/*
* These flags used only by the kernel as part of the
* irq handling routines.
*
* IRQF_DISABLED - keep irqs disabled when calling the action handler
* IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in an shared interrupt is considered for
* performance reasons)
*/
#define IRQF_DISABLED 0x00000020
#define IRQF_SAMPLE_RANDOM 0x00000040
#define IRQF_SHARED 0x00000080
#define IRQF_PROBE_SHARED 0x00000100
#define IRQF_TIMER 0x00000200
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000

4 个参数 name 通常是设备驱动程序的名称。改值用在 /proc/interrupt系统 (虚拟) 文件上,或内核发生中断错误时使用。
5 个参数 dev_id可作为共享中断时的中断区别参数,也可以用来指定中断服务函数需要参考的数据地址。所以如果几个中断对于同一个中断处理函数,我们就需要用到这个参数了
dev_id!
返回值:

函数运行正常时返回 0 ,否则返回对应错误的负值。

 

Open时第二处如何注册定时器处理函数 如果采用定时消抖的话?

static struct timer_list key_timers[4];  //定义4个去抖定时器

 

使用时钟,先声明一个timer_list结构,调用init_timer对它进行初始化。time_list结构里expires是标明这个时钟的周期,单位采用jiffies的单位。jiffiesLinux一个全局变量,代表时间。它的单位随硬件平台的不同而不同。系统里定义了一个常数HZ,代表每秒种最小时间间隔的数目。这样jiffies的单位就是1/HZIntel平台jiffies的单位是1/100秒,这就是系统所能分辨的最小时间间隔了。所以expires/HZ就是以秒为单位的这个时钟的周期。function就是时间到了以后的回调函数,它的参数就是timer_list中的 datadata这个参数在初始化时钟的时候赋值,一般赋给它设备的device结构指针。在预置时间到系统调用function,同时系统把这个 time_list从定时队列里清除。所以如果需要一直使用定时函数,要在function里再次调用add_timer()把这个timer_list加进定时队列。

首先定义一个timer_list数组关于timer_list结构体原型如下:

struct timer_list {

     struct list_head entry;

     unsigned long expires;

 

     void (*function)(unsigned long);  //指向定时器处理函数定时到就进入

     unsigned long data;

 

     struct tvec_base *base;

#ifdef CONFIG_TIMER_STATS

     void *start_site;

     char start_comm[16];

     int start_pid;

#endif

#ifdef CONFIG_LOCKDEP

     struct lockdep_map lockdep_map;

#endif

};

key_timers[i].function = key_timer;       //注册定时器处理函数

key_timers[i].data = i;   //作为上面函数的形参

init_timer(&key_timers[i]);  //四个按键对应的4个定时器初始化

第三个:进程(使调用该驱动的应用程序)休眠

因为应用程序都是一个死循环,在死循环里面肯定会调用驱动中的read write等函数,如果每次调用驱动都响应,那么cpu资源也会被该应用程序占尽。解决办法就是采用休眠和唤醒机制。

static DECLARE_WAIT_QUEUE_HEAD(key_waitq); //定义并初始化等待队列

static volatile int ev_press = 0;     //按键按下的标识

利用这个宏注册一个休眠队列

注册了之后什么时候让进程(应用程序)休眠,什么时候唤醒进程呢?

我们的思维是:没有发生我们预想的某个事件时,我们让进程休眠起来,当某个事件发生了后,我们唤醒该进程!

所以我们定义一个按键标识ev_press,当没有任何按键按下时它为0

所以当应用调用驱动的读时:我们将应用休眠起来

if(!ev_press)//0 表示没有产生按键按下

     {

         if(file->f_flags & O_NONBLOCK) //如果应用程序采用非阻塞方式读数据则返回错误

         {

              return -EAGAIN;  //返回重试的标识符

         }

         else //阻塞方式当没有按键按下时让等待队列进入睡眠

         {

              wait_event_interruptible(key_waitq, ev_press); //ev_press不成立(0),挂起队列

         }

     }

唤醒,唤醒后从休眠的地方继续执行

ev_press = 1;

              wake_up_interruptible(&key_waitq);   //按键按下唤醒队列