参考:《linux设备驱动开发详解》宋宝华-人民邮电出版社
http://www.cnblogs.com/wang_yb/archive/2013/04/23/3037268.html
中断处理程序架构
设备的中断会打断内核中进程的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽可能地短小精悍。但是,这个良好的愿望往往与现实并不吻合。在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理。为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux 将中断处理程序分解为两个半部:顶半部(top half)和底半部(bottom half)。
顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。
现在,中断处理工作的重心就落在了底半部的头上,它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同,因为顶半部往往被设计成不可中断。底半部则相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。
尽管顶半部、底半部的结合能够改善系统的响应能力,但是,僵化地认为 Linux设备驱动中的中断处理一定要分两个半部则是不对的。如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。
ls /proc/interrupts 查看系统中中断的统计信息
下半部机制
linux实现下半部的机制主要有:tasklet、工作队列、软中断
1.tasklet
tasklet也是利用软中断来实现的,但是它提供了比软中断更好用的接口(其实就是基于软中断又封装了一下),所以除了对性能要求特别高的情况,一般建议使用tasklet来实现自己的中断。
tasklet_struct 结构体
struct tasklet_struct
{
struct tasklet_struct *next; /* 链表中的下一个tasklet */
unsigned long state; /* tasklet状态 */
atomic_t count; /* 引用计数器 */
void (*func)(unsigned long); /* tasklet处理函数 */
unsigned long data; /* tasklet处理函数的参数 */
};
代码 DECLARE_TASKLET(my_tasklet,my_tasklet_func,data)实现了定义名称为my_tasklet 的 tasklet 并将其与 my_tasklet_func()这个函数绑定,而传入这个函数的参数为 data。
DECLARE_TASKLET(name, func, data)
在需要调度 tasklet 的时候引用一个 tasklet_schedule()函数就能使系统在适当的时候进行调度运行,如下所示:
tasklet_schedule(&my_tasklet);
tasklet方式简单实现驱动代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
//#include <mach/gpio-bank.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#include <linux/regulator/consumer.h>
//#include "gps.h"
#include <linux/delay.h>
//中断头文件
#include <linux/irq.h>
#include <linux/interrupt.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("SZA");
int irq_num;
void my_tasklet_func(unsigned long para)
{
printk("my_tasklet_func-----\n para is %d\n irq_num is %d\n",para,irq_num);
}
DECLARE_TASKLET(my_tasklet,my_tasklet_func,1000);
static irq_handler_t home_handler(int irq,void *dev_id)
{
printk(" home_handler ====!!! \n");
irq_num++;
tasklet_schedule(&my_tasklet);
return IRQ_HANDLED;
}
static int irq_pocky_init(void)
{
int ret;
printk("irq_pocky_init ----\n");
ret = request_irq(IRQ_EINT(9),home_handler,IRQ_TYPE_EDGE_FALLING,"home",NULL);
if(ret < 0)
{
printk("irq request fail!!!!!");
}
return 0;
}
static void irq_pocky_exit(void)
{
free_irq(IRQ_EINT(9),NULL);
printk("irq_pocky_exit ----\n");
}
module_init(irq_pocky_init);
module_exit(irq_pocky_exit);
2.工作队列
工作队列的使用方法和 tasklet 非常相似,下面的代码用于定义一个工作队列和一个底半部执行函数。
struct work_struct my_wq; /*定义一个工作队列*/
void my_wq_func(unsigned long); /*定义一个处理函数*/
通过 INIT_WORK()可以初始化这个工作队列并将工作队列与处理函数绑定,如下所示:
INIT_WORK(&my_wq, (void (*)(void *)) my_wq_func, NULL); /*初始化工作队列并将其与处理函数绑定*/
与 tasklet_schedule()对应的用于调度工作队列执行的函数为 schedule_work()
schedule_work(&my_wq); /*调度工作队列执行*/
简单实现驱动代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
//#include <mach/gpio-bank.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#include <linux/regulator/consumer.h>
//#include "gps.h"
#include <linux/delay.h>
//中断头文件
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("SZA");
int irq_num;
struct work_struct my_wq; /* 定义一个工作队列 */
void my_wq_func(unsigned long para) /*定义一个处理函数*/
{
printk("my_tasklet_func-----\n para is %d\n irq_num is %d\n",para,irq_num);
}
static irq_handler_t home_handler(int irq,void *dev_id)
{
printk(" home_handler ====!!! \n");
irq_num++;
schedule_work(&my_wq);
return IRQ_HANDLED;
}
static int irq_work_init(void)
{
int ret;
printk("irq_work_init ----\n");
ret = request_irq(IRQ_EINT(9),home_handler,IRQ_TYPE_EDGE_FALLING,"home",NULL);
if(ret < 0)
{
printk("irq request fail!!!!!\n");
}
INIT_WORK(&my_wq,my_wq_func);
return 0;
}
static void irq_work_exit(void)
{
free_irq(IRQ_EINT(9),NULL);
printk("irq_work_exit ----\n");
}
module_init(irq_work_init);
module_exit(irq_work_exit);
3.软中断
软中断是用软件方式模拟硬件中断的概念,实现宏观上的异步执行效果,tasklet也是基于软中断实现的。
在 Linux 内核中,用 softirq_action 结构体表征一个软中断,这个结构体中包含软中断处理函数指针和传递给该函数的参数。使用 open_softirq()函数可以注册软中断对应的处理函数,而 raise_softirq()函数可以触发一个软中断。
(1)注册软中断函数open_softirq()
/* 将软中断类型和软中断处理函数加入到软中断序列中
*
* @nr - 软中断类型
* @(*action)(struct softirq_action *) - 软中断处理函数指针
*/
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
软中断的类型:
/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
frequency threaded job scheduling. For almost all the purposes
tasklets are more than enough. F.e. all serial device BHs et
al. should be converted to tasklets, not to softirqs.
*/
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
中断处理函数的参数struct softirq_action
/*
* 这个结构体的字段是个函数指针,字段名称是action
* 函数指针的返回指是void型
* 函数指针的参数是 struct softirq_action 的地址,其实就是指向 softirq_vec 中的某一项
* 如果 open_softirq 是这样调用的: open_softirq(NET_TX_SOFTIRQ, my_tx_action);
* 那么 my_tx_action 的参数就是 softirq_vec[NET_TX_SOFTIRQ]的地址
*/
struct softirq_action
{
void (*action)(struct softirq_action *);
};
(2)软中断触发函数raise_softirq
/*
* 触发某个中断类型的软中断
* @nr - 被触发的中断类型
* 从函数中可以看出,在处理软中断前后有保存和恢复寄存器的操作
*/
void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
(3)执行软中断do_softirq()函数
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
/* 判断是否在中断处理中,如果正在中断处理,就直接返回 */
if (in_interrupt())
return;
/* 保存当前寄存器的值 */
local_irq_save(flags);
/* 取得当前已注册软中断的位图 */
pending = local_softirq_pending();
/* 循环处理所有已注册的软中断 */
if (pending)
__do_softirq();
/* 恢复寄存器的值到中断处理前 */
local_irq_restore(flags);
}
简单驱动代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
//#include <mach/gpio-bank.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#include <linux/regulator/consumer.h>
//#include "gps.h"
#include <linux/delay.h>
//中断头文件
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("SZA");
int irq_num;
void my_action(struct softirq_action * softirq_action)
{
printk("my_tasklet_func-----\n irq_num is %d\n",irq_num);
}
static irq_handler_t home_handler(int irq,void *dev_id)
{
printk(" home_handler ====!!! \n");
irq_num++;
raise_softirq(TASKLET_SOFTIRQ);
return IRQ_HANDLED;
}
static int irq_soft_init(void)
{
int ret;
printk("irq_soft_init ----\n");
ret = request_irq(IRQ_EINT(9),home_handler,IRQ_TYPE_EDGE_FALLING,"home",NULL);
if(ret < 0)
{
printk("irq request fail!!!!!\n");
}
open_softirq(TASKLET_SOFTIRQ,my_action);
return 0;
}
static void irq_soft_exit(void)
{
free_irq(IRQ_EINT(9),NULL);
printk("irq_soft_exit ----\n");
}
module_init(irq_soft_init);
module_exit(irq_soft_exit);
由于内核没有用EXPORT_SYMBOL导出open_softirq和raise_softirq函数,所以编译时有如下警告:
WARNING: "open_softirq" [/root/chap08/mysoftirq.ko] undefined! WARNING: "raise_softirq" [/root/chap08/mysoftirq.ko] undefined!
于是修改内核代码kernel/softirq.c:
void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
EXPORT_SYMBOL(raise_softirq); /* 追加的代码 */
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
EXPORT_SYMBOL(open_softirq); /* 追加的代码 */