您的位置:首页 > 产品设计 > UI/UE

中断上半部和下半部之低半部实现方法-softirq tasklet workqueue

2017-02-17 09:55 232 查看

Asmlinkage表示参数传送通过stack来传送而不是寄存器
中断种类:一种是由CPU外部产生的,另一种是由CPU本身在执行程序的过程中产生的
外部中断:就是通常所讲的中断INTERRUPT,对于执行中的软件来说,这种中断的发生完全是异步的,根本无法预测此类中断会在什么时候发生,因此,CPU或者软件对外部中断的响应完全是被动的。不过,软件可以通过关中断指令关闭对中断的响应,把它反映情况的途径掐断。这样就可以眼不见心不烦了(不可屏蔽款中断是不可以屏蔽的)
由软件产生的中断则不同,它是由专设的指令,如X86中的INT N,在程序中有意地产生的,所以是主动的,同步的。只要CPU执行一条INT指令,就知道在开始执行下一条指令之前一定要进入中断服务程序,这种主动的中断称为陷阱TRAP.
另外一种与中断相似的机制称为异常EXCEPTION,一般也是异步的,多半由于不小心犯了规才发生的。例如,当你在程序中发出一条除法指令DIV,而除数为0时,就会发生一次异常。这多半是因为不小,而不是故意的。所以也是被动的。当然,也不排除故意的可能性。
这样,一共就有三种类似的机制,既中断,陷阱以及异常。

Linux系统中中断相关初始化:LINUX内核在初始化阶段完成了对页式虚存管理的初始化后,便调用trap_init()
and init_IRQ()两个函数进行中断机制的初始化。trap_init()
中要是对一些系统保留的中断向量的初始化,而init_IRQ()由主要是用于外设的中断

Linux系统对于中断的处理框架:中断的请求生到CPU的响应,再到中断服务程序的调用与返回,沿着CPU所经过的路线走一遍。这样才能分析清楚中断响应和服务的总体的格局和安排。对于ARM系统调用函数指示流程如下:
产生中断时进入中断相量入口
entry-armv.S
/*
* Interrupt dispatcher
*/
->entry-armv.S(irq_handler)->entry-macro-multi.S(asm_do_IRQ)->arm/arm/kernel/asm_do_IRQ()
->Irq.c (arch\arm\kernel)generic_handle_irq->Irqdesc.c (kernel\irq)generic_handle_irq->
Irqdesc.h (include\linux)generic_handle_irq_desc
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}这个函数要去调用我们通过request_irq注册的中断函数。
Irq-eint.c (arch\arm\mach-exynos):
desc->handle_irq = handle_edge_irq;
Irq-eint.c (arch\arm\mach-exynos):
desc->handle_irq = handle_level_irq;
上面两个是handle_irq是CPU对于边沿触发和电平触发的的handle_irq定义。这个里面必定有我们自己注册的函数调用路径。
下面简要分析这两个函数的调用路径。
handle_edge_irq-》handle_irq_event-》handle_irq_event_percpu-》res
= action->handler(irq, action->dev_id);最终调用注册的回调函数。
handle_level_irq-》handle_irq_eventt-》handle_irq_event_percpu-》res
= action->handler(irq, action->dev_id);最终调用注册的回调函数。
由上面可知CPU产生的中断通过中断相量进入处理函数然后产生一个中断号(线,注册中断时用的号值)是一一对应的。注意这儿并不与中断线(号)与硬件中断号没有必须联系。硬件CPU上支持的中断号是物理的,而在进入IRQ后通过算法产生一个逻辑的中断线。我们最终用的是中断线。
对于三星4412开发板的中断线(requset_irq用到的)号码在linux/arch/arm/mach-exynos/include/mach/irqs-exynos4.h中定义了的。64+X
其中X就是这个函数注册中断用到的。这个X值是CPU平台确定下来了的,不能称随便更改,X值是由硬件中断进入中断相量后然后进入IRQ总入口,经过一些运算方式确定下来了的值。这个X值得来主要看.macro
get_irqnr_and_base, irqnr, irqstat, base, tmp这个宏在entry-macro.s中的。Irqnr这个就是这宏下面代码通过运算后得到的值。这个得到值与linux/arch/arm/mach-exynos/include/mach/irqs-exynos4.h中的中断线(号)是对对应的,只有对应才能正确的找到request_irq注册的中断函数。中断线(号相当于一个ID,也就是说一个识别码,struct
irq_desc irq_desc[NR_IRQS];数组就是这个函数注册时的存储表),struct irq_desc irq_desc[NR_IRQS];中的每一个数组元数支持一个链表,这样可以让多个中断共用一个中断号既共享。最后各个中断处理函数通过DEV_ID来分析是来至那个中断源。

