1、设备驱动的基本概念
在访问设备时,如果不管设备是否有数据都死等它的数据,那别的设备就得不到访问。
因此在硬件设计中引进了中断机制。
内核中对时钟的处理也采用中断方式,而内核软件定时器最终依赖于时钟中断。
中断处理和进程是CPU上两类完全独立的执行体。中断处理例程和其他代码并发运行
这就讲引起并发问题,对并发控制技术的透彻理解对处理中断来讲是非常重要。
设备中断的到来会打断内核中进程的正常调度和运行。为了在中断执行时间尽可能短
和中断处理需完成大量工作之间找个平衡点,Linux将中断程序分解为顶半部(top half)
和底半部(buttom half)。
top half : 往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”
(挂到底半部执行队列)的工作;
bottom half : 完成中断事件的绝大多数任务。
2、Linux中断编程
2.1申请和释放中断
使用中断的设备需要申请和释放对应中断(IRQ)。
1)、申请IRQ
申请IRQ,在硬件上体现为关联上了某条中断信号线:
int request_irq(unsigned int irq,
irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long irqflags, const char * devname, void *dev_id)
* request_irq - allocate an interrupt line
* @irq: Interrupt line to allocate
* @handler: Function to be called when the IRQ occurs
* @irqflags: Interrupt type flags
* @devname: An ascii name for the claiming device
* @dev_id: A cookie passed back to the handler function
Flags:
*
* SA_SHIRQ Interrupt is shared
* SA_INTERRUPT Disable local interrupts while processing
* SA_SAMPLE_RANDOM The interrupt can be used for entropy(如果设备以真正
随机的周期产生中断,就应该设置该标志位)
request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,
返回-EBUSY表示中断已经被占用且不能共享。
2)、释放IRQ
void free_irq(unsigned int irq, void *dev_id)
* free_irq - free an interrupt allocated with request_irq
* @irq: Interrupt line to free
* @dev_id: Device identity to free
2.2使能和屏蔽中断
屏蔽一个中断源,以下三个函数对系统内所有CPU都生效,其中(1)和(3)的区别在于
(3)立即返回,而(1)等待目前的中断处理完成。在拥有自旋锁的时候需要阻塞中断,但
应该注意尽量少禁用中断
(1) void disable_irq(unsigned int irq)
* disable_irq - disable an irq and wait for completion
* @irq: Interrupt to disable
(2) void enable_irq(unsigned int irq)
* enable_irq - enable handling of an irq
* @irq: Interrupt to enable
(3) void disable_irq_nosync(unsigned int irq)
* disable_irq_nosync - disable an irq without waiting
* @irq: Interrupt to disable
以上三个函数对系统内所有CPU都生效,对于本CPU内的中断屏蔽,
local_irq_save(flags)
local_irq_disable(void)
local_irq_restore(flags)
local_irq_enable(void)
3、底半部机制
底半部的实现机制主要有tasklet、工作队列、和软中断。其中软中断和
tasklet处理函数中不能休眠,而工作队列处理函数中允许休眠,中断处理程序
的一个典型任务是:如果中断通知进程所等待的事件已经发生,比如新的数
据到达,就会唤醒该设备上休眠的进程。
3.1tasklet
所有tasklet代码都必须是原子的。
/* Set up our tasklet if we're doing that. */
void xxx_do_tasklet(unsigned long); /*定义一个处理函数*/
/*定义一tasklet结构xxx_tasklet,与xxx_do_tasklet函数关联*/
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
调度指定的tasklet函数:
tasklet_schedule(&xxx_tasklet); /*调度一个tasklet运行*/
使用tasklet作为底半部处理中断的设备驱动程序模板:
//定义tasklet和底半部函数并关联
void xxx_do_tasklet(unsigned long); /*定义一个处理函数*/
/*定义一tasklet结构xxx_tasklet,与xxx_do_tasklet函数关联*/
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(short_irq, short_interrupt,
0x0, "xxx", NULL);
...
}
//设备驱动模块卸载函数
int __exit xxx_exit(void)
{
...
free_irq(xxx_irq, xxx_interrupt);
...
}
3.2工作队列
可以具有高的延迟,但允许休眠。
static struct work_struct xxx_wq; //定义一个工作队列
irqreturn_t xxx_wq_interrupt(int irq, void *dev_id) //定义一个处理函数
//初始化工作队列,并将工作队列与处理函数绑定
INIT_WORK(&xxx_wq, (void (*)(struct work_struct *)) xxx_do_tasklet);
schedule_work(&xxx_wq);
用工作队列处理中断底半部的设备驱动程序模板
//定义工作队列
static 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)
{
....
schedule_work(&xxx_work);
....
}
//设备驱动模块加载函数
int __init xxx_init(void)
{
...
申请中断
result = request_irq(short_irq, short_interrupt,
0x0, "xxx", NULL);
...
INIT_WORK(&xxx_wq, (void (*)(struct work_struct *)) xxx_do_tasklet);
...
}
//设备驱动模块卸载函数
int __exit xxx_exit(void)
{
...
free_irq(xxx_irq, xxx_interrupt);
...
}