5-1 Linux内核中断机制(中断的申请和释放、中断低半部tasklet队列和workqueue队列、中断共享的实现)
2013-07-11 17:35
375 查看
5-1 Linux内核中断机制
中断处理流程
中断源
设备驱动中中断处理例程的实现。
内核中实现计时、延时操作的函数。
1、
什么是中断?
2、
中断的分类:
2.1按中断源分类:内部中断、外部中断
2.2按中断是否可屏蔽分类:可屏蔽中断、不可屏蔽中断(NMI)
2.3按中断入口跳转方法的不同分类:向量中断、非向量中断。
3、申请和释放IRQ
int request_irq(unsigned int irq, //要申请的中断号。
Irqreturn_t (*handler)(int,void*,struct pt_regs*),//要安装的中断处理函数的指针
Unsigned long flags, //填写中断类型
Const char *dev_name,
Void *dev_id); //用于共享的中断数据线。它是用来唯一的标识设备。
void free_irq(unsigned int irq,void *dev_id);//释放,
注:flags: 0:普通外部中断 SA_INTERRUPT:快速中断 SA_SHIRQ:共享中断
申请和释放实例:
/*中断处理函数*/
irqreturn_t xxx_interrupt(int irq,void* dev_id,struct pt_regs *regs)
{
……
中断的具体的内容
……
}
/*设备启动模块的加载函数*/
int __init xxx_init(void)
{
…
/*申请中断*/
result=request_ir(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL);//xxx_irq取多少要根据芯片手册来决定。
…
}
/*设备驱动模块卸载函数*/
void __exit xxx_exit(void)
{
……
/*释放中断*/
free_irp(xxx_irq,NULL);
…
}
4、
Linux中断处理流程
产生中断
->跳转到中断向量表入口地址(一般放在高位,在arch/arm/kernel/entry-armv.S汇编级的工作,主要保存上下文状态)
->asm_do_IRQ(中断处理公共段在arch/arm/kernel/irq.c根据irq编号从已申请的中断(通过request_irq)找到irq编号对应的中断处理函数)
->执行对应的中断处理函数 (自己编写的处理函数)
->返回到asm_do_IRQ()
->返回到entry-armv.S
(恢复到中断前的上下文状态,结束中断处理,继续执行中断发生前的程序)
5、使能和屏蔽中断
5.1禁止/使能单个中断
void disable_irq(int irq);//等待目前的中断处理完成,禁用该IRQ
void disable_irq_nosync(int irq);//禁用并立即返回
void enable_irq(int irq);
5.2禁止/使能所有的中断
void local_irq_disable(void);//所有的中断都禁用
void local_irq_enable(void); //允许所有中断
void local_irq_save(unsigned long flags);//把中断状态保存到flags中,禁用所有中断。
void local_irq_restore(unsigned long flags);//把中断状态flags恢复,允许所有中断。
6、
为什么将中断处理程序分成顶半部和低半部。
6.1需求:
中断处理要求尽快结束,而不能使中断阻塞的时间过长。而有些处理例程要完成耗时的任务。
6.2:解决方案:
顶半部:让中断尽可能的短。
低半部:完成耗时的任务。
7、
顶半部:是实际的中断例程,用request_irq注册的中断例程,它在很短的时间内完成。
8、
低半部:被顶半部调度,并在稍后更安全的时间执行的例程。
9、
实现低半部的机制
1.tasklet(任队列) 2.Work queue(工作队列)
9.1
tasklet(任队列):
速度快,优先选择。原子操作,运行于中断上下文。
9.2
Work queue(工作队列):高延时,运行休眠,运行于上下文。
10、tasklet的使用
10.1声明tasklet
DECLARE_TASKLET(name,function,data);//低半部执行的函数
name:tasklet的名字
funciton:执行tasklet时调用的函数(低半部的函数入口地址)
data:(function函数的参数)一个用来传递给tasklet函数的unsigned
long 类型的值。
如:DECLARE_TASKLECT(xxx_tasklet,xxx_do_tasklet,0);
10.2调度tasklet
void tasklet_schedule(struct tasklet_struct *t)//执行时,它不会直接直接调用function,而是调用tasklet_schedule()函数。然后它自己会间接地调用function()函数。
11、tasklet使用模板。
/*定义tasklet和低半部函数并关联*/
void xxx_do_tasklet(unsigned long);//低半部函数的声明
DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);//定义一个xxx_tasklet对象,xxx_do_tasklet是一个函数。
/*中断处理低半部*/
void xxx_do_taskle(unsigned long)
{
……
}
/*中断处理顶半部*/
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs* reg)
{
……
tasklet_schedule(&xxx_tasklet);
}
12、Workqueue工作队列
12.1创建新的工作队列
struct workqueue_struct* create_workqueue(const char* name); //一个工作队列可对应多个“内核线程”
struct workqueue_struct* create_singlethread_workqueue(const char* name);//一个工作队列对应单个线程。
12.2 向工作队列提交任务,首先填充一个work_struct结构(work_stuct是干什么用的)。
DECLARE_WORK(name, void(*function)(void*), void *data);
INIT_WORK(struct work_struct*
work, void(* function)(void *), void* data); //要填充work的,并与低半部函数function关联起来。
PREPARE_WORK(struct work_struct*
work, void(* function)(void* ), void* data);//不会初始化用来将work_struct结构连接到工作队列的指针,一般适用于任务已经提交,只是修改了任务时使用PREPARE_WORK.
12.3、提交任务
int queue_work(struct
workqueue_struct* queue,struct work_sturct* work );
int queue_delayed_work(struct workqueue_struct* queue,struct work_struct *work,unsigned long delay);//实际的工作至少会在经过指定的jiffies(由delay指定)之后才会被执行
12.4、取消某个队列的入口项
int cancel_delayed_work(struct work_struct *work);
// 返回!=0:内核会确保不会初始化给定入口项的执行。
// 返回==0:则说明该入口项已经在其他的处理器上运行(工作队列可用于多处理器,而tasklet只用于单处理器),因此在cancel_delayed_work返回后可能人在运行,怎么办?使用下面那个函数
需要强制刷新工作队列:void flush_workqueue(struct workqueue_struct* queue);
12.5、销毁工作队列
void destroy_workqueue(struct workqueue_struct *queue);
上面的工作队列会比较繁杂。(下面有简单的)
13、共享工作队列
13.1在许多情况下,驱动不需要有自己的工作队列,只是偶然地向工作队列添加任务。
13.2使用内核提供的共享的默认工作队列。
13.3不应该长期独占该队列,即不能长时间休眠。
13.4我们的任务可能需要更长的时间延时才能获得处理器时间。
14、使用共享队列
14.1初始化
INIT_WORK(struct work_stuct* work,void (*function)(void),void *data);//自定义一个任务结构体对象。也是要填充work的,并与低半部函数function关联起来。
14.2 调度
int schedule_work(struct work_struct *work);//立即调用
int schedule_delayed_work(struct work_struct *work,unsigned long delay);//延时调用
14.3取消共享工作队列的一个入口项(即一个工作任务work)
int cancel_delay_work(struct work_sruct *work);
14.4刷新共享工作队列
void flush_scheduled_work(void);
14.5共享队列的实例:
Work queue的使用模板
/*定义工作队列和关联函数*/
struct work_struct
xxx_wq;//要使用共享工作队列,首先要定义一个工作任务结构对象。
void xxx_do_work(unsigned long);//低半部函数的声明
/*中断处理低半部*/
void
xxx_do_work(unsigned long)
{
…
}
/*中断处理顶半部*/
irqreturn_t
xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
…
shedule_work(&xxx_wq);//调用shedule_work()函数实现顶半部跳转到底半部函数。
…
}
/*设备驱动模块加载函数*/
int__init xxx_init(void)
{
……
/*申请中断,当然申请的顶半部的函数*/
result=request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL);//顶半部函数
/*初始化工作队列*/
INIT_WORK(&xxx_wq,(void (*)(void *))
xxx_do_work,NULL);//任务结构,低半部函数
……
}
15、中断共享
15.1 Linux中断共享:多个设备使用同一个中断线号,同一个中断设备线号的所有处理程序链接成一个链表。
15.2 共享中断的多个设备在申请中断时都应使用SA_SHIRQ标志。
15.3、设备结构指针可以作为request_irq(…,void *dev_id)的最好一个参数dev_id传入,dev_id这个参数必须是唯一的,用来标志一个唯一的设备。
15.4、在中断到来时,对应链表的所有共享该中断的中断处理程序都被执行,他们会检查dev_id参数信息,并根据硬件中断寄存器中的信息判断是否本设备的中断,如果不是,应迅速返回,如果是,则处理完成,如果链表中没有一个是,则说明出现错误。
15.5、中断共享模板
/*设备驱动模块加载函数*/
int xxx_init(void)
{
……
/*申请共享中断*/
result=request_irq(sh_irq,xxx_interrupt,SA_SHIRQ,”xxx”,xxx_dev);
……
}
/*设备驱动模块卸载函数*/
void __exit xxx_exit(void)
{
…
/*释放中断*/
free_irq(xxx_irq,xxx_dev);
…
}
/*中断处理顶部*/
irqreturn_t
xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
…
int status=read_int_status();//获取中断源
if(!is_myint(dev_int,status))//判断是否本设备中断
{
return IRQ_NONE;//通知内核该中断不需要自己处理
}
…
return IRQ_HANDLED; //通知内核处理该中断
}
声明:本文非原创,整理自申嵌
中断处理流程
中断源
设备驱动中中断处理例程的实现。
内核中实现计时、延时操作的函数。
1、
什么是中断?
2、
中断的分类:
2.1按中断源分类:内部中断、外部中断
2.2按中断是否可屏蔽分类:可屏蔽中断、不可屏蔽中断(NMI)
2.3按中断入口跳转方法的不同分类:向量中断、非向量中断。
3、申请和释放IRQ
int request_irq(unsigned int irq, //要申请的中断号。
Irqreturn_t (*handler)(int,void*,struct pt_regs*),//要安装的中断处理函数的指针
Unsigned long flags, //填写中断类型
Const char *dev_name,
Void *dev_id); //用于共享的中断数据线。它是用来唯一的标识设备。
void free_irq(unsigned int irq,void *dev_id);//释放,
注:flags: 0:普通外部中断 SA_INTERRUPT:快速中断 SA_SHIRQ:共享中断
申请和释放实例:
/*中断处理函数*/
irqreturn_t xxx_interrupt(int irq,void* dev_id,struct pt_regs *regs)
{
……
中断的具体的内容
……
}
/*设备启动模块的加载函数*/
int __init xxx_init(void)
{
…
/*申请中断*/
result=request_ir(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL);//xxx_irq取多少要根据芯片手册来决定。
…
}
/*设备驱动模块卸载函数*/
void __exit xxx_exit(void)
{
……
/*释放中断*/
free_irp(xxx_irq,NULL);
…
}
4、
Linux中断处理流程
产生中断
->跳转到中断向量表入口地址(一般放在高位,在arch/arm/kernel/entry-armv.S汇编级的工作,主要保存上下文状态)
->asm_do_IRQ(中断处理公共段在arch/arm/kernel/irq.c根据irq编号从已申请的中断(通过request_irq)找到irq编号对应的中断处理函数)
->执行对应的中断处理函数 (自己编写的处理函数)
->返回到asm_do_IRQ()
->返回到entry-armv.S
(恢复到中断前的上下文状态,结束中断处理,继续执行中断发生前的程序)
5、使能和屏蔽中断
5.1禁止/使能单个中断
void disable_irq(int irq);//等待目前的中断处理完成,禁用该IRQ
void disable_irq_nosync(int irq);//禁用并立即返回
void enable_irq(int irq);
5.2禁止/使能所有的中断
void local_irq_disable(void);//所有的中断都禁用
void local_irq_enable(void); //允许所有中断
void local_irq_save(unsigned long flags);//把中断状态保存到flags中,禁用所有中断。
void local_irq_restore(unsigned long flags);//把中断状态flags恢复,允许所有中断。
6、
为什么将中断处理程序分成顶半部和低半部。
6.1需求:
中断处理要求尽快结束,而不能使中断阻塞的时间过长。而有些处理例程要完成耗时的任务。
6.2:解决方案:
顶半部:让中断尽可能的短。
低半部:完成耗时的任务。
7、
顶半部:是实际的中断例程,用request_irq注册的中断例程,它在很短的时间内完成。
8、
低半部:被顶半部调度,并在稍后更安全的时间执行的例程。
9、
实现低半部的机制
1.tasklet(任队列) 2.Work queue(工作队列)
9.1
tasklet(任队列):
速度快,优先选择。原子操作,运行于中断上下文。
9.2
Work queue(工作队列):高延时,运行休眠,运行于上下文。
10、tasklet的使用
10.1声明tasklet
DECLARE_TASKLET(name,function,data);//低半部执行的函数
name:tasklet的名字
funciton:执行tasklet时调用的函数(低半部的函数入口地址)
data:(function函数的参数)一个用来传递给tasklet函数的unsigned
long 类型的值。
如:DECLARE_TASKLECT(xxx_tasklet,xxx_do_tasklet,0);
10.2调度tasklet
void tasklet_schedule(struct tasklet_struct *t)//执行时,它不会直接直接调用function,而是调用tasklet_schedule()函数。然后它自己会间接地调用function()函数。
11、tasklet使用模板。
/*定义tasklet和低半部函数并关联*/
void xxx_do_tasklet(unsigned long);//低半部函数的声明
DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);//定义一个xxx_tasklet对象,xxx_do_tasklet是一个函数。
/*中断处理低半部*/
void xxx_do_taskle(unsigned long)
{
……
}
/*中断处理顶半部*/
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs* reg)
{
……
tasklet_schedule(&xxx_tasklet);
}
12、Workqueue工作队列
12.1创建新的工作队列
struct workqueue_struct* create_workqueue(const char* name); //一个工作队列可对应多个“内核线程”
struct workqueue_struct* create_singlethread_workqueue(const char* name);//一个工作队列对应单个线程。
12.2 向工作队列提交任务,首先填充一个work_struct结构(work_stuct是干什么用的)。
DECLARE_WORK(name, void(*function)(void*), void *data);
INIT_WORK(struct work_struct*
work, void(* function)(void *), void* data); //要填充work的,并与低半部函数function关联起来。
PREPARE_WORK(struct work_struct*
work, void(* function)(void* ), void* data);//不会初始化用来将work_struct结构连接到工作队列的指针,一般适用于任务已经提交,只是修改了任务时使用PREPARE_WORK.
12.3、提交任务
int queue_work(struct
workqueue_struct* queue,struct work_sturct* work );
int queue_delayed_work(struct workqueue_struct* queue,struct work_struct *work,unsigned long delay);//实际的工作至少会在经过指定的jiffies(由delay指定)之后才会被执行
12.4、取消某个队列的入口项
int cancel_delayed_work(struct work_struct *work);
// 返回!=0:内核会确保不会初始化给定入口项的执行。
// 返回==0:则说明该入口项已经在其他的处理器上运行(工作队列可用于多处理器,而tasklet只用于单处理器),因此在cancel_delayed_work返回后可能人在运行,怎么办?使用下面那个函数
需要强制刷新工作队列:void flush_workqueue(struct workqueue_struct* queue);
12.5、销毁工作队列
void destroy_workqueue(struct workqueue_struct *queue);
上面的工作队列会比较繁杂。(下面有简单的)
13、共享工作队列
13.1在许多情况下,驱动不需要有自己的工作队列,只是偶然地向工作队列添加任务。
13.2使用内核提供的共享的默认工作队列。
13.3不应该长期独占该队列,即不能长时间休眠。
13.4我们的任务可能需要更长的时间延时才能获得处理器时间。
14、使用共享队列
14.1初始化
INIT_WORK(struct work_stuct* work,void (*function)(void),void *data);//自定义一个任务结构体对象。也是要填充work的,并与低半部函数function关联起来。
14.2 调度
int schedule_work(struct work_struct *work);//立即调用
int schedule_delayed_work(struct work_struct *work,unsigned long delay);//延时调用
14.3取消共享工作队列的一个入口项(即一个工作任务work)
int cancel_delay_work(struct work_sruct *work);
14.4刷新共享工作队列
void flush_scheduled_work(void);
14.5共享队列的实例:
Work queue的使用模板
/*定义工作队列和关联函数*/
struct work_struct
xxx_wq;//要使用共享工作队列,首先要定义一个工作任务结构对象。
void xxx_do_work(unsigned long);//低半部函数的声明
/*中断处理低半部*/
void
xxx_do_work(unsigned long)
{
…
}
/*中断处理顶半部*/
irqreturn_t
xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
…
shedule_work(&xxx_wq);//调用shedule_work()函数实现顶半部跳转到底半部函数。
…
}
/*设备驱动模块加载函数*/
int__init xxx_init(void)
{
……
/*申请中断,当然申请的顶半部的函数*/
result=request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL);//顶半部函数
/*初始化工作队列*/
INIT_WORK(&xxx_wq,(void (*)(void *))
xxx_do_work,NULL);//任务结构,低半部函数
……
}
15、中断共享
15.1 Linux中断共享:多个设备使用同一个中断线号,同一个中断设备线号的所有处理程序链接成一个链表。
15.2 共享中断的多个设备在申请中断时都应使用SA_SHIRQ标志。
15.3、设备结构指针可以作为request_irq(…,void *dev_id)的最好一个参数dev_id传入,dev_id这个参数必须是唯一的,用来标志一个唯一的设备。
15.4、在中断到来时,对应链表的所有共享该中断的中断处理程序都被执行,他们会检查dev_id参数信息,并根据硬件中断寄存器中的信息判断是否本设备的中断,如果不是,应迅速返回,如果是,则处理完成,如果链表中没有一个是,则说明出现错误。
15.5、中断共享模板
/*设备驱动模块加载函数*/
int xxx_init(void)
{
……
/*申请共享中断*/
result=request_irq(sh_irq,xxx_interrupt,SA_SHIRQ,”xxx”,xxx_dev);
……
}
/*设备驱动模块卸载函数*/
void __exit xxx_exit(void)
{
…
/*释放中断*/
free_irq(xxx_irq,xxx_dev);
…
}
/*中断处理顶部*/
irqreturn_t
xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
…
int status=read_int_status();//获取中断源
if(!is_myint(dev_int,status))//判断是否本设备中断
{
return IRQ_NONE;//通知内核该中断不需要自己处理
}
…
return IRQ_HANDLED; //通知内核处理该中断
}
声明:本文非原创,整理自申嵌
相关文章推荐
- Linux内核提供了三种不同形式的中断底半部实现机制:软中断、tasklet和工作队列。
- [笔记分享] [中断] 中断申请释放以及上下半部
- 中断处理的tasklet(小任务)机制和workqueue(工作队列)机制
- linux驱动开发--中断:tasklet实现中断底半部
- linux 触摸屏驱动中断下半部实现-工作队列
- 中断处理的tasklet(小任务)机制和workqueue(工作队列)机制
- 中断下半部-工作队列(比较tasklet和工作队列,推荐)
- linux驱动开发之输入子系统编程(一)使用工作队列实现中断下半部
- 详解中断下半部tasklet 和workqueue(基于S5PV210的按键中断)
- Linux2.6内核--中断下半部实现方法 工作队列
- 中断上半部和下半部之低半部实现方法-softirq tasklet workqueue
- linux中断下半部实现机制之tasklet
- 中断处理的tasklet(小任务)机制和workqueue(工作队列)机制
- 底半部机制分析:软中断,tasklet,工作队列
- 中断底半部&顶半部tasklet 与 workqueue
- 中断处理的tasklet(小任务)机制和workqueue(工作队列)机制
- Linux2.6内核--中断下半部实现方法 工作队列
- 《Linux设备驱动开发详解》-- Linux中断处理底半部机制(tasklet、工作队列和软中断)
- linux驱动开发--中断:工作者队列实现中断底半部
- 详解中断下半部tasklet 和workqueue(基于S5PV210的按键中断)