LINUX中断低半部几中实现机制:softirq软件中断 tasklet(小任务)
workqueue工作队列
软中断本身是一种机制,同时也是一个框架,在这个框架里有BH机制,这是一种特殊的软件中断,也可以说是设计最保守的,但却是最简单,最安全的软件中断。
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
上面的软件中断框架的操作函数在中断返回时
系统调用返回时会检查是否有软中处理函数待执行,如果有则就执行它。否则返回上层。do_softirq。这两种状况执行软件中断的原理是开唤醒ksoftirqd线程。这个线程去执行软件中断服务程序。
软件中断服务程序不允许在一个硬件中服务程序内部执行,也不允计在一个软件中断服务程序内部执行。

内核在每次中断(以及系统调用和异常)服务完毕返回用户空音之前都要检查是否需要调度,若有需要就进行进程调度。事实上,调度只是当CPU在内核中运行时才可能发生。进程调度发生在两种情况下。一种是自愿的,通过像SLEEP之类的系统调用实现。或者是在通过其它系统调用进入内核以后因某种原因受阻需要等待,而自愿让内核调度其它进程先来运行。另一种是强制的,当一个进程连续运行的时间超过一定限度时,内核就会强制地度度其它进程运行。LINUX是分时系统,时钟中断是维护生命的必要条件,所以称时钟中断为heart
beart也既心跳。
Struct timeval xtime数据结构中记载的是从历史上某一刻开始的时间的绝对值,其数值来自计算机中一个CMOS晶片,常常称为实时时钟。这块CMOS晶片是由电池供电的,所以既使机器断了电也还能维持正确的时间。
Jiffies是一个无符号的全局整数,记录着从开机以来时钟中断的次数。每个jiffy的长度就是时钟中断的周期,有时候也称为一个tick,取决于系统中的一个常数HZ.在内核中jiffies远远比xtime重要。
在LINUX中谈及系统时钟时,实际上是指着内核中的两个全局变量一个xtime
一个jiffies.
Linux系统中struct timer_list
定义N个这样的定时器,这个是软件层面实现的定时器与硬件上的定时器中断是两码事,struct timer_list低层实现是由硬件上的定器中断来实现的。相当一个1
10 100MS这样一些周期性的调用的。
mod_timer
/**
* mod_timer - modify a timer's timeout
* @timer: the timer to be modified
* @expires: new timeout in jiffies/
add_timer
/**
* add_timer - start a timer
* @timer: the timer to be added/

进程上下文和中断上下文介绍
http://www.cnblogs.com/lambda107/archive/2010/08/10/1795767.html
根据中断的来源,中断可分为内部中断和外部中断,内部中断的中断来源来自CPU内部(软件中断指令,溢出,除法错误等,例如,操作系统从用户态切换到内核态需要借助CPU内部中断),外部中断的中断源来自CPU外部,由外设提出请求。
根据是否可以屏蔽中断分为可屏蔽中断与不屏蔽中断NMI,可屏蔽中断可以通过屏蔽字被屏蔽,屏蔽后,该中断不再得到响应,而不可屏蔽中断不能被屏蔽。[不可屏蔽中断属于
中断请求 的一种。外部不可屏蔽中断请求经由专门的CPU针脚NMI(像电源掉电),通知CPU发生了灾难性事件,如电源掉电、总线奇偶位出错等。内部不可屏蔽中断请求是CPU内部自发产生的,如存储器读写出错、溢出中断、除法出错中断等。NMI线上中断请求是不可屏蔽的(既无法禁止的)、而且立即被CPU锁存。因此NMI是边沿触发,不需要电平触发。NMI的优先级也比INTR高。不可屏蔽中断的类型指定为2,在CPU响应NMI时,不必由中断源提供中断类型码,因此NMI响应也不需要执行总线周期INTA
]
根据中断入口跳转方法的不同,中断分为向量中断和非向中断。采用向量中断的CPU通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断事情对应的地址执行。不同的中断号的中断有不同的入口地址,非向量中断的多个中断共享一个入口址,进入该入口址后再通过软件来判断中断标志来识别具体是那个中断。也就是说,向是中断由硬件提供中断服务程序的入口地址,非向量中断由软件提供中断服务程序入口地址。
中断上半部(top half)在中断到来时立既执行是紧急的硬件操作,中断下半部(bottom half)一般是中断到来时延缓执行的耗时操作。顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行登记中断的工作,登记中断意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。Linux系统中,查看/proc/interrupts文件可以获得系统中断的统计信息。
一个Tasklet结构体中断低半部方法同时只能在一个CPU上执行,而软中断低半部方法的结构体同时可以多个CPU上同时进行。Tasklet低半部的实用模型如下:
void my_tasklet_func(unsigned long);//定义一个处理函数
DECLARE_TASKLET(my_tasklet,my_tasklet_func,data);//定义一个tasklet struct结构体
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs){
...
tasklet_schedult(&xxx_tasklet);
...
}//在request_irq注册的中断中的注册回调函数xxx_interrupt中调用tasklet_schedult(&xxx_tasklet);我们定义的tasklet
struct结构体,这样系统在合适的时候就会调用我们定义的tasklet struct中定义的低半部函数my_tasklet_func

Linux
工作队列和等待队列的区别 :http://blog.chinaunix.net/uid-22590270-id-3242503.html
Tasklet workqueue
软件中断区别:http://blog.chinaunix.net/uid-27664726-id-3821077.html
Tasklet
软件中断会在中断返回时调用irq_exit函数中__do_softirq函数去执行我们在中断上半文中加入的下半文函数。因此对于tasklet
软件中断也是在中断中执行的,只是在中断返回时,因此这个里面的函数不能有阻塞和休眠。只是在中断返回时打开了硬件中断,这个时候执行的东西可以被打断。如果在中断返回时出现阻塞或休眠那么系统可能出现僵死的情况。而工作队列是在在一个线程中去执行的,可以阻塞和休眠。软件中断和tasklet可以在中断返回时执行同时也可以ksoftirqd线程去执行,而工作队列只在ksoftirqd中去执行,所以前者不可以休眠,后者可以休眠。
Work_strut
工作队列的低半部实现方式的实用方法:
struct work_struct my_wq;
void my_wq_func(unsigned long);
下面函数在一般在驱动的注册时与request_irq在同一个大函数中实现
INIT_WORK(&my_wq,(void(*))my_wq_func,NULL);
在request_irq注册的回调函数中调用schedule_work(&my_wq)它,来实现延后执行中断下半部。
软件中断是用软件方式模拟硬件中断的概念,实现宏观上的异步执行效果。Tasklet也是基于软件中断实现的。
硬中断,软件中断和信号的区别:硬件中断是外部设备对CPU的中断,软件中断通常是硬件中断服务程序对内核的中断,而信号则是由内核或其它进程对某个进程的中断。
在LINXU内核中,用softirq_action结构体表征一个软件中断。这个结构中包含软件中断处理函数指针和传递给该函数的函数,使用OPEN_SOFTIRQ()函数可以注册软件中断对的应处理软件,而RASIE_SOFTIRQ()函数可以触发一个软件中断。
软件中断和TASKLET仍然运行于中断上下文,而工作队列则运行于进程上下文,因此,软件中断和TASKLET处理函数中不能睡眠,而工作队列处理函数中允许睡眠。local_bh_disable()和
local_bh_enable()是内核中用于禁止和使能软中断和
tasklet 底

半部机制的函数

_iomem是2.6.9中加入的特性。是用来个表示指会指向一个I/O的内存空间。主要是为了driver的通用性考虑。由于不同的CPU体系结构对I/O空间的表示可能不同。当使用__iomem时,compiler会忽略对变量的检查(因为用的是void
__iomem)。但sparse会对它进行检查,当__iomem的指针和正常的指针混用时,就会发出一些warnings。
2440RTC中断详解http://blog.csdn.net/hanmengaidudu/article/details/23964263
软件意义上的定时器最终依赖硬件定时器来实现,内核在时钟中断发后检测各定时器是否到期,到期后的定时器处理函数将作为软件中断在低半部执行,实质上,时钟中断处理程序执行updata_process_timers()函数,该函数调用run_local?_timers()函数,这个函数处理timer_sofirq软件中断,运行当前处器上到期的所有定时器。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: