Linux系统中的中断

中断的引入

硬件中断是可屏蔽的, 软中断不可屏蔽!

妈妈怎么知道孩子醒了

妈妈怎么知道卧室里小孩醒了?

① 时不时进房间看一下:查询方式

简单,但是累

② 进去房间陪小孩一起睡觉,小孩醒了会吵醒她:休眠-唤醒

不累,但是妈妈干不了活了

③ 妈妈要干很多活,但是可以陪小孩睡一会,定个闹钟:poll方式

要浪费点时间,但是可以继续干活。

妈妈要么是被小孩吵醒,要么是被闹钟吵醒。

④ 妈妈在客厅干活,小孩醒了他会自己走出房门告诉妈妈:异步通知

妈妈、小孩互不耽误。

后面的3种方式,都需要“小孩来中断妈妈”:中断她的睡眠、中断她的工作。

实际上,能“中断”妈妈的事情可多了:

① 远处的猫叫:这可以被忽略

② 门铃、小孩哭声:妈妈的应对措施不一样

③ 身体不舒服:那要赶紧休息

④ 有蜘蛛掉下来了:赶紧跑啊,救命

 妈妈当前正在看书,被“中断”后她会怎么做?流程如下:

① 妈妈正在看书

② 发生了各种声音

        可忽略的远处猫叫

        快递员按门铃

        卧室中小孩哭了

③ 妈妈怎么办?

        a. 先在书中放入书签,合上书 (这其实就是相当于保存现场了)

        b. 去处理                                (调用中断服务程序)

                        对于不同的情况,处理方法不同:

                        对于门铃:开门取快递

                        对于哭声:照顾小孩

        c. 回来继续看书        (恢复现场)


基本概念 

/*******************************************************/

/*******************************************************/

什么是异常?

  • 正常工作之外的流程都叫异常
  • 异常会打断正在执行的工作,并且一般我们希望异常处理完成后继续回来执行原来的工作
  • 中断是异常的一种


异常向量表:

  • 所有的CPU都有异常向量表,这是CPU设计时就设定好的,是硬件决定的。
  • 当异常发生时,CPU会自动动作(PC指针跳转到异常向量处处理异常,有时伴随一些辅助动作)。
  • 异常向量表是硬件向软件提供的处理异常的支持。

注意区分中断和中断源:

        在Linux中有各种各样的中断:比如GPIO

        一个中断类型可能有多个中断源

中断向量:中断的地址的变量;

中断向量表:中断类型号与相应中断源的中断处理程序入口地址之间的连接表;

中断服务程序:中断时所执行的中断代码

  1. 寄存器是一般是中央处理器CPU的内部组成部分,读写速度和CPU运行速度基本匹配。寄存器是有限存储容量的高速存储部件,可用来存储指令、数据和地址。在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC)。
  2. 存储器在CPU外,一般只硬盘、U盘等在切断电源后可以保存资料的设备。
  3. 内存指的是内存条,由于一半的硬盘读取速度很慢,所以用先将硬盘里面的东西读取到内存条里面,然后在给CPU进行处理,这样是为了加快系统的运行速度。
  4. CPU计算时,先把数据从硬盘读到内存,然后再把即将要用到的数据读到寄存器。最理想的情况下,CPU要用到的数据都能从寄存器读到,这样读写速度很快,如果寄存器没有要用的数据,就要从内存中甚至从硬盘里读取,这样读取数据的时间比CPU计算的时间还多。
     

CPU在运行的过程中,也会被各种“异常”打断。这些“异常”有:

① 指令未定义

② 指令、数据访问有问题

③ SWI(软中断)

④ 快中断

⑤ 中断

中断也属于一种“异常”,导致中断发生的情况有很多,比如:

① 按键

② 定时器

③ ADC转换完成

④ UART发送完数据、收到数据

⑤ 等等

这些众多的“中断源”,汇集到“中断控制器”,由“中断控制器”选择优先级最高的中断并通知CPU。


中断的处理流程

arm对异常(中断)处理过程:

初始化:

        a. 设置中断源,让它可以产生中断

        b. 设置中断控制器(可以屏蔽某个中断,优先级)

        c. 设置CPU总开关(使能中断)

执行其他程序:正常程序

产生中断:比如按下按键--->中断控制器--->CPU

CPU 每执行完一条指令都会检查有无中断/异常产生

CPU发现有中断/异常产生,开始处理。

对于不同的异常,就会跳去不同的地址执行程序。

