LINUX的中断处理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013298300/article/details/38874321

				          LINUX中断

LINUX的中断响应

1 硬件结构
X86结构上面,处理器本身并没有集成中断控制器。所以外部拓展了一个中断控制器,中断控制器用于连接中断源以及CPU的中断管脚。在发生中断的时候,CPU管脚接收到中断信号并且做出响应。硬件逻辑电路完成了自动保护现场的功能。

2 中断的软件部分
响应中断后,CPUPC指针自动指向中断向量表跳转到中断程序入口。这时侯调用处理函数,保护现场并且读出中断号IRQ以识别是哪个设备发生了中断。由于在响应中断的时候,为了防止中断被打断(LINUX是非实时内核),中断处理必须在关闭中断响应的情况下进行,这样的做法一个弊端就是可能会导致在中断处理期间无法响应其他设备的中断。于是要求ISR必须要短小精悍。为了满足这一要求,提高系统的吞吐量,LINUX采用上下部分中断响应的方法(不是必须的),在中断程序中主要登记任务(延时处理),退出中断后才处理登记的需要完成的任务。

二
实现细节
1 do_irq()
在中断响应就是调用这个函数来处理中断的。函数实现如下

void __irq_entry do_IRQ(unsigned int irq, 
				struct pt_regs *regs)
{
    /*保护现场,更新寄存器。保存抽象CPU*/
    struct regs *old_regs = set_irq_regs(regs);
    
    /*设置相关标志位以通知内核当前在处理中断状态*/
    irq_entry();
    /*检查堆栈是否溢出*/
    check_stack_overflow();
    /*处理中断,调用desc->handler*/
    generic_handle_irq(irq);
    /*清除相关标志位并且进入中断底半部处理任务以通知内核退出中断*/
    irq_exit();
    /*恢复现场*/
    set_irq_regs(old_regs);   
}

1 该函数首先要进行现场保护工作
由于LINUX是多任务操作系统,对于每一个任务来说,都完全占有所有地址和CPU(抽象CPU),所以在中断发生的时候,该函数调用set_irq_regs(regs)函数,将一个per_CPU类型的变量保存到old_regs当中,然后赋予_irq_regs赋予新的regs这样的话就可以访问到当前的中断现场。(对于中断现场的保护,实际上在发生中断的时候就已经由硬件逻辑保护起来了)

2 设置标志位进入中断
LINUX内核需要随时把握当前中断状态,这时侯中断程序需要设置一些标志位来表明自己进入中断。

3 检查堆栈溢出
理论上,LINUX的中断处理应该是在中断关闭的状态下进行的,但是当打开外部中断响应的时候,就有可能出现中断嵌套。在深度嵌套的情况下,就有可能出现堆栈溢出的问题。这时侯系统就会变得不安全(安卓ROOT的原理就是堆栈溢出提权),在必要的情况下系统会reset

4 调用通用中断处理函数
这个函数开始着手处理中断

static inline void generic_handle_irq(unsigned
int irq)
{
    struct irq_desc *desc = &irq_desc[irq];
    desc->handle(irq,desc);
}

这个函数根据irq号索引得到一个irq_desc(中断描述符)的数据结构。列出其重要成员如下:

struct irq_desc{
    irq_date irq_date;
    struct timer_rand_state *timer_rand_state;
    unsigned int __percpu   *kstat_irqs;
    irq_flow_handler_t handle_irq;
    struct irqaction*action;
}

irq_date 成员是一个保存有中断irq号,PIC抽象(主要是操作PIC的接口)的数据结构。
kstat_irqs成员用于中断统计计数。
handle_irq是一个高级中断处理函数,它会在内部调用具体的ISR
action则是一个ISR链表,在handle_irq当中被遍历调用。

5 调用
  
irq_exit()清除相关标志位并且处理中断底半部。如果底板半部是软中断或者tasklet那么就是运行在软中断上下文,如果是工作队列,那么就是运行在进程上下文。

三
中断的安装

1
调用irq_set_handler/irq_set_chained_handler函数安装高级中断处理函数handle_irq

static inlint void irq_set_handler(unsigned int
irq, 
                                 
irq_flow_handler_t handle)
{
    __irq_set_handler(irq, handle, 0, NULL);
}

static inlint void
irq_set_chained_handler(unsigned int irq, 
                                 
irq_flow_handler_t handle)
{
    __irq_set_handler(irq, handle, 1, NULL);
}

其中有
void  __irq_set_handler(unsigned int irq,
irq_flow_handler_t handle, 
                              int is_chained,
const char *name);

参数is_chained用于表明是否支持中断共享(建立action链表)。如果支持中断共享,那么在该函数内部,会设置
    desc->status_use_accessors |=
_IRQ_NOPROBE | _IRQ_NOREQUEST;
也就是这个中断不能支持中断探测,也无法支持request_irq来安装ISR

2 irq_handler完成的工作(边沿触发型实例)

1 首先检查该中断线是否屏蔽/是否正在处理/是否安装ISR。如果三者有其一,那就退出。
 
2 设置中断计数

3 向设备发送中断响应信号,表明CPU已经响应中断,好让设备停止发送中断信号。

4
检查是否安装了ISR,如果没有安装那就屏蔽中断线。因为如果没有安装ISR,那么中断信号得不到处理,CPU就会不停地响应中断。

5 调用handle_irq_event(desc)来处理中断事件。
该函数首先清除IRQS_PENDING然后再IRQD_IRQ_INPROGRESS置位表示正在进行中断处理。
遍历action链表进行调用。

3 调用request_irq进行ISR安装
 
static inline 
__must_check request_irq(unsigned int irq,
irq_handler_t handler,
                         unsigned long
irqflags,const char *devname,
                         void *dev_id)

函数request_irq其实就是调用了
int request_threaded_irq(unsigned int irq,
irq_handler_t handler,
                         irq_handler_t
threaded_fn,unsigned long irqflags,
                          const char *devname,
void *dev_id)
参数传递关系如下:

request_threaded_irq(irq,
handler,NULL,irqflags,name, dev_id)

其中irq是中断号,hanlderISR,而name是名字,dev_id是用来唯一标示ISRID

ISR的安装过程
1
检查irqflags,如果其中IRQ_SHARED置位,那么就要求给出dev_id,否则退出安装。
2 获取描述符。
3 
desc->status_use_accessors_IRQ_NOREQUEST是否置位,如果置位表明不能通过request_threaded_irq函数来安装ISR,退出安装。
4 获取action空间,并且初始化,将ISR安装到action链表上。
5调用__setup_irqaction安装到desc上。
如果desc->action为空,表明尚未安装action,即独占中断,此时只需desc->action
= action即可。
如果desc->action不为空,表明以安装action,也就是中断共享。这时侯要求新安装的中断不能破坏之前的中断工作模式,也就是要求irqflags要一致,如果不一致,那么安装失败。需要检查的irqflags位有:IRQF_SHARED,
IRQF_TRIGGER_MASK, IRQF_ONESHOT, IRQF_PERCUP

irq_theraded 机制

irq_theraded
机制就是在安装一个中断时,会在request_threaded_irq内部生成一个独立的线程,并且创建出来的时候以TASK_INTERRUPTIBLE状态睡眠,当中断发生时,只需要在ISR中唤醒即可。


五
中断底半部

为了解决处理大量数据和中断时间短的矛盾,LINUX将中断处理分成两部分,一部分是中断顶半部,一部分是中断底半部。中断顶半部是在屏蔽抢占和中断的情况下进行的,所以要求顶半部的程序必须短小。而底半部用于处理大量的数据,运行在可被中断的情况下。其机制---软中断和tasklet可以运行在中断上下文和进程上下文,由于
可能运行在中断上下文,所在软中断和tasklet中不允许有引起调度的函数调用,因为调度会引起上下文切换,这样会破坏中断上下文,导致异常。一般软中断在中断退出的时候实现。

实例:

do_IRQ()函数的irq_exit函数中实现了SOFTIRQ的调用。列出主要代码如下:

void irq_exit()
{
     /*栈中的preempt_count
-= IRQ——EXIT_OFFSET代表硬中断结束*/
    sub_preempt_cout(IRQ_EXIT_OFFSET);
           
/*如果当前不在中断上下文当中(判断在响应中断之前是否有软中断在执行),
   如果之前有软中断(中
  断底 半部)在处理,那么直接返回,继续处理*/
   
   if(!in_interrput() &&
local_softirq_pending())
        invoke_softirq();
}

首先函数先结束硬中断,这时侯内核抢占性和中断响应已经打开。然后判断是否处在中断上下文&是否有激活的软中断,如果处于中断上下文或者没有软中断激活,就直接返回。如果不处于中断上下文且有软中断已经激活,则处理软中断。

其中in_interrupt()是一个宏,展开如下:
int_interrupt() (preempt_count() & (HARDIRQ
| SOFTIRQ |NMI_MASK))
显然这里判断是否处于中断上下文包括:硬中断上下文,软中断上下文,NMI_MASK

考察这样的一种情况:
假设在处理软中断的时候,产生了一个异步中断,系统响应中断进入顶半部处理完之后,由于在中断之前正在处理软中断,所以这时侯应当是处在软中断上下文的,SOFTIRQ
是置位的,也就是说(preempt_count()
& (HARDIRQ | SOFTIRQ
|NMI_MASK))这个表达式应该是!0的。于是这时侯if(!in_interrput()
&&
local_softirq_pending())不成立,中断函数直接返回,返回到原来的软中断当中,继续处理软中断。如果没有if(!in_interrput()
&&
local_softirq_pending())语句,那么中断函数就会直接处理另外的软中断,而原来在软中断不能继续执行。
所以,“当前不在中断上下文中”这一前提保证了如果代码正在SOFTIRQ部分执行的时候,如果发生了一个外部中断,那么中断处理函数结束HARDIRQ部分时,将不会处理softirq,而是直接返回,这样此前被中断的SOFTIRQ部分将继续执行。

invoke_softirq()是一个宏,展开如下:

#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
#define invoke_softirq() __do_irq()
#else
#define invoke_softirq() do_irq()

定义__ARCH_IRQ_EXIT_IRQS_DISABLED这个宏则硬中断的退出的时候关闭外部中断响应,直接调用__do_irq()即可。如果没有关闭,则调用do_irq(),该函数最终还是要调用__do_irq()函数,不过在调用之前会先关闭中断。总之,要保证在调用__do_irq时屏蔽外部中断响应。软件中断有11种类型,每一种类型对应__softirq_pending的一比特位,在__do_irq函数中,会从低到高位扫描__softirq_pending,当发现某一位置位,就调用软中断向量表(一个函数指针数组)中的对应函数。

值得注意的是,在处理SOFTIRQ时(从向量表取出响应函数的过程)是不可以响应外部中断的,但是当执行软件中断函数的时候是可以响应外部中断的。(详情看__do_irq源代码)。另外,由于软件中断运行于中断上下文当中,所以不能在软中断内调用任何可能引起进程调度的函数,因为进程的调度意味着进程上下文的切换,会破坏中断上下文,导致异常。


            

没有更多推荐了,返回首页