Linux中断

一.中断的分类

中断,cpu执行过程中,出现某些突发事件紧急处理,CPU必须先暂停执行当前的程序,转而执行相应突发时间程序代码,处理完毕后,CPU再返回原来程序位置继续执行。这就是中断现象及中断基本处理原则(任务调度也是中断的一种现象)。

按照中断的来源来划分中断源,中断可分成内部中断(如SWI)和外部中断(如按键)两种。

按照是否可屏蔽,中断可以分为可屏蔽中断(如IRQ)和不可屏蔽中断(如SWI)两种。

按照中断入口跳转方式不同,中断可以分为向量中断和非向量中断。向量中断由硬件提供中断服务入口地址,采用向量中断的cpu为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行,不同中断号的中断有不同的入口地址。非向量中断由软件提供中断服务程序入口地址,采用非向量中断的多个中断共享一个入口地址,进入该入口地址后再通过软件判断中断标志来识别具体是哪个中断。


由于中断服务程序的执行不存在于进程上下文,因此,要求中断服务程序的时间尽可能短。因此,Linux在中断处理中引入了顶半部和底半部分离的机制。顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志,然后进行登记中断的工作。登记中断意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样顶半部的执行速度就会很快,可以服务更多的中断请求。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部与顶半部的最大不同,因为顶半部往往被设计成不可中断底半部相对来说不是非常紧急,相对顶半部比较耗时,不在硬件中断服务程序中执行,因此,如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。定时器在硬件上也依赖中断来实现。

二.中断函数

使用中断的设备需要申请和释放中断,分别使用内核提供的request_irq()和free_irq()函数。

1.申请和释放中断

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)

irq :要申请的硬件中断号

handler:向系统登记的中断处理函数(顶半部),是一个回调函数,中断发生时,系统调用这个函数将参数dev_id传递给它。

iflags:中断处理的属性,可以指定中断的触发方式以及处理方式。触发方式:IRQF_TRIGGER_RISING、IRQF_TRIGGER_FALLING、IRQF_TRIGGER_HIGH、IRQF_TRIGGER_LOW等。处理方式:IRQF_DISABLED,表明中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断;IRQF_SHARED,表示多个设备共享中断。

dev_id:在中断共享时会用到,一般设置为这个设备的设备结构体或NULL。

返回值:0表示成功,-EINVAL表示中断号无效或处理函数指针为NULL,-EBUSY表示中断已经被占用且不能共享。

顶半部handler的类型irq_handler_t定义为:

typedef irqturn_t (*irq_handler_t)(int, void *);

typedef int irqreturn_t;


void free_irq(unsigned int irq, void *dev_id);

参数同上。

2.使能和屏蔽中断

void enable_irq(int irq);

void disable_irq(int irq);

void disable_irq_nosync(int irq);

disable_irq()和disable_irq_nosync()的区别在于前者等待目前的中断处理完成,后者立即返回。由于disable_irq()会等待指定的中断被处理完,因此在n号中断的顶半部调用disable_irq(n),会引起系统死锁,这种情况下只能调用disable_irq_nosync(n)。


local开头的函数作用于屏蔽本cpu的所有中断

#define local_irq_save(flags)  ...

void local_irq_disable(void);

#define local_irq_restore(flags) ...

void local_irq_enable(void);


3.中断共享

多个设备共享一根中断线的情况在实际的硬件系统的广泛存在。

1)共享中断的多个设备在申请中断时,都应该使用IRQF_SHARED标志,而且一个设备以IRQF_SHARED申请某个中断成功的前提是该中断未被申请或者该中断虽然申请了但之前申请该中断的所有设备都以IRQF_SHARED标志申请的。

2)中断到来时会遍历执行共享此中断的所有中断处理程序,直到某一函数返回IRQ_HANDLED。在中断处理程序顶半部中,应迅速根据硬件寄存器中的信息比照传入的dev_id参数判断是否是本设备的中断,若不是,应迅速返回IRQ_NONE。

示例代码:

/*中断处理顶半部*/

irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)

{

...

int status = read_int_status(); /*获知中断源*/

if(!is_myint(dev_id,status))  /*判断是否本设备中断*/

return IRQ_NONE;    /*不是本设备中断,立即返回*/

/*是本设备中断,进行处理*/

...

return IRQ_HANDLED;   /*返回IRQ_HANDLED表明中断已经被处理*/

}

/*设备驱动模块加载函数*/

int xxx_init(void)

{

...

/*申请共享中断*/

result = request_irq(sh_irq, xxx_interrupt, IRQF_SHARED, "xxx", xxx_dev);

...

}

/*设备驱动模块卸载函数*/

void xxx_exit(void)

{

...

/*释放中断*/

free_irq(xxx_irq, xxx_interrput);

...

}

三.底半部机制

1.tasklet

tasklet的使用比较简单,我们只要定义tasklet及其处理函数并将两者关联,例如:

void my_tasklet_func(unsigned long); /*定义一个处理函数*/

DECLARE_TASKLET(my_tasklet,my_tasklet_func,data); /*定义一个tasklet结构my_tasklet并将其与my_tasklet_func(data)函数相关联*/

需要调度tasklet的时候引入一个tasklet_schedule()函数就能在适当的时候进行调度运行:

tasklet_schedule(&my_tasklet); /*通常在顶半部程序中使用,激活底半部中断*/


tasklet运行于中断上下文,处理函数(底半部中断)不能有休眠代码。

2.工作队列

工作队列的使用方法和tasklet类似,不同之处在于工作队列需要进行初始化。

struct work_struct my_wq;   /*定义一个工作队列*/

void my_wq_func(unsigned long); /*定义一个处理函数*/

INIT_WORK(&my_wq,(void(*)(void *))my_wq_func,NULL); /*初始化工作队列,并将工作队列与处理函数绑定*/

schedule_work(&my_wq); /*调度工作队列执行,通常放在顶半部中断程序中*/


工作队列运行于进程上下文,处理函数(底半部中断)可以有睡眠代码。

3.软中断

该机制与SMP(多cpu)紧密联系,谁触发谁执行。

结构体softirq_action表征一个软中断,结构体中包含了软中断处理函数指针和传递给这个函数的参数。

open_softirq() 注册软中断对应的处理函数

raise_softirq() 触发一个软中断


软中断运行于中断上下文,处理函数中不能包含睡眠的代码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值