这地址上,只是一条跳转指令,跳去执行某个函数(地址),这个就是异常向量。(这就和异常向量表联系起来了)

③④⑤都是硬件做的。

这些函数做什么事情?

软件做的:

a. 保存现场(各种寄存器)

b. 处理异常(中断):

分辨中断源,再调用不同的处理函数

c. 恢复现场


异常向量表定义的是什么?

×××
定义的就是异常服务程序的跳转指令,因为每个异常向量在异常向量表中只有很少字节的存储空间,所以通常存放跳转指令,使程序跳转到存储器的其他地方,再执行中断处理。这里cpu就可以找异常服务程序

当CPU产生了异常 ,CPU会先判断产生了什么类型的异常  然后就会去往 异常向量表的对应异常类型的地址中 查找接下来要执行的指令 然后跳转到存储该类型异常服务函数的地址 执行异常服务函数

×××

<--这就是ARM的异常向量表图示 左侧为地址(因为ARM是32位所以是4字节对齐)

例如:若发生了普通中断 ,PC指针会去到中断向量表的 IRQ地址处, 然后跳转到 IRQ对应的存储器的某个存放中断服务函数的地址 然后执行中断服务函数

u-boot或是Linux内核,都有类似如下的代码:

_start: b   reset

    ldr pc, _undefined_instruction

    ldr pc, _software_interrupt

    ldr pc, _prefetch_abort

    ldr pc, _data_abort

    ldr pc, _not_used

    ldr pc, _irq //发生中断时,CPU跳到这个地址执行该指令 **假设地址为0x18**

    ldr pc, _fiq

这就是异常向量表,每一条指令对应一种异常。

发生复位时,CPU就去 执行第1条指令:b  reset。

发生中断时,CPU就去执行“ldr  pc, _irq”这条指令。

这些指令存放的位置是固定的,比如对于ARM9芯片中断向量的地址是0x18。

当发生中断时,CPU就强制跳去执行0x18处的代码。

在向量表里,一般都是放置一条跳转指令,发生该异常时,CPU就会执行向量表中的跳转指令,去调用更复杂的函数。

当然,向量表的位置并不总是从0地址开始,很多芯片可以设置某个vector base寄存器,指定向量表在其他位置,比如设置vector base为0x80000000,指定为DDR的某个地址。但是表中的各个异常向量的偏移地址,是固定的:复位向量偏移地址是0,中断是0x18。

关于GIC

注意到异常向量表中和中断相关的中断只有 IRQ和FIQ 但是我们知道在Linux中是有各种各样的类型的中断的,那么CPU是怎么区分各种各样的中断的?

通过GIC。

 GIC 是联系外设中断和 CPU 的桥梁,也是各 CPU 之间中断互联的通道(也带有管理功能),它负责检测、管理、分发中断,可以做到:

1、使能或禁止中断;

2、把中断分组到Group0还是Group1(Group0作为安全模式使用连接FIQ  ,Group1 作为非安全模式使用,连接IRQ );

3、多核系统中将中断分配到不同处理器上;

4、设置电平触发还是边沿触发方式(不等于外设的触发方式);

5、虚拟化扩展。

ARM CPU 对外的连接只有2 个中断: IRQ和FIQ ,相对应的处理模式分别是一般中断(IRQ )处理模式和快速中断(FIQ )处理模式。所以GIC 最后要把中断汇集成2 条线,与CPU 对接。

相当于是一次中断的产生 需要CPU执行两个中断服务函数:第一次是CPU执行GIC上报的中断的中断服务函数,也就是处理函数。第二次是 GIC的处理函数,去找触发中断的中断源是哪一个 然后再去调用该中断源的中断服务函数

就如上图:

假设GPIO1的中断在GPIO中断中是B号中断,GPIO类型的中断在GIC中是A号中断。既:不管是GPIO1的引脚2中断还是GPIO2的引脚3中断、不管是上升沿还是下降沿触发的中断,在GIC中都是A号中断;随后GIC就会将A号中断上报给CPU,CPU就知道了此时发生了A号中断。

CPU会去执行GIC A号中断的中断服务函数(处理函数) 在处理函数中会去找引发GIC中断A(GPIO中断)的是哪个中断 发现是B号中断 也就是找到对应的irq_dsc数组项  irq_dsc[virq]  上图中是B号中断 也就是找到 irq_dsc[B] 然后会马上执行handle_irq函数 去找到是哪个中断源 然后马上顺序执行irqaction结构体类型的action链表中的每个成员的中断服务函数,如果不是触发中断的中断源的中断服务函数就会马上退出 执行下一个,直到找到对应中断源的中断服务函数 执行该函数。


进程、线程、中断的核心:栈

中断中断,中断谁?

中断当前正在运行的进程、线程。

进程、线程是什么?内核如何切换进程、线程、中断?

要理解这些概念,必须理解栈的作用。

 ARM处理器程序运行的过程

ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点:

① 对内存只有读、写指令

② 对于数据的运算是在CPU内部实现

③ 使用RISC指令的CPU复杂度小一点,易于设计

比如对于a=a+b这样的算式,需要经过下面4个步骤才可以实现:

细看这几个步骤,有些疑问:

① 读a,那么a的值读出来后保存在CPU里面哪里?

② 读b,那么b的值读出来后保存在CPU里面哪里?

③ a+b的结果又保存在哪里?

CPU运行时,先去取得指令,再执行指令(寄存器R15来处理机器指令执行下面的步骤):

① 把内存a的值读入CPU寄存器R0

② 把内存b的值读入CPU寄存器R1

③ 把R0、R1累加,存入R0

④ 把R0的值写入内存a

程序被中断时,怎么保存现场?

从上图可知,CPU内部的寄存器很重要,如果要暂停一个程序,中断一个程序,就需要把这些寄存器的值保存下来:这就称为保存现场。

保存在哪里?内存,这块内存就称之为栈。

程序要继续执行,就先从栈中恢复那些CPU内部寄存器的值。

(也就是说上下文切换 其实就是切换寄存器的值。)

这个场景并不局限于中断,下图可以概括程序A、B的切换过程,其他情况是类似的:

 (上图是程序A被程序B打断的过程,如果程序A被中断打断也是类似的)

a. 函数调用:

在函数A里调用函数B,实际就是中断函数A的执行。

那么需要把函数A调用B之前瞬间的CPU寄存器的值,保存到栈里;

再去执行函数B;

函数B返回之后,就从栈中恢复函数A对应的CPU寄存器值,继续执行。

b. 中断处理

进程A正在执行,这时候发生了中断。

CPU强制跳到中断异常向量地址去执行,

这时就需要保存进程A被中断瞬间的CPU寄存器值,

可以保存在进程A的内核态栈,也可以保存在进程A的内核结构体中。

中断处理完毕,要继续运行进程A之前,恢复这些值。

c. 进程切换

在所谓的多任务操作系统中,我们以为多个程序是同时运行的。

如果我们能感知微秒、纳秒级的事件,可以发现操作系统时让这些程序依次执行一小段时间,进程A的时间用完了,就切换到进程B。

怎么切换?

切换过程是发生在内核态里的,跟中断的处理类似。

进程A的被切换瞬间的CPU寄存器值保存在某个地方;

恢复进程B之前保存的CPU寄存器值,这样就可以运行进程B了。

(所以进程的上下文切换实际上就是切换cpu寄存器中的值)

所以,在中断处理的过程中,伴存着进程的保存现场、恢复现场。

进程的调度也是使用栈来保存、恢复现场:

值得注意的地方:

 在Linux中:资源分配的单位是进程,调度的单位是线程。

 也就是说Linux中线程参与调度,也就是说会涉及保存现场,那么线程就得有自己的栈;也就是线程栈。

(线程栈保存在所属进程的共享区中。(我们在学习进程的时候知道共享区是用来保存 链接的libc库等和线程栈和进程间共享内存等的东西 详情去看写的进程的基本概念的博客))

线程在被调度时(也就是被切换时),此时CPU的寄存器的值会被保存在该线程的线程栈中(保存现场),当下一次切换到该线程时,将线程栈中保存的各寄存器值写入到CPU的寄存器 (这就是恢复现场了)


 Linux系统对中断处理的演进

Linux系统中有硬件中断,也有软件中断。(主要学习硬件中断)

Linux内核通过中断号来找到对应的中断服务函数

对硬件中断的处理有2个原则:不能嵌套,越快越好。

Linux对中断的扩展:硬件中断、软件中断

1.

Linux系统把中断的意义扩展了,对于按键中断等硬件产生的中断,称之为“硬件中断”(hard irq)。每个硬件中断都有对应的处理函数,比如按键中断、网卡中断的处理函数肯定不一样。

为方便理解,你可以先认为对硬件中断的处理是用数组来实现的,数组里存放的是函数指针:

 注意:上图是简化的,Linux中这个数组复杂多了。

当发生A中断时,对应的irq_function_A函数被调用。硬件导致该函数被调用。

2.

/****************软中断start*****************/

相对的,还可以人为地制造中断:软件中断(soft irq),如下图所示:

注意:上图是简化的,Linux中这个数组复杂多了。

问题来了:

a. 软件中断何时生产?

由软件决定,对于X号软件中断,只需要把它的flag设置为1就表示发生了该中断。

b. 软件中断何时处理?

软件中断嘛,并不是那么十万火急,有空再处理它好了。

什么时候有空?不能让它一直等吧?

Linux系统中,各种硬件中断频繁发生,至少定时器中断每10ms发生一次,那取个巧?

在处理完硬件中断后,再去处理软件中断?就这么办!

有哪些软件中断?

查内核源码include/linux/interrupt.h

怎么触发软件中断?最核心的函数是raise_softirq,简单地理解就是设置softirq_veq[nr]的标记位:

怎么设置软件中断的处理函数:

extern void open_softirq(int nr, void (*action) (struct soft_action*));

后面讲到的中断下半部tasklet就是使用软件中断实现的。 

 /****************软中断end*****************/

中断处理原则1:不能嵌套

官方资料:中断处理不能嵌套

kernel/git/torvalds/linux.git - Linux kernel source tree

中断处理函数需要调用C函数,这就需要用到栈。

中断A正在处理的过程中,假设又发生了中断B,那么在栈里要保存A的现场,然后处理B。

在处理B的过程中又发生了中断C,那么在栈里要保存B的现场,然后处理C。

如果中断嵌套突然暴发,那么栈将越来越大,栈终将耗尽。

所以,为了防止这种情况发生,也是为了简单化中断的处理,在Linux系统上中断无法嵌套:即当前中断A没处理完之前,不会响应另一个中断B(即使它的优先级更高)。

中断处理原则2:越快越好

妈妈在家中照顾小孩时,门铃响起,她开门取快递:这就是中断的处理。她取个快递敢花上半天吗?不怕小孩出意外吗?

同理,在Linux系统中,中断的处理也是越快越好。

在单芯片系统中,假设中断处理很慢,那应用程序在这段时间内就无法执行:系统显得很迟顿。

在SMP系统中,假设中断处理很慢,那么正在处理这个中断的CPU上的其他线程也无法执行。

在中断的处理过程中,该CPU是不能进行进程调度的,所以中断的处理要越快越好,尽早让其他中断能被处理──进程调度靠定时器中断来实现。

在Linux系统中使用中断是挺简单的,为某个中断irq注册中断处理函数handler,可以使用:

request_irq函数

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)
{
	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

可见request_irq ()其实还是调用了request_pthreaded_irq ()

 其实就是不使用线程的 request_pthreaded_irq ()

注意:request_irq和request_threaded_irq的irq参数其实是虚拟中断号

 在handler函数中,代码要尽可能高效。

但是,处理某个中断要做的事情就是很多,没办法加快。比如对于按键中断,我们需要等待几十毫秒消除机械抖动。难道要在handler中等待吗?对于计算机来说,这可是一个段很长的时间。怎么办?

对于耗时中断的处理办法

1.继续使用requst_irq 但是将中断分为 上半部 和 下半部 

2.使用requst_pthreaded_irq 内核线程处理中断

1.上半部、下半部

要处理的事情实在太多,拆分为:上半部、下半部

当一个中断要耗费很多时间来处理时,它的坏处是:在这段时间内,其他中断无法被处理。换句话说,在这段时间内,系统是关中断的。

如果某个中断就是要做那么多事,我们能不能把它拆分成两部分:紧急的、不紧急的?

在handler函数里只做紧急的事,然后就重新开中断,让系统得以正常运行;那些不紧急的事,以后再处理,处理时是开中断的。

中断下半部的实现有很多种方法,讲2种主要的:a.tasklet(小任务)、b.work queue(工作队列) 

1.A. 下半部要做的事情耗时不是太长:tasklet

假设我们把中断分为上半部、下半部。发生中断时,上半部下半部的代码何时、如何被调用?

当下半部比较耗时但是能忍受,并且它的处理比较简单时,可以用tasklet来处理下半部。tasklet是使用软件中断来实现。

写字太多,不如贴代码,代码一目了然:

 使用流程图简化一下:

假设硬件中断A的上半部函数为irq_top_half_A,下半部为irq_bottom_half_A。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值