5-1 Linux内核中断机制(中断的申请和释放、中断低半部tasklet队列和workqueue队列、中断共享的实现)

本文深入讲解Linux内核中断机制,包括中断分类、处理流程、共享中断等核心概念,以及如何在设备驱动中实现中断处理例程。同时介绍了计时、延时操作函数,并探讨了中断处理程序的顶半部与低半部的设计思想。

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(工作队列):高延时,运行休眠,运行于上下文

10tasklet的使用

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()函数。

11tasklet使用模板。

/*定义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);
}

 

12Workqueue工作队列

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

}

 

声明:本文非原创,整理自申嵌
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gdliweibing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值