kernel驱动中断学习记录

学习书籍为《Linux设备驱动开发详解》

一、Linux将中断处理程序处理为:顶半部和底半部

顶半部用于完成尽量少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态,并在清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部中断执行的速度就会很快,从而可以服务更多的中断请求。顶半部中断往往被设备成不可中断,底半部中断相对来说并不是非常紧急的,而且相对比较耗时漫步在硬件中断服务程序中执行。

中断编程
申请中断
int request_irq(unsigned int irq, irq_handler_t handler, unsigned log flags, const chat *name, void *dev);
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接口里再显式的释放,有点类似于java的垃圾回收机制。
返回值0表示成功,返回-EINVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享

释放中断
void free_irq(unsigned int irq, void *dev_id);

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

屏蔽和恢复中断
屏蔽CPU内的所有中断
#define local_irq_save(flags) …
void local_irq_disable(void);
前者会将目前的中断状态保留在flags中,后者直接禁止中断而不是保存状态
恢复中断
#define local_irq_restore(flags) …
void local_irq_enable(void);
以上以local_开头的方法的作用范围是本CPU内

二、Linux实现底半部的机制主要有tasklet、工作队列、软中断和线程化irq

1、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);
...
}

2、工作队列的执行上下文是内核线程,因此可以调度和睡眠。
实现模板

/*定义工作队列和关联函数*/
struct work_struct xxx_wq;
void xxx_do_work(struct work_struct *work);

/*中断处理底半部*/
void xxx_do_work(struct work_struct *work)
{...}

/*中断处理顶半部*/
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
...
schedule_work(&xxx_wq);
...
return IRQ_HANDLED;
}

/*设备驱动模块加载函数*/
int __init xxx_init(void)
{
...
/*申请中断*/
result  = request_irq(xxx_irq, xxx_interrupt, 0, "xxx", NULL);
/*初始化工作队列*/
INIT_WORK(&xxx_wq, xxx_do_work);
...
}

/*设备驱动模块卸载函数*/
void xxx_exit(void)
{
...
/*释放中断*/
free_irq(xxx_irq, xxx_interrupt);
...
}

扩展:硬中断、软中断和信号的区别
硬中断是外部设备对CPU的中断,软中断是中断底半部的一种处理机制,而信号则是由内核对某个进程的中断。

三、中断共享

多个设备共享一根硬件中断线的情况在实际的硬件系统中广泛存在,使用方法如下
(1)共享中断的多个设备在申请中断时,都应该使用IRQF_SHARED标志,而且一个设备以IRQF_SHARED申请某中断成功的前提是该中断未被申请,或该中断虽然被申请了,但是之前该中断的所有设备也都以IRQF_SHARED标志申请该中断。
(2)尽管内核模块可访问的全局地址都可以作为request_irq(…, void *dev_id)的最后一个参数dev_id, 但是设备结构体指针显然是可传入的最佳参数。
(3)在中断到来时,会遍历执行共享此中断的所有中断处理程序,直到某一个函数返回IRQ_HANDLED。在中断处理程序顶半部中,应根据硬件寄存器中的信息比照传入的dev_id参数迅速地判断是否为本设备的中断,若不是,应迅速返回IRQ_NONE。
编程模板

/*中断处理顶半部*/
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
...
int status = read_int_status(); /*获知中断源*/
if(!is_myint(dev_id, status))  /*判断是否为本设备中断*/
  return IRQ_NONE;

/*是本设备中断,进行处理*/
...
return IRQ_HANDLED;
}

/*设备驱动模块加载函数*/
int xxx_init(void)
{
...
/*申请共享中断*/
result = request_irq(xxx_irq, xxx_interrupt, IRQF_SHARED, "xxx", xxx_dev);
...
}

/*设备驱动模块卸载函数*/
void  xxx_exit(void)
{
...
/*释放中断*/
free_irq(xxx_irq, xxx_interrupt);
...
}

四、内核定时器

软件定时器最终依赖硬件定时器的实现,内核在时钟中断发生后检测各定时器是否到期,到期后的定时器处理函数将作为软中断底半部执行。实质上,时钟中断处理程序会唤起TIMER_SOFTIRQ软中断,运行当前处理器上到期的所有定时器。

timer_list结构体
struct timer_list{
struct list _head entry;
unsigned long expires;  //定时器到期的时间jiffies
struct tvec_base *base;
void (*function)(unsigned long); //定时器期满后,将被执行
unsigned long data; //传入定时器的参数

int slack;
#ifdef CONFIG_TIMER_STATS
  int start_pid;
  void *start_site;
  char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
  struct lockdep_map locakdep_map;
#endif
};

函数说明
初始化定时器: void init_timer(struct timer_list *timer)
增加定时器: void add_timer(struct timer_list * timer)
删除定时器: int del_timer(struct timer_list * timer)
修改定时器的定时时间:int mod_timer(struct timer_list *timer, unsigned long expires)

内核定时器模板

struct xxx_dev{
struct cdev cdev;
...
timer_list xxx_timer;  //设备使用的定时器
}
/*功能函数*/
xxx_funcl()
{
struct xxx_dev *dev = filp->private_data;
...
/*初始化定时器*/
init_timer(&dev->xxx_timer);
dev->xx_timer.function = &xxx_do_timer;
dev-xxx_timer.data = (unsigned long )dev;
/*设备结构体指针作为定时器处理函数参数*/
dev->xxx_timer.expires = jiffies + delay;
/*添加(注册)定时器*/
add_timer(&dev->xxx_timer);
...
}

/*功能函数*/
xxx_fun2c()
{
...
/*删除定时器*/
del_timer(&dev->xxx_timer);
...
}

/*定时器处理函数*/
static void xxx_do_timer(unsigned long arg)
{
struct xxx_device *dev = (struct xxx_device *)(arg);
...
/*调度定时器再执行*/
dev->xxx_timer.expires = jiffies + delay;
add_timer(&dev->xxx_timer);
...
}

定时器的到期时间往往是在目前jiffies的基础上添加一个时延,若为Hz,则表示延迟1s

持续学习中。。。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值