Linux内核的中断机制分析栢图实验室Word文档下载推荐.docx
《Linux内核的中断机制分析栢图实验室Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《Linux内核的中断机制分析栢图实验室Word文档下载推荐.docx(11页珍藏版)》请在冰豆网上搜索。
/*高层次的中断事件处理函数*/
structirq_chip*chip;
/*低层次的硬件操作*/
structmsi_desc*msi_desc;
void*handler_data;
/*chip方法使用的数据*/
void*chip_data;
/*chip私有数据*/
structirqaction*action;
/*行为链表(actionlist)*/
unsignedintstatus;
/*状态*/
unsignedintdepth;
/*关中断次数*/
unsignedintwake_depth;
/*唤醒次数*/
unsignedintirq_count;
/*发生的中断次数*/
unsignedlonglast_unhandled;
/*滞留时间*/
unsignedintirqs_unhandled;
spinlock_tlock;
/*自选锁*/
#ifdefCONFIG_SMP
cpumask_var_taffinity;
unsignedintnode;
#ifdefCONFIG_GENERIC_PENDING_IRQ
cpumask_var_tpending_mask;
atomic_tthreads_active;
wait_queue_head_twait_for_threads;
#ifdefCONFIG_PROC_FS
structproc_dir_entry*dir;
/*在proc文件系统中的目录*/
constchar*name;
/*名称*/
}____cacheline_internodealigned_in_smp;
I、Linux中断的申请与释放:
在<
linux/interrupt.h>
,实现中断申请接口:
request_irq(unsignedintirq,irq_handler_thandler,unsignedlongflags,constchar*name,void*dev);
函数参数说明
unsignedintirq:
所要申请的硬件中断号
irq_handler_thandler:
中断服务程序的入口地址,中断发生时,系统调用handler这个函数。
irq_handler_t为自定义类型,其原型为:
typedefirqreturn_t(*irq_handler_t)(int,void*);
而irqreturn_t的原型为:
typedefenumirqreturnirqreturn_t;
enumirqreturn{
IRQ_NONE,/*此设备没有产生中断*/
IRQ_HANDLED,/*中断被处理*/
IRQ_WAKE_THREAD,/*唤醒中断*/
};
在枚举类型irqreturn定义在include/linux/irqreturn.h文件中。
unsignedlongflags:
中断处理的属性,与中断管理有关的位掩码选项,有一下几组值:
#defineIRQF_DISABLED0x00000020/*中断禁止*/
#defineIRQF_SAMPLE_RANDOM0x00000040/*供系统产生随机数使用*/
#defineIRQF_SHARED0x00000080/*在设备之间可共享*/
#defineIRQF_PROBE_SHARED0x00000100/*探测共享中断*/
#defineIRQF_TIMER0x00000200/*专用于时钟中断*/
#defineIRQF_PERCPU0x00000400/*每CPU周期执行中断*/
#defineIRQF_NOBALANCING0x00000800/*复位中断*/
#defineIRQF_IRQPOLL0x00001000/*共享中断中根据注册时间判断*/
#defineIRQF_ONESHOT0x00002000/*硬件中断处理完后触发*/
#defineIRQF_TRIGGER_NONE0x00000000/*无触发中断*/
#defineIRQF_TRIGGER_RISING0x00000001/*指定中断触发类型:
上升沿有效*/
#defineIRQF_TRIGGER_FALLING0x00000002/*中断触发类型:
下降沿有效*/
#defineIRQF_TRIGGER_HIGH0x00000004/*指定中断触发类型:
高电平有效*/
#defineIRQF_TRIGGER_LOW0x00000008/*指定中断触发类型:
低电平有效*/
#defineIRQF_TRIGGER_MASK(IRQF_TRIGGER_HIGH|IRQF_TRIGGER_LOW|\
IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING)
#defineIRQF_TRIGGER_PROBE0x00000010/*触发式检测中断*/
constchar*dev_name:
设备描述,表示那一个设备在使用这个中断。
void*dev_id:
用作共享中断线的指针.。
一般设置为这个设备的设备结构体或者NULL。
它是一个独特的标识,用在当释放中断线时以及可能还被驱动用来指向它自己的私有数据区,来标识哪个设备在中断。
这个参数在真正的驱动程序中一般是指向设备数据结构的指针.在调用中断处理程序的时候它就会传递给中断处理程序的void*dev_id。
如果中断没有被共享,dev_id可以设置为NULL。
II、释放IRQ
voidfree_irq(unsignedintirq,void*dev_id);
III、中断线共享的数据结构
structirqaction{
irq_handler_thandler;
/*具体的中断处理程序*/
unsignedlongflags;
/*中断处理属性*/
/*名称,会显示在/proc/interreupts中*/
void*dev_id;
/*设备ID,用于区分共享一条中断线的多个处理程序*/
structirqaction*next;
/*指向下一个irq_action结构*/
intirq;
/*中断通道号*/
/*指向proc/irq/NN/name的入口*/
irq_handler_tthread_fn;
/*线程中断处理函数*/
structtask_struct*thread;
/*线程中断指针*/
unsignedlongthread_flags;
/*与线程有关的中断标记属性*/
thread_flags参见枚举型
enum{
IRQTF_RUNTHREAD,/*线程中断处理*/
IRQTF_DIED,/*线程中断死亡*/
IRQTF_WARNED,/*警告信息*/
IRQTF_AFFINITY,/*调整线程中断的关系*/
多个中断处理程序可以共享同一条中断线,irqaction结构中的next成员用来把共享同一条中断线的所有中断处理程序组成一个单向链表,dev_id成员用于区分各个中断处理程序。
根据以上内容可以得出中断机制各个数据结构之间的联系如下图所示:
三.中断的处理过程
Linux中断分为两个半部:
上半部(tophalf)和下半部(bottomhalf)。
上半部的功能是"
登记中断"
,当一个中断发生时,它进行相应地硬件读写后就把中断例程的下半部挂到该设备的下半部执行队列中去。
因此,上半部执行的速度就会很快,可以服务更多的中断请求。
但是,仅有"
是远远不够的,因为中断的事件可能很复杂。
因此,Linux引入了一个下半部,来完成中断事件的绝大多数使命。
下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!
下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。
中断号的查看可以使用下面的命令:
“cat/proc/interrupts”。
Linux实现下半部的机制主要有tasklet和工作队列。
小任务tasklet的实现
其数据结构为structtasklet_struct,每一个结构体代表一个独立的小任务,定义如下
structtasklet_struct
{
structtasklet_struct*next;
/*指向下一个链表结构*/
unsignedlongstate;
/*小任务状态*/
atomic_tcount;
/*引用计数器*/
void(*func)(unsignedlong);
/*小任务的处理函数*/
unsignedlongdata;
/*传递小任务函数的参数*/
state的取值参照下边的枚举型:
enum
TASKLET_STATE_SCHED,/*小任务已被调用执行*/
TASKLET_STATE_RUN/*仅在多处理器上使用*/
count域是小任务的引用计数器。
只有当它的值为0的时候才能被激活,并其被设置为挂起状态时,才能够被执行,否则为禁止状态。
I、声明和使用小任务tasklet
静态的创建一个小任务的宏有一下两个:
#defineDECLARE_TASKLET(name,func,data)\
structtasklet_structname={NULL,0,ATOMIC_INIT(0),func,data}
#defineDECLARE_TASKLET_DISABLED(name,func,data)\
structtasklet_structname={NULL,0,ATOMIC_INIT
(1),func,data}
这两个宏的区别在于计数器设置的初始值不同,前者可以看出为0,后者为1。
为0的表示激活状态,为1的表示禁止状态。
其中ATOMIC_INIT宏为:
#defineATOMIC_INIT(i){(i)}
即可看出就是设置的数字。
此宏在include/asm-generic/atomic.h中定义。
这样就创建了一个名为name的小任务,其处理函数为func。
当该函数被调用的时候,data参数就被传递给它。
II、小任务处理函数程序
处理函数的的形式为:
voidmy_tasklet_func(unsignedlongdata)。
这样DECLARE_TASKLET(my_tasklet,my_tasklet_func,data)实现了小任务名和处理函数的绑定,而data就是函数参数。
III、调度编写的tasklet
调度小任务时引用tasklet_schedule(&
my_tasklet)函数就能使系统在合适的时候进行调度。
函数原型为:
staticinlinevoidtasklet_schedule(structtasklet_struct*t)
if(!
test_and_set_bit(TASKLET_STATE_SCHED,&
t->
state))
__tasklet_schedule(t);
}
这个调度函数放在中断处理的上半部处理函数中,这样中断申请的时候调用处理函数(即irq_handler_thandler)后,转去执行下半部的小任务。
如果希望使用DECLARE_TASKLET_DISABLED(name,function,data)创建小任务,那么在激活的时候也得调用相应的函数被使能
tasklet_enable(structtasklet_struct*);
//使能tasklet
tasklet_disble(structtasklet_struct*);
//禁用tasklet
tasklet_init(structtasklet_struct*,void(*func)(unsignedlong),unsignedlong);
当然也可以调用tasklet_kill(structtasklet_struct*)从挂起队列中删除一个小任务。
清除指定tasklet的可调度位,即不允许调度该tasklet。
使用tasklet作为下半部的处理中断的设备驱动程序模板如下:
/*定义tasklet和下半部函数并关联*/
voidmy_do_tasklet(unsignedlong);
DECLARE_TASKLET(my_tasklet,my_tasklet_func,0);
/*中断处理下半部*/
voidmy_do_tasklet(unsignedlong)
……/*编写自己的处理事件内容*/
/*中断处理上半部*/
irpreturn_tmy_interrupt(unsignedintirq,void*dev_id)
……
tasklet_schedule(&
my_tasklet)/*调度my_tasklet函数,根据声明将去执行my_tasklet_func函数*/
/*设备驱动的加载函数*/
int__initxxx_init(void)
/*申请中断,转去执行my_interrupt函数并传入参数*/
result=request_irq(my_irq,my_interrupt,IRQF_DISABLED,"
xxx"
NULL);
/*设备驱动模块的卸载函数*/
void__exitxxx_exit(void)
/*释放中断*/
free_irq(my_irq,my_interrupt);
工作队列的实现
工作队列work_struct结构体,位于/include/linux/workqueue.h
typedefvoid(*work_func_t)(structwork_struct*work);
structwork_struct{
atomic_long_tdata;
/*传递给处理函数的参数*/
#defineWORK_STRUCT_PENDING0/*这个工作是否正在等待处理标志*/
#defineWORK_STRUCT_FLAG_MASK(3UL)
#defineWORK_STRUCT_WQ_DATA_MASK(~WORK_STRUCT_FLAG_MASK)
structlist_headentry;
/*连接所有工作的链表*/
work_func_tfunc;
/*要执行的函数*/
#ifdefCONFIG_LOCKDEP
structlockdep_maplockdep_map;
这些结构被连接成链表。
当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。
工作被执行完毕,它就将相应的work_struct对象从链表上移去。
当链表上不再有对象的时候,它就会继续休眠。
可以通过DECLARE_WORK在编译时静态地创建该结构,以完成推后的工作。
#defineDECLARE_WORK(n,f)\
structwork_structn=__WORK_INITIALIZER(n,f)
而后边这个宏为一下内容:
#define__WORK_INITIALIZER(n,f){\
.data=WORK_DATA_INIT(),\
.entry={&
(n).entry,&
(n).entry},\
.func=(f),\
__WORK_INIT_LOCKDEP_MAP(#n,&
(n))\
其为参数data赋值的宏定义为:
#defineWORK_DATA_INIT()ATOMIC_LONG_INIT(0)
这样就会静态地创建一个名为n,待执行函数为f,参数为data的work_struct结构。
同样,也可以在运行时通过指针创建一个工作:
INIT_WORK(structwork_struct*work,void(*func)(void*));
这会动态地初始化一个由work指向的工作队列,并将其与处理函数绑定。
宏原型为:
#defineINIT_WORK(_work,_func)\
do{\
staticstructlock_class_key__key;
\
\
(_work)->
data=(atomic_long_t)WORK_DATA_INIT();
lockdep_init_map(&
lockdep_map,#_work,&
__key,0);
INIT_LIST_HEAD(&
entry);
PREPARE_WORK((_work),(_func));
}while(0)
在需要调度的时候引用类似tasklet_schedule()函数的相应调度工作队列执行的函数schedule_work(),如:
schedule_work(&
work);
/*调度工作队列执行*/
如果有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。
在这种情况下,可以调度指定的时间后执行函数:
schedule_delayed_work(&
work,delay);
intschedule_delayed_work(structdelayed_work*work,unsignedlongdelay);
其中是以delayed_work为结构体的指针,而这个结构体的定义是在work_struct结构体的基础上增加了一项timer_list结构体。
structdelayed_work{
structwork_structwork;
structtimer_listtimer;
/*延迟的工作队列所用到的定时器,当不需要延迟时初始化为NULL*/
这样,便使预设的工作队列直到delay指定的时钟节拍用完以后才会执行。
使用工作队列处理中断下半部的设备驱动程序模板如下:
/*定义工作队列和下半部函数并关联*/
structwork_structmy_wq;
voidmy_do_work(unsignedlong);
voidmy_do_work(unsignedlong)
my_wq)/*调度my_wq函数,根据工作队列初始化函数将去执行my_do_work函数*/
/*申请中断,转去执行my_interrupt函数并传入参数*/
/*初始化工作队列函数,并与自定义处理函数关联*/
INIT_WORK(&
my_irq,(void(*)(void*))my_do_work);