linux设备驱动笔记--中断

中断分类

1)根据中断来源,内部中断和外部中断,内部中断的中断源来自CPU内部(软件中断指令、溢出、除法错误等,例如,操作系统从用户态切换到内核态需借助CPU内部的软件中断),外部中断的中断源来自CPU外部,由外设提出请求。
2)根据中断是否可以屏蔽,中断可分为可屏蔽中断与不可屏蔽中断(NMI),可屏蔽中断可以通过设置中断控制器寄存器等方法被屏蔽,屏蔽后,该中断不再得到响应,而不可屏蔽中断不能被屏蔽。
3)根据中断入口跳转方法,中断可分为向量中断和非向量中断。采用向量中断的CPU通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行。不同中断号的中断有不同的入口地址。非向量中断的多个中断共享一个入口地址,进入该入口地址后,再通过软件判断中断标志来识别具体是哪个中断。也就是说,向量中断由硬件提供中断服务程序入口地址,非向量中断由软件提供中断服务程序入口地址。

linux中断处理程序架构

Linux将中断处理程序分解为两个半部:顶半部(Top Half)和底半部(Bottom Half)。
顶半部用于完成尽量少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态,并在清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,从而可以服务更多的中断请求。
底半部,需用它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同,因为顶半部往往被设计成不可中断。底半部相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。

linux中断编程

申请中断

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev);

irq是要申请的硬件中断号。handler是向系统登记的中断处理函数(顶半部),是一个回调函数,中断发生时,系统调用这个函数,dev参数将被传递给它。rqflags是中断处理的属性,可以指定中断的触发方式以及处理方式。
request_irq()返回0表示成功,返回-EINVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。

int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id);

devm_开头的API申请的是内核“managed”的资源,一般不需要在出错处理和remove()接口里再显式的释放。

释放中断

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

参数与申请中断函数一样。

中断底半部处理机制

tasklet

方法一

struct tasklet_struct		//定义tasklet结构体
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);	//初始化tasklet结构体

方法二

void my_tasklet_func(unsigned long); /* 定义一个处理函数 */
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);	//一次性初始化tasklet

调度tasklet

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

tasklet_schedule(&my_tasklet);

tasklet使用模板

/* 定义 tasklet 和底半部函数并将它们关联 */
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
/* 中断处理底半部 */
void xxx_do_tasklet(unsigned long)
{
	...
}
/* 中断处理顶半部 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
	...
 tasklet_schedule(&xxx_tasklet);
 ...
}
/* 设备驱动模块加载函数 */
int __init xxx_init(void)
{
	...
	/* 申请中断 */
	result = request_irq(xxx_irq, xxx_interrupt, 0, "xxx", NULL); ...
	return IRQ_HANDLED;
}
/* 设备驱动模块卸载函数 */
void __exit xxx_exit(void)
{
	...
	/* 释放中断 */
	free_irq(xxx_irq, xxx_interrupt);
}

工作队列

工作队列的使用方法和tasklet非常相似,tasklet是基于软中断实现的,因此也运行于软中断上下文,但是工作队列的执行上下文是内核线程,因此可以调度和睡眠。

struct work_struct my_wq; /* 定义一个工作队列 */
void my_wq_func(struct work_struct *work); /* 定义一个处理函数 */

通过INIT_WORK()可以初始化这个工作队列并将工作队列与处理函数绑定:

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

与tasklet_schedule()对应的用于调度工作队列执行的函数为schedule_work()

schedule_work(&my_wq); /* 调度工作队列执行 */

使用模板与工作队列类似。

软中断

线程irq

中断共享

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值