深入浅出:Linux设备驱动之中断与定时器

“我叮咛你的 你说 不会遗忘 你告诉我的 我也全部珍藏 对于我们来说 记忆是飘不落的日子 永远不会发黄 相聚的时候 总是很短 期待的时候 总是很长 岁月的溪水边 捡拾起多少闪亮的诗行 如果你要想念我 就望一望天上那 闪烁的繁星 有我寻觅你的 目光” 谢谢你,曾经来过~
中断与定时器是我们再熟悉不过的问题了,我们在进行裸机开发学习的 时候,这几乎就是重难点,也是每个程序必要的模块信息,那么在Linux中,我们又怎么实现延时、计数,和中断呢?

一、中断

1.概述

所谓中断是指cpu在执行程序的过程中,出现了某些突发事件急待处理,cpu必需暂停执行当前执行的程序,转去处理突发事件,处理完之后cpu又返回原程序位置并继续执行,根据中断来源,中断分为内部中断和外部中断,软中断指令等属于内部中断,中断还可以分为可屏蔽中断和不可以屏蔽中断。Linux 的中断处理分为顶半部和底半部,顶半部完成尽可能少得的比较紧急的功能,往往只是简单的完成“登记中断”的工作,就是将底半部处理程序挂到该设备的底半部处理队列中去,中断处理机制如下图:

深入浅出:Linux设备驱动之中断与定时器_第1张图片

2、中断编程

2.1 申请和释放中断

(1) 申请irq

irq 是要申请的中断号,handler是向系统登记的中断处理函数,irq_flags是中断处理的属性,可以指定中断的触发方式机处理方式,在处理方式方面,IRQF_DISABLED,表明中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,IRQF_SHARED,表示多个设备共享中断(中断处理程序)。dev_id 在中断共享时会用到,一般设置为这个设备的结构体或者NULL.

(2) 释放irq

 

2.2、使能屏蔽中断

(1) 屏蔽(3个)

void disable_irq (int irq);

void disable_irq_nosync (ing irq);//立即返回

void enable_irq (int irq);

void disable_irq_nosync(int irq)与void disable_irq(int irg)的区别是前者立即返回,后者等待目前中断处理完。

(2) 屏蔽所有中断

#define local_irq_save (flags)//屏蔽本cpu所有

void local_irq_disable (void) //屏蔽本cpu所有中断

前者会保留中断状态保存在flags中(flags为unsigned long类型)。

(3) 恢复中断

#define local_irq_restore (flags)

void local_irq_enable (void);

以local开头的方法作用范围是本cpu内。

2.3 底半部机制–实现机制主要有tasklet, 工作队列和软中断

(1) tasklet

(2)工作队列

(3) 软中断(与通常说的软中断(软件指令引发的中断),比如arm的swi是完全不同的概念)

在linux内核中,用softirq_action结构体表征一个软中断,这个结构体包含软中断处理函数指针和传递给函数的参数。使用open_softirq()函数可以注册软中断对应的处理函数,而raise_softirq()函数可以触发一个软中断。软中断和tasklet 运行与软中断上下文,仍属于原子上下文的一种,而工作队列则运行与进程上下文。因此,软中断和tasklet处理函数中不能睡眠,而工作队列处理函数中允许睡眠。local_bh_disable() 和 local_bh_enable()是内核中用于禁止和使能软中断和tasklet底半部机制的函数。

2.4 中断共享

多个设备共享一根中断线的情况在硬件系统中广泛存在,共享中断的多个设备在申请中断时,都应该使用IRQF_SHARED标志,而且一个设备以IRQF_SHARED标志申请中断成功的前提是该中断未被申请或该中断虽然被申请了,但它之前申请该中断的设备都以IRQF_SHARED标志申请中断,尽管内核模块可以访问全局地址都可以作为request_irq(…,void *dev_id)的最后一个参数,但是社结构体被指针显然是可传入的最佳参数.

在中端到来时,会遍历共享此中断的所有中断处理程序,在中断处理程序顶半部中,应该根据硬件寄存器中的信息比照传入的dev_id参数判断是不是本设备的中断

共享中断模块

二、定时器/时钟

1、概述

软件意义上的定时器最终依赖硬件定时器来实现,内核在时钟中断发生后检测个定时器释放到期,到期后的定时器处理函数将作为软中断底半部执行。驱动编程中,可以利用一组函数和数据结构来完成定时器触发工作或者某些周期性任务。

(1) 一个timer_list 结构体的实例对应一个定时器,其定义如下:

如定义一个名为my_timer 的定时器:

(2) 初始化定时器

(3) 增加定时器

(4) 删除定时器

(5) 修改定时器的expire

(6) 对于周期性的任务,linux内核还提供了一种delayed_work机制来完成,本质上用工作队列和定时器实现。

6.1,内核延时

linux内核中提供了如下3个函数分别进行纳秒,微妙和毫秒延时

上述延时实现的原理实质上是忙等待,毫秒延时比较cpu耗资源,对于毫秒级以上时延,内核提供了如下函数

上述函数将使得调用它的进程,睡眠参数指定的时间,unsigned long msleep_interruptible()可以被信号打断,另两个不行

6.2、睡着延迟

睡着延迟在等待的时间到来之间进程处于睡眠状态,schedule_timeout()可以使当前任务睡眠指定的jiffies之后重新被调度,msleep()和msleep_interruptible()就包含了schedule_timeout()实质上schedule_timeout()的实现原理是向系统添加一个定时器,在定时器处理函数中唤醒参数对应的进程,其中结合了sleep_on()和__set_current_state(TASK_INTERRUPTIBLE)等函数。

 

2、内核定时器使用模板

HZ表示延时1s

3、实例–秒字符设备second_drv.c ,它在被打开时将初始化的定时器加到内核定时器链表中,每秒输出一次当前的jiffes,代码如下:

在second的open()函数中,将启动定时器,此后每秒会再次运行定时器处理函数,且在release()函数中删除,编译驱动,加载并创建“/dev/second”设备文件节点之后,用以下程序打开,second_test会不断读取来自“/dev/second”设备文件以来经历的秒数。

运行second_test后,不断输出jiffes的值,如下

而应用程序将不断输出来自打开的“/dev/second”如下:

三、总结

Linux中断处理分为两个半部,上述都讲得很清楚了,这里强调以下,为了充分利用CPU资源,在对延时使用不是很精确的情况下,睡眠等待值得推荐。对于上述的几个例子,需要大家自己在Linux的操作中敲出来,并且编译,看输出的结果才能完全理解~

你可能感兴趣的:(linux,驱动)