您的位置:首页 > 运维架构 > Linux

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; //通知内核处理该中断

}

声明:本文非原创,整理自申嵌
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: