一个设备的中断处理程序是它设备驱动程序的一部分--设备驱动程序是用于对设备进行管理的内核代码。中断处理程序与其他内核函数的真正区别在于,中断处理程序是被内核调用来响应中断的,而它们运行于我们称之为中断上下文的特殊上下文中。

中断与定时器:

中断的概念:指CPU在执行过程中,出现某些突发事件急待处理,CPU暂停执行当前程序,转去处理突发事件,处理完后CPU又返回原程序被中断的位置继续执行

中断的分类:内部中断和外部中断
内部中断:中断源来自CPU内部(软件中断指令、溢出、触发错误等)
外部中断:中断源来自CPU外部,由外设提出请求

屏蔽中断和不可屏蔽中断:
可屏蔽中断:可以通过屏蔽字被屏蔽,屏蔽后,该中断不再得到响应
不可屏蔽中断:不能被屏蔽

向量中断和非向量中断:

向量中断 :  CPU通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行

非向量中断:多个中断共享一个入口地址。进入该入口地址后再通过软件判断中断标志来识别具体哪个是中断
也就是说向量中断由软件提供中断服务程序入口地址,非向量中断由软件提供中断入口地址

/*典型的非向量中断首先会判断中断源,然后调用不同中断源的中断处理程序*/
irq_handler()
{
...
int int_src = read_int_status();/*读硬件的中断相关寄存器*/
switch(int_src){//判断中断标志
case DEV_A:
dev_a_handler();
break;
case DEV_B:
dev_b_handler();
break;
...
default:
break;
}
...
}

定时器中断原理:
定时器在硬件上也以来中断,PIT(可编程间隔定时器)接收一个时钟输入,
当时钟脉冲到来时,将目前计数值增1并与已经设置的计数值比较,若相等,证明计数周期满,产生定时器中断,并复位计数值。

如下图所示:

Linux中断处理程序架构:

Linux将中断分为:顶半部(top half)和底半部(bottom half)

顶半部:      完成尽可能少的比较紧急的功能,它往往只是简单的读取寄存器中的中断状态并清除中断标志后就进行“登记中断”(也就是将底半部处理程序挂在到设备的底半部执行队列中)的工作
特点:响应速度快

底半部:      中断处理的大部分工作都在底半部,它几乎做了中断处理程序的所有事情。
特点:处理相对来说不是非常紧急的事件

小知识:Linux中查看/proc/interrupts文件可以获得系统中断的统计信息。

如下图所示:


第一列是中断号       第二列是向CPU产生该中断的次数

介绍完相关基础概念后,让我们一起来探讨一下Linux中断编程
Linux中断编程:
内核维护了一个中断信号线的注册表,该注册表类似于I/O端口的注册表。驱动模块在使用中断前
要先请求一个中断通道(或者中断请求IRQ),然后在使用后释放该通道。
在头文件<linux/interrupt.h>中申明了申请和释放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参数传递给它。
irqflags:           是中断处理的属性,可以指定中断的触发方式和处理方式:
触发方式:        IRQF_TRIGGER_RISING、IRQF_TRIGGER_FALLING、IRQF_TRIGGER_HIGH、IRQF_TRIGGER_LOW
处理方式:       IRQF_DISABLE表明中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断
IRQF_SHARED        表示多个设备共享中断
dev_id         在中断共享时会用到,一般设置为NULL

返回值:为0表示成功,返回-EINVAL表示中断号无效,返回-EBUSY表示中断已经被占用,且不能共享
顶半部的handler的类型irq_handler_t定义为
typedef irqreturn_t (*irq_handler_t)(int,void*);
typedef int irqreturn_t;

2.释放IRQ
有请求当然就有释放了
void free_irq(unsigned int irq,void *dev_id);
参数定义与request_irq类似

3.使能和屏蔽中断
void disable_irq(int irq);//等待目前中断处理完成(最好别在顶板部使用,你懂得)
void disable_irq_nosync(int irq);//立即返回
void enable_irq(int irq);//

4.屏蔽本CPU内所有中断:
#define local_irq_save(flags)...//禁止中断并保存状态
void local_irq_disable(void);//禁止中断,不保存状态

下面来分别介绍一下顶半部和底半部的实现机制

底半部机制:
简介:底半部机制主要有tasklet、工作队列、软中断

1.底半部实现方法之一tasklet

(1)我们需要定义tasklet机器处理器并将两者关联
例如:
void my_tasklet_func(unsigned long);/*定义一个处理函数*/

DECLARE_TASKLET(my_tasklet,my_tasklet_func,data);
/*上述代码定义了名为my_tasklet的tasklet并将其与my_tasklet_func()函数绑定,传入的参数为data*/

(2)调度
tasklet_schedule(&my_tasklet);
//使用此函数就能在适当的时候进行调度运行

tasklet使用模板

/*定义tasklet和底半部函数并关联*/void xxx_do_tasklet(unsignedlong);


DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);

//将xxx_tasklet与xxx_do_tasklet绑定,传入参数0/*中断处理底半部*/void xxx_do_tasklet(unsignedlong){...}

/*中断处理顶半部*/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,IRQF_DISABLED,"xxx",NULL);...return IRQ_HANDLED;}
/*设备驱动模块卸载函数*/void __exit xxx_exit(void){.../*释放中断*/
free_irq(xxx_irq,xxx_interrupt);...}


2.底半部实现方法之二---工作队列
使用方法和tasklet类似
相关操作:
struct work_struct my_wq;             /*定义一个工作队列*/
void my_wq_func(unsigned long);/*定义一个处理函数*/
通过INIT_WORK()可以初始化这个工作队列并将工作队列与处理函数绑定
INIT_WORK(&my_wq,(void (*)(void *))my_wq_func,NULL);
/*初始化工作队列并将其与处理函数绑定*/
schedule_work(&my_wq);/*调度工作队列执行*/

/*工作队列使用模板*/

/*定义工作队列和关联函数*/struct work_struct xxx_wq(unsignedlong);
void xxx_do_work(unsignedlong);
/*中断处理底半部*/void xxx_do_work(unsignedlong){...}

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

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

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

中断共享
中断共享是指多个设备共享一根中断线的情况

中断共享的使用方法:
(1).在申请中断时,使用IRQF_SHARED标识
(2).在中断到来时,会遍历共享此中断的所有中断处理程序,直到某一个函数返回
IRQ_HANDLED,在中断处理程序顶半部中,应迅速根据硬件寄存器中的信息参照dev_id参数
判断是否为本设备的中断,若不是立即返回IR1_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_HANDLER表明中断已经被处理*/
}

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

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

内核定时器
内核定时器编程:
简介:软件意义上的定时器最终是依赖于硬件定时器实现的,内核在时钟中断发生后检测各
定时器是否到期,到期后定时器处理函数作为软中断在底半部执行。

Linux内核定时器操作:
1.timer_list结构体
每一个timer_list对应一个定时器
struct timer_list{
struct list_head entry;/*定时器列表*/
unsigned long expires;/*定时器到期时间*/
void (*function)(unsigned long);/*定时器处理函数*/
unsigned long data;/*作为参数被传递给定时器处理函数*/
struct timer_base_s *base;
...
};
当定时器满的时候,定时器处理函数将被执行

2.初始化定时器
void init_timer(struct timer_list * timer);
//初始化timer_list的entry的next为NULL,并给base指针赋值。
TIMER_INITIALIZER(_function,_expires,_data);//此宏用来
//赋值定时器结构体的function、expires
、data和base成员
#define TIMER_INITIALIZER(function,_expires,_data){
.entry = {.prev = TIMER_ENTRY_STATIC},\
.function= (_function), \
.expires = (_expire), \
.data = (_data),  \
.base = &boot_tvec_bases,\
}

DEFINE_TIMER(_name,_function,_expires,_data)//定义一个定时器结构体变量
//并为此变量取名_name

//还有一个setup_timer()函数也可以用于定时器结构体的初始化,此函数大家自己去网上查吧

3.增加定时器
void add_timer(struct timer_list * timer);
//注册内核定时器,也就是将定时器加入到内核动态定时器链表当中

4.删除定时器
del_timer(struct timer_list *timer);
del_timer_sync()//在删除一个定时器时等待删除操作被处理完(不能用于中断上下文中)

5.修改定时器expires
int mod_timer(struct timer_list * timer,unsigned long expires);
//修改定时器的到期时间

/*内核定时器使用模板*/

/*xxx设备结构体*/
struct xxx_dev{
struct cdev cdev;
...
timer_list xxx_timer;/*设备要使用的定时器*/
};

/*xxx驱动中的某函数*/
xxx_funcl(...)
{
struct xxx_dev *dev = filp->private_data;
...
/*初始化定时器*/
init_timer(&dev->xxx_timer);
dev->xxx_timer.function = &xxx_do_timer;
dev->xxx_timer.data = (unsigned long)dev;
/*设备结构体指针作为定时器处理函数参数*/
   
dev->xxx_timer.expires = jiffes + delays;
/*添加(注册)定时器*/
add_timer(&dev->xxx_timer);
...
}

/*xxx驱动中的某函数*/
xxx_func2(...)
{
...
/*删除定时器*/
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 = jiffes + delay;
add_timer(&dev -> xxx_timer);
...
}

//定时器到期时间往往是在jiffies的基础上添加一个时延,若为HZ
则表示延迟一秒

内核中的延迟工作:
简介:对于这种周期性的工作,Linux提供了一套封装好的快捷机制,
本质上利用工作队列和定时器实现

这其中用到两个结构体:
(1)struct delayed_work{
struct work_struct work;
struct timer_list timer;
};

(2)struct work_struct{
atomic_long_t data;
...
}

相关操作:
int schedule_delay_work(struct delayed_work *work,unsigned long delay);
//当指定的delay到来时delay_work中的work成员的work_func_t类型成员func()会被执行
work_func_t类型定义如下:
typedef void (*work_func_t)(struct work_struct *work);
//delay参数的单位是jiffes

mescs_to_jiffies(unsigned long mesc);//将毫秒转化成jiffes单位

int cancel_delayed_work(struct delayed_work *work);
int cancel_delayed_work_sync(struct delayed_work *work);//等待直到删除(不能用于中断上下文)

内核延迟:
短延迟:
Linux内核提供了如下三个函数分别进行纳秒、微妙和毫秒延迟:
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);

机制:根据CPU频率进行一定次数的循环(忙等待)

注意:在Linux内核中最好不要使用毫秒级的延时,因为这样会无谓消耗CPU的资源
对于毫秒以上的延时,Linux提供如下函数
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);//可以被打断
void ssleep(unsigned int seconds);
//上述函数使得调用它的进程睡眠指定的时间

长延迟:
机制:设置当前jiffies加上时间间隔的jiffies,直到未来的jiffies达到目标jiffires

/*实例:先延迟100个jiffies再延迟2s*/
unsigned long delay = jiffies + 100;
while(time_before(jiffies,delay));

/*再延迟2s*/
unsigned long delay =  jiffies + 2*Hz;
while(time_before(jiffies,delay));//循环直到到达指定的时间

与timer_before()相对应的还有一个time_after

睡着延迟:
睡着延迟是比忙等待更好的一种方法
机制:在等待的时间到来之前进程处于睡眠状态,CPU资源被其他进程使用
实现函数有:
schedule_timeout()
schedule_timeout_uninterruptible()
其实在短延迟中的msleep() msleep_interruptible()
本质上都是依赖于此函数实现的

下面两个函数可以让当前进程加入到等待队列中,从而在等待队列上睡眠,当超时
发生时,进程被唤醒
sleep_on_timeout(wait_queue_head_t *q,unsigned long timeout);

interruptible_sleep_on_timeout(wait_queue_head_t *q,unsigned long timeout);


linux内核--中断处理程序的更多相关文章

  1. Linux 内核中断内幕

    转自:http://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/index.html#resources Linux 内核中断内幕 ...

  2. Linux内核中断和异常分析(中)

    在linux内核中,每一个能够发出中断请求的硬件设备控制器都有一条名为IRQ的输出线.所有现在存在的IRQ线都与一个名为可编程中断控制器的硬件电路的输入引脚相连,上次讲到单片机的时候,我就讲到了单片机 ...

  3. Linux内核中断顶半部和底半部的理解

    文章目录 中断上半部.下半部的概念 实现中断下半部的三种方法 软中断 软中断模版 tasklet tasklet函数模版 工作队列 工作队列函数模版 进程上下文和中断上下文 软中断和硬中断的区别 硬中 ...

  4. Linux内核中断和异常分析(上)

    中断,通常被定义为一个事件.打个比方,你烧热水,水沸腾了,这时候你要去关掉烧热水的电磁炉,然后再去办之前手中停不下来的事情.那么热水沸腾就是打断你正常工作的一个信号机制.当然,还有其它的情况,我们以后 ...

  5. linux内核中断之看门狗

    一:内核中断 linux内核中的看门狗中断跟之前的裸板的中断差不多,在编写驱动之前,需要线把内核自带的watch dog模块裁剪掉,要不然会出现错误:在Device Drivers /Watchdog ...

  6. Linux 内核中断内幕【转】

    转自:http://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/ 本文对中断系统进行了全面的分析与探讨,主要包括中断控制器.中断分类 ...

  7. 羽夏看Linux内核——中断与分页相关入门知识

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作.如想转载,请把我的转载信息附在文章后面,并 ...

  8. Linux内核中断学习

    1.内核中断概述 (1)在OS环境下编写中断处理函数与之前在裸机中编写中断处理函数的方式是不一样的,在Linux内核中提供了一套用来管理硬件中断资源的软件体系架构. (2)在操作系统中,中断号与gpi ...

  9. linux内核--中断和中断处理(一)

        让硬件在需要的时候再向内核发出信号.这就是中断机制,先讨论中断,进而讨论内核如何使用所谓的中断处理函数处理对应的中断. 一.中断   1)中断     中断使得硬件得以发出通知给处理器.例如, ...

随机推荐

  1. (Error) The type AESKeyGenerator is not accessible due to restriction on required library.

    error for 'Access restriction: The type AESKeyGenerator is not accessible due to restriction on requ ...

  2. linux command cp.

    Examples cp file1.txt newdir Copies the file1.txt in the current directory to the newdir subdirector ...

  3. 七.生成n位随机字符串

    --1.借助newid() go --创建视图(因为在函数中无法直接使用newid()) create view vnewid as select newid() N'MacoId'; go --创建 ...

  4. js 验证表单 js提交验证类

    附加:js验证radio是否选择 <script language="javascript">function checkform(obj){for(i=0;i< ...

  5. 4.HTTP入门

    什么是http协议查看http协议的工具http协议内容Http请求请求行http协议版本请求资源请求方式GET方式提交POST方式提交请求头3.3 实体内容3.4 HttpServletReques ...

  6. hdu1301 Jungle Roads (Prim)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1301 依旧Prim............不多说了 #include<iostream> ...

  7. MySQL存储过程带in和out参数

    MySQL存储过程带in和out参数 最简单的例子: [html] mysql> DELIMITER $$ mysql> USE test $$ Database changed mysq ...

  8. excel设置单元格不可编辑

    把允许编辑的单元格选定,右键-设置单元格格式-保护,把锁定前的对钩去掉.再点工具-保护工作表.这样就可以只让你刚才设定的单元格允许编辑,其他不允许.

  9. Linux下Openfire相关安装和配置

    记录下来,方便下次再用时从头查找资料 小京东ecshop中的通讯有用到openfire,Window下配置安装很简单,直接下载exe文件安装就行,而linux下要麻烦一点.安装后的配置下面会细说: 一 ...

  10. h.264加权预测

    帧间运动是基于视频亮度(luma)不发生改变的一个假设,而在视频序列中经常能遇到亮度变化的场景,比如淡入淡出.镜头光圈调整.整体或局部光源改变等,在这些场景中,简单帧间运动补偿的效果可想而知(实际编码 ...