上一篇实现按键采用了中断加延时消抖和进程休眠的方法实现,现在对里面的一些重要函数分析。
注册中断函数!
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的单位。jiffies是Linux一个全局变量,代表时间。它的单位随硬件平台的不同而不同。系统里定义了一个常数HZ,代表每秒种最小时间间隔的数目。这样jiffies的单位就是1/HZ。Intel平台jiffies的单位是1/100秒,这就是系统所能分辨的最小时间间隔了。所以expires/HZ就是以秒为单位的这个时钟的周期。function就是时间到了以后的回调函数,它的参数就是timer_list中的 data。data这个参数在初始化时钟的时候赋值,一般赋给它设备的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); //按键按下唤醒队列