2012年(66)
分类:
2012-07-26 11:26:49
原文地址:linux 中断机制的处理过程 作者:fly123456789
#define IRQF_TRIGGER_FALLING 0x00000002/*中断触发类型:下降沿有效*/
#define IRQF_TRIGGER_HIGH 0x00000004/*指定中断触发类型:高电平有效*/
#define IRQF_TRIGGER_LOW 0x00000008/*指定中断触发类型:低电平有效*/
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010/*触发式检测中断*/
const char *dev_name:设备描述,表示那一个设备在使用这个中断。
void *dev_id:用作共享中断线的指针.。一般设置为这个设备的设备结构体或者NULL。它是一个独特的标识, 用在当释放中断线时以及可能还被驱动用来指向它自己的私有数据区,来标识哪个设备在中断 。这个参数在真正的驱动程序中一般是指向设备数据结构的指针.在调用中断处理程序的时候它就会传递给中断处理程序的void *dev_id。如果中断没有被共享, dev_id 可以设置为 NULL。
II、释放IRQ
void free_irq(unsigned int irq, void *dev_id);
III、中断线共享的数据结构
struct irqaction {
irq_handler_t handler; /* 具体的中断处理程序 */
unsigned long flags;/*中断处理属性*/
const char *name; /* 名称,会显示在/proc/interreupts 中 */
void *dev_id; /* 设备ID,用于区分共享一条中断线的多个处理程序 */
struct irqaction *next; /* 指向下一个irq_action 结构 */
int irq; /* 中断通道号 */
struct proc_dir_entry *dir; /* 指向proc/irq/NN/name 的入口*/
irq_handler_t thread_fn;/*线程中断处理函数*/
struct task_struct *thread;/*线程中断指针*/
unsigned long thread_flags;/*与线程有关的中断标记属性*/
};
thread_flags参见枚举型
enum {
IRQTF_RUNTHREAD,/*线程中断处理*/
IRQTF_DIED,/*线程中断死亡*/
IRQTF_WARNED,/*警告信息*/
IRQTF_AFFINITY,/*调整线程中断的关系*/
};
多个中断处理程序可以共享同一条中断线,irqaction 结构中的 next 成员用来把共享同一条中断线的所有中断处理程序组成一个单向链表,dev_id 成员用于区分各个中断处理程序。
根据以上内容可以得出中断机制各个数据结构之间的联系如下图所示:
三.中断的处理过程
Linux中断分为两个半部:上半部(tophalf)和下半部(bottom half)。上半部的功能是"登记中断",当一个中断发生时,它进行相应地硬件读写后就把中断例程的下半部挂到该设备的下半部执行队列中去。因此,上半部执行的速度就会很快,可以服务更多的中断请求。但是,仅有"登记中断"是远远不够的,因为中断的事件可能很复杂。因此,Linux引入了一个下半部,来完成中断事件的绝大多数使命。下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。
中断号的查看可以使用下面的命令:“cat /proc/interrupts”。
Linux实现下半部的机制主要有tasklet和工作队列。
小任务tasklet的实现
其数据结构为struct tasklet_struct,每一个结构体代表一个独立的小任务,定义如下
struct tasklet_struct
{
struct tasklet_struct *next;/*指向下一个链表结构*/
unsigned long state;/*小任务状态*/
atomic_t count;/*引用计数器*/
void (*func)(unsigned long);/*小任务的处理函数*/
unsigned long data;/*传递小任务函数的参数*/
};
state的取值参照下边的枚举型:
enum
{ TASKLET_STATE_SCHED, /* 小任务已被调用执行*/
TASKLET_STATE_RUN /*仅在多处理器上使用*/
};
count域是小任务的引用计数器。只有当它的值为0的时候才能被激活,并其被设置为挂起状态时,才能够被执行,否则为禁止状态。
I、声明和使用小任务tasklet
静态的创建一个小任务的宏有一下两个:
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
这两个宏的区别在于计数器设置的初始值不同,前者可以看出为0,后者为1。为0的表示激活状态,为1的表示禁止状态。其中ATOMIC_INIT宏为:
#define ATOMIC_INIT(i) { (i) }
即可看出就是设置的数字。此宏在include/asm-generic/atomic.h中定义。这样就创建了一个名为name的小任务,其处理函数为func。当该函数被调用的时候,data参数就被传递给它。
II、小任务处理函数程序
处理函数的的形式为:void my_tasklet_func(unsigned long data)。这样DECLARE_TASKLET(my_tasklet, my_tasklet_func, data)实现了小任务名和处理函数的绑定,而data就是函数参数。
III、调度编写的tasklet
调度小任务时引用tasklet_schedule(&my_tasklet)函数就能使系统在合适的时候进行调度。函数原型为:
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
这个调度函数放在中断处理的上半部处理函数中,这样中断申请的时候调用处理函数(即irq_handler_t handler)后,转去执行下半部的小任务。
如果希望使用DECLARE_TASKLET_DISABLED(name,function,data)创建小任务,那么在激活的时候也得调用相应的函数被使能
tasklet_enable(struct tasklet_struct *); //使能tasklet
tasklet_disble(struct tasklet_struct *); //禁用tasklet
tasklet_init(struct tasklet_struct *,void (*func)(unsigned long),unsigned long);
当然也可以调用tasklet_kill(struct tasklet_struct *)从挂起队列中删除一个小任务。清除指定tasklet的可调度位,即不允许调度该tasklet 。
使用tasklet作为下半部的处理中断的设备驱动程序模板如下:
/*定义tasklet和下半部函数并关联*/
void my_do_tasklet(unsigned long);
DECLARE_TASKLET(my_tasklet, my_tasklet_func, 0);
/*中断处理下半部*/
void my_do_tasklet(unsigned long)
{
……/*编写自己的处理事件内容*/
}
/*中断处理上半部*/
irpreturn_t my_interrupt(unsigned int irq,void *dev_id)
{
……
tasklet_schedule(&my_tasklet)/*调度my_tasklet函数,根据声明将去执行my_tasklet_func函数*/
……
}
/*设备驱动的加载函数*/
int __init xxx_init(void)
{
……
/*申请中断, 转去执行my_interrupt函数并传入参数*/
result=request_irq(my_irq,my_interrupt,IRQF_DISABL ED,"xxx",NULL);
……
}
/*设备驱动模块的卸载函数*/
void __exit xxx_exit(void)
{
……
/*释放中断*/
free_irq(my_irq,my_interrupt);
……
} 工作队列的实现
工作队列work_struct结构体,位于/include/linux/workqueue.h
typedef void (*work_func_t)(struct work_struct *work);
struct work_struct {
atomic_long_t data; /*传递给处理函数的参数*/
#define WORK_STRUCT_PENDING 0/*这个工作是否正在等待处理标志*/
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
struct list_head entry; /* 连接所有工作的链表*/
work_func_t func; /* 要执行的函数*/
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
这些结构被连接成链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。可以通过DECLARE_WORK在编译时静态地创建该结构,以完成推后的工作。
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
而后边这个宏为一下内容:
#define __WORK_INITIALIZER(n, f) { \
.data = http://blog.soso.com/qz.q/WORK_DATA_INIT(), /
.entry = { &(n).entry, &(n).entry }, \
.func = (f), \
__WORK_INIT_LOCKDEP_MAP(#n, &(n)) \
}
其为参数data赋值的宏定义为:
#define WORK_DATA_INIT() ATOMIC_LONG_INIT(0)
这样就会静态地创建一个名为n,待执行函数为f,参数为data的work_struct结构。同样,也可以在运行时通过指针创建一个工作:
INIT_WORK(struct work_struct *work, void(*func) (void *));
这会动态地初始化一个由work指向的工作队列,并将其与处理函数绑定。宏原型为:
#define INIT_WORK(_work, _func) \
do { \
static struct lock_class_key __key; \
\
(_work)->data = http://blog.soso.com/qz.q/(atomic_long_t) WORK_DATA_INIT(); /
lockdep_init_map(&(_work)->lockdep_map, #_work, &__key, 0);\
INIT_LIST_HEAD(&(_work)->entry); \
PREPARE_WORK((_work), (_func)); \
} while (0)
在需要调度的时候引用类似tasklet_schedule()函数的相应调度工作队列执行的函数schedule_work(),如:
schedule_work(&work);/*调度工作队列执行*/
如果有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。在这种情况下,可以调度指定的时间后执行函数:
schedule_delayed_work(&work,delay);函数原型为:
int schedule_delayed_work(struct delayed_work *work, unsigned long delay);
其中是以delayed_work为结构体的指针,而这个结构体的定义是在work_struct结构体的基础上增加了一项timer_list结构体。
struct delayed_work {
struct work_struct work;
struct timer_list timer; /* 延迟的工作队列所用到的定时器,当不需要延迟时初始化为NULL*/
};
这样,便使预设的工作队列直到delay指定的时钟节拍用完以后才会执行。
使用工作队列处理中断下半部的设备驱动程序模板如下:
/*定义工作队列和下半部函数并关联*/
struct work_struct my_wq;
void my_do_work(unsigned long);
/*中断处理下半部*/
void my_do_work(unsigned long)
{
……/*编写自己的处理事件内容*/
}
/*中断处理上半部*/
irpreturn_t my_interrupt(unsigned int irq,void *dev_id) { …… schedule_work(&my_wq)/*调度my_wq函数,根据工作队列初始化函数将去执行my_do_work函数*/
……
}
/*设备驱动的加载函数*/
int __init xxx_init(void)
{
……
/*申请中断,转去执行my_interrupt函数并传入参数*/
result=request_irq(my_irq,my_interrupt,IRQF_DISABL ED,"xxx",NULL);
……
/*初始化工作队列函数,并与自定义处理函数关联*/
INIT_WORK(&my_irq,(void (*)(void *))my_do_work);
……
}
/*设备驱动模块的卸载函数*/
void __exit xxx_exit(void)
{
……
/*释放中断*/
free_irq(my_irq,my_interrupt);
……
}