linux内核中断处理程序.docx
《linux内核中断处理程序.docx》由会员分享,可在线阅读,更多相关《linux内核中断处理程序.docx(6页珍藏版)》请在冰豆网上搜索。
![linux内核中断处理程序.docx](https://file1.bdocx.com/fileroot1/2023-1/21/7e226d0a-1db9-43e7-be2d-1ec3cf5c868f/7e226d0a-1db9-43e7-be2d-1ec3cf5c868f1.gif)
linux内核中断处理程序
linux内核--中断处理程序
:
CPU通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行非向量中断:
多个中断共享一个入口地址。
进入该入口地址后再通过软件判断中断标志来识别具体哪个是中断也就是说向量中断由软件提供中断服务程序入口地址,非向量中断由软件提供中断入口地址/*典型的非向量中断首先会判断中断源,然后调用不同中断源的中断处理程序*/irq_handler(){...intint_src=read_int_status();/*读硬件的中断相关寄存器*/switch(int_src){//判断中断标志caseDEV_A:
dev_a_handler();break;caseDEV_B:
dev_b_handler();break;...default:
break;}...}定时器中断原理:
定时器在硬件上也以来中断,PIT(可编程间隔定时器)接收一个时钟输入,当时钟脉冲到来时,将目前计数值增1并与已经设置的计数值比较,若相等,证明计数周期满,产生定时器中断,并复位计数值。
如下图所示:
Linux中断处理程序架构:
Linux将中断分为:
顶半部(tophalf)和底半部(bottomhalf)顶半部:
完成尽可能少的比较紧急的功能,它往往只是简单的读取寄存器中的中断状态并清除中断标志后就进行“登记中断”(也就是将底半部处理程序挂在到设备的底半部执行队列中)的工作特点:
响应速度快底半部:
中断处理的大部分工作都在底半部,它几乎做了中断处理程序的所有事情。
特点:
处理相对来说不是非常紧急的事件小知识:
Linux中查看/proc/interrupts文件可以获得系统中断的统计信息。
如下图所示:
第一列是中断号第二列是向CPU产生该中断的次数介绍完相关基础概念后,让我们一起来探讨一下Linux中断编程Linux中断编程:
内核维护了一个中断信号线的注册表,该注册表类似于I/O端口的注册表。
驱动模块在使用中断前要先请求一个中断通道(或者中断请求IRQ),然后在使用后释放该通道。
在头文件<linux/interrupt.h>中申明了申请和释放IRQ的接口。
1.申请和释放中断申请中断:
intrequest_irq(unsignedintirq,irq_handler_thandler,unsignedlongirqflags,constchar*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定义为typedefirqreturn_t(*irq_handler_t)(int,void*);typedefintirqreturn_t;2.释放IRQ有请求当然就有释放了voidfree_irq(unsignedintirq,void*dev_id);参数定义与request_irq类似3.使能和屏蔽中断voiddisable_irq(intirq);//等待目前中断处理完成(最好别在顶板部使用,你懂得)voiddisable_irq_nosync(intirq);//立即返回voidenable_irq(intirq);//4.屏蔽本CPU内所有中断:
#definelocal_irq_save(flags)...//禁止中断并保存状态voidlocal_irq_disable(void);//禁止中断,不保存状态下面来分别介绍一下顶半部和底半部的实现机制底半部机制:
简介:
底半部机制主要有tasklet、工作队列、软中断1.底半部实现方法之一tasklet
(1)我们需要定义tasklet机器处理器并将两者关联例如:
voidmy_tasklet_func(unsignedlong);/*定义一个处理函数*/DECLARE_TASKLET(my_tasklet,my_tasklet_func,data);/*上述代码定义了名为my_tasklet的tasklet并将其与my_tasklet_func()函数绑定,传入的参数为data*/
(2)调度tasklet_schedule(&my_tasklet);//使用此函数就能在适当的时候进行调度运行tasklet使用模板/*定义tasklet和底半部函数并关联*/
voidxxx_do_tasklet(unsignedlong);
DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);//将xxx_tasklet与xxx_do_tasklet绑定,传入参数0/*中断处理底半部*/
voidxxx_do_tasklet(unsignedlong)
{
...
}
/*中断处理顶半部*/
irqreturn_txxx_interrupt(intirq,void*dev_id)
{
...
tasklet_schedule(&xxx_tasklet);//调度底半部
...
}/*设备驱动模块加载函数*/
int__initxxx_init(void)
{
...
/*申请中断*//**/
result=request_irq(xxx_irq,xxx_interrupt,IRQF_DISABLED,"xxx",NULL);
...
returnIRQ_HANDLED;
}/*设备驱动模块卸载函数*/
void__exitxxx_exit(void)
{
...
/*释放中断*/
free_irq(xxx_irq,xxx_interrupt);
...
}2.底半部实现方法之二---工作队列使用方法和tasklet类似相关操作:
structwork_structmy_wq;/*定义一个工作队列*/voidmy_wq_func(unsignedlong);/*定义一个处理函数*/通过INIT_WORK()可以初始化这个工作队列并将工作队列与处理函数绑定INIT_WORK(&my_wq,(void(*)(void*))my_wq_func,NULL);/*初始化工作队列并将其与处理函数绑定*/schedule_work(&my_wq);/*调度工作队列执行*//*工作队列使用模板*//*定义工作队列和关联函数*/
structwork_structxxx_wq(unsignedlong);voidxxx_do_work(unsignedlong);/*中断处理底半部*/
voidxxx_do_work(unsignedlong)
{
...
}/*中断处理顶半部*/
irqreturn_txxx_interrupt(intirq,void*dev_id)
{
...
schedule_work(&my_wq);//调度底半部
...
returnIRQ_HANDLED;
}/*设备驱动模块加载函数*/
intxxx_init(void)
{
...
/*申请中断*/
result=request_irq(xxx_wq,xxx_interrupt,IRQF_DISABLED,"xxx",NULL);
...
/*初始化工作队列*/
INIT_WORK(&my_wq,(void(*)(void*))xxx_do_work,NULL);
}
/*设备驱动模块卸载函数*/
voidxxx_exit(void)
{
...
/*释放中断*/
free_irq(xxx_irq,xxx_interrupt);
...
}
中断共享中断共享是指多个设备共享一根中断线的情况中断共享的使用方法:
(1).在申请中断时,使用IRQF_SHARED标识
(2).在中断到来时,会遍历共享此中断的所有中断处理程序,直到某一个函数返回IRQ_HANDLED,在中断处理程序顶半部中,应迅速根据硬件寄存器中的信息参照dev_id参数判断是否为本设备的中断,若不是立即返回IR1_NONE/*共享中断编程模板*/irqreturn_txxx_interrupt(intirq,void*dev_id,structpt_regs*regs){...intstatus=read_int_status();/*获知中断源*/if(!
is_myint(dev_id,status))/*判断是否为本设备中断*/returnIRQ_NONE;/*不是本设备中断,立即返回*//*是本设备中断,进行处理*/...returnIRQ_HANDLED;/*返回IRQ_HANDLER表明中断已经被处理*/}/*设备模块加载函数*/intxxx_init(void){.../*申请共享中断*/result=request_irq(sh_irq,xxx_interrupt,IRQF_SHARE,"xxx",xxx_dev);...}/*设备驱动模块卸载函数*/voidxxx_exit(){.../*释放中断*/free_irq(xxx_irq,xxx_interrupt);...}内核定时器内核定时器编程:
简介:
软件意义上的定时器最终是依赖于硬件定时器实现的,内核在时钟中断发生后检测各定时器是否到期,到期后定时器处理函数作为软中断在底半部执行。
Linux内核定时器操作:
1.timer_list结构体每一个timer_list对应一个定时器structtimer_list{structlist_headentry;/*定时器列表*/unsignedlongexpires;/*定时器到期时间*/void(*function)(unsignedlong);/*定时器处理函数*/unsignedlongdata;/*作为参数被传递给定时器处理函数*/structtimer_base_s*base;...};当定时器满的时候,定时器处理函数将被执行2.初始化定时器voidinit_timer(structtimer_list*timer);//初始化timer_list的entry的next为NULL,并给base指针赋值。
TIMER_INITIALIZER(_function,_expires,_data);//此宏用来//赋值定时器结构体的function、expires、data和base成员#defineTIMER_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.增加定时器voidadd_timer(structtimer_list*timer);//注册内核定时器,也就是将定时器加入到内核动态定时器链表当中4.删除定时器del_timer(structtimer_list*timer);del_timer_sync()//在删除一个定时器时等待删除操作被处理完(不能用于中断上下文中)5.修改定时器expiresintmod_timer(structtimer_list*timer,unsignedlongexpires);//修改定时器的到期时间/*内核定时器使用模板*//*xxx设备结构体*/structxxx_dev{structcdevcdev;...timer_listxxx_timer;/*设备要使用的定时器*/};/*xxx驱动中的某函数*/xxx_funcl(...){structxxx_dev*dev=filp->private_data;.../*初始化定时器*/init_timer(&dev->xxx_timer);dev->xxx_timer.function=&xxx_do_timer;dev->xxx_timer.data=(unsignedlong)dev;/*设备结构体指针作为定时器处理函数参数*/dev->xxx_timer.expires=jiffes+delays;/*添加(注册)定时器*/add_timer(&dev->xxx_timer);...}/*xxx驱动中的某函数*/xxx_func2(...){.../*删除定时器*/del_timer(&dev->xxx_timer);...}/*定时器处理函数*/staticvoidxxx_do_timer(unsignedlongarg){structxxx_device*dev=(structxxx_device*)(arg);.../*调度定时器再执行*/dev->xxx_timer.expires=jiffes+delay;add_timer(&dev->xxx_timer);...}//定时器到期时间往往是在jiffies的基础上添加一个时延,若为HZ则表示延迟一秒内核中的延迟工作:
简介:
对于这种周期性的工作,Linux提供了一套封装好的快捷机制,本质上利用工作队列和定时器实现这其中用到两个结构体:
(1)structdelayed_work{structwork_structwork;structtimer_listtimer;};
(2)structwork_struct{atomic_long_tdata;...}相关操作:
intschedule_delay_work(structdelayed_work*work,unsignedlongdelay);//当指定的delay到来时delay_work中的work成员的work_func_t类型成员func()会被执行work_func_t类型定义如下:
typedefvoid(*work_func_t)(structwork_struct*work);//delay参数的单位是jiffesmescs_to_jiffies(unsignedlongmesc);//将毫秒转化成jiffes单位intcancel_delayed_work(structdelayed_work*work);intcancel_delayed_work_sync(structdelayed_work*work);//等待直到删除(不能用于中断上下文)内核延迟:
短延迟:
Linux内核提供了如下三个函数分别进行纳秒、微妙和毫秒延迟:
voidndelay(unsignedlongnsecs);voidudelay(unsignedlongusecs);voidmdelay(unsignedlongmsecs);机制:
根据CPU频率进行一定次数的循环(忙等待)注意:
在Linux内核中最好不要使用毫秒级的延时,因为这样会无谓消耗CPU的资源对于毫秒以上的延时,Linux提供如下函数voidmsleep(unsignedintmillisecs);unsignedlongmsleep_interruptible(unsignedintmillisecs);//可以被打断voidssleep(unsignedintseconds);//上述函数使得调用它的进程睡眠指定的时间长延迟:
机制:
设置当前jiffies加上时间间隔的jiffies,直到未来的jiffies达到目标jiffires/*实例:
先延迟100个jiffies再延迟2s*/unsignedlongdelay=jiffies+100;while(time_before(jiffies,delay));/*再延迟2s*/unsignedlongdelay=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,unsignedlongtimeout);interruptible_sleep_on_timeout(wait_queue_head_t*q,unsignedlongtimeout);在基于powerpc的linux中,request_irq里填的是软件中断号,也就是/proc/interrupts里的编号很不幸的是他们不是一一对应,也没有线性规律做移植的时候对着dts和手册反复比较,有时会有拿不准的时候最好能够一目了然地流出来内核识别的软件中断号和硬件中断号的映射关系于是可以在arch/powerpc/kernel/irq.c的irq_create_of_mapping()里看到,软件中断号硬件中断号是在这里完成映射的在最后的returnvirq;前面加一句printk("!
-_-hard%lu---virtual%lu\n",hwirq,virq);就这么简单,启动时就能一目了然看到对应关系了,对驱动移植很有帮助!
-_-hard38---virtual38!
-_-hard14---virtual16!
-_-hard15---virtual17!
-_-hard37---virtual37!
-_-hard36---virtual36!
-_-hard35---virtual35!
-_-hard34---virtual34!
-_-hard33---virtual33!
-_-hard32---virtual32!
-_-hard19---virtual19!
-_-hard20---virtual20!
-_-hard9---virtual18!
-_-hard10---virtual21!
-_-hard18---virtual22!
-_-hard19---virtual19!
-_-hard16---virtual23------------------------------------------------------root@MPC8313erdb:
/#cat/proc/interruptsCPU016:
26IPICLeveli2c-mpc17:
0IPICLeveli2c-mpc18:
224IPICLevelserial22:
4IPICEdgeserial23:
341IPICLevelmpc83xx_spi35:
0IPICLevelenet_error36:
1783IPICLevelenet_rx37:
544IPICLevelenet_tx77:
1IPICLevelfsl-elbc