Linux内核设计与实现(8)---中断和中断处理

之前写过中断相关的文章了,详细分析过ARM底层中断实现过程,这篇文章着重从操作系统设计层面理解中断系统。
1.中断
中断:是外围设备产生的异步事件,不同设备,中断号不同;比如在经典PC上,IRQ 0是时钟中断,IRQ 1是键盘中断;对于连接于PCI总线上的设备,中断还可以是动态分配的。
异常:与中断不同,异常的产生必须考虑与处理器时钟同步,比如除零,缺页,软中断等,都是由处理器自身产生的同步事件,异常也可称为同步中断
本文对异步中断的讨论,大部分也适合于异常(同步中断)。

2.中断处理
每个中断都对应有一个中断处理程序;
ISR是被内核调用来响应中断的,它运行在中断上下文中(也称原子上下文),因此代码是不可阻塞的。
ISR应该快速执行。

3.上半部与下半部
又想ISR运行的快,又想ISR完成的工作量多,这是一个此消彼长的矛盾关系;为了平衡这个矛盾,Linux内核把中断分为两部分:
上半部:接收一个中断,立即开始执行,但只做严格时限的工作;例如中断应答,硬件复位之类,这些工作是在所有中断被禁止的情况下完成的。
下半部:上半部执行完后,在合适的时间,调度执行下半部,下半部可以处理一些耗时的工作,可以休眠

4.注册中断处理程序
中断处理程序通过request_irq()注册(在linux/interrupt.h定义)

点击(此处)折叠或打开

  1. int request_irq(
  2.     unsigned int irq,
  3.     irq_handler_t handler,
  4.     unsigned long flags,
  5.     const char *name,
  6.     void *dev)

(1)第一个参数irq:

表示要分配的中断号,这个值通常是预先确定的,但对有的设备来也可以探测获取,或通过编程动态确定。

(2) handler:函数指针,指向这个中断的ISR。

该函数原型如下,接受2个参数,并有irqreturn_t类型的返回值:

typedef irqreturn_t (*irq_handler_t)(int, void *);

(3) flags:中断处理标志,可以用0,也可以用以下一个或多个标志的位掩码

IRQF_DISABLED:  keep irqs disabled when calling the action handler

如果不设置该位,中断处理程序可以与除本身外的其他任何中断同时运行,一般不会设置此位,这个用法是给希望快速执行的轻量级中断,在过去的中断中用以区分“快速”和“慢速”中断。

IRQF_SAMPLE_RANDOM

此标志表明这个设备的中断对内核熵池有贡献,用以产生随机数。

IRQF_TIMER

该标志是特别为系统定时器的中断处理程序而准备的。

IRQF_SHARED:allow sharing the irq among several devices

必须在给定中断线的所有处理程序中都指定这个标志,否则,一个中断线上只能有一个处理程序;

(4)name:

中断设备的名字,比如PC上键盘中断对应的值是“keyboard”,这个名字会被/proc/irq和/proc/interrupts文件使用。

(5)dev :用于共享中断线

①中断线被共享:dev必须传递唯一信息,当一个中断处理程序需要释放时,dev将提供唯一的标志信息,以便确定要在共享中断线上要删除哪一个处理程序;

②不是共享中断线,可以设为NULL,也可以用来传递设备私有信息

request_irq()成功执行会返回0,若非0,就表示发生错误,这时指定中断处理程序不会被注册。最常见的错误是-EBUSY,它表示中断线已经在使用,或者注册共享中断时没有指定IRQF_SHARED。

初始化硬件和注册中断处理程序的顺序必须正确,以防止ISR在设备初始化完成之前就开始执行。初始化硬件,包括set_irq_type();

int set_irq_type(unsigned int irq, unsigned int type);

irq:中断号

type中断类型,可以设为下列标志

IRQT_RISING,IRQT_FALLING,IRQT_BOTHEDGEIRQT_LOW ,IRQT_HIGH

释放中断处理程序:

卸载驱动程序的时候,要注销相应的中断处理程序,释放中断线

void free_irq(unsigned int irq, void *dev);

如果中断线非共享,该函数删除处理程序,禁止相应中断线。

若为共享中断,free_irq()删除dev相应的处理程序,而这个中断线只有在所有处理程序都被删除后,才会被禁用。

必须在进程上下文中调用free_irq()。

5.编写中断处理程序
(1) 中断处理程序原型:typedef irqreturn_t (*irq_handler_t)(int, irq void * dev);

irq:中断号

dev通用指针,就是request_irq()注册时的dev。

在共享中断线时,用来区分不同设备;

非共享中断中,一般传入硬件设备私有信息

irqreturn_t:返回值,一般由两个值

IRQ_NONE: 当ISR检测到一个中断,但该中断对应的设备并不是注册ISR时指定的产生源时,返回IRQ_NONE;

IRQ_HANDLED: ISR被正确调用且确实是它所对应的设备产生了中断,返回IRQ_HANDLED.

也可以用宏IRQ_RETVAL(val),val非零时返回IRQ_HANDLED,val为0时返回IRQ_NONE.

利用特殊返回值,讷河可以知道设备发出的是否是一种虚假的中断(为请求)。

中断处理期望完成的工作,扩充的耗时的工作,应该分配给下半部完成。

(2) 中断处理程序特点:

①不能向用户空间发送或接收数据,因为它不在任何进程上下文运行;

②处理程序不能做任何可能休眠的操作;

③不能调用schedule()函数;

④Linux的ISR无须是重入的,当一个给定ISR在执行时,相应的中断线在所有处理器上都会被屏蔽。

⑤通常情况下,其他中断是打开的,所以这些不同中断线上的ISR都可以被处理。

中断的嵌套,还要看CPU硬件提供的支持,Linux只能适应硬件,而不能改变硬件特性。

(3) 共享的中断处理程序:注意点主要有三

①request_irq()的参数flags必须设置为IRQF_SHARED标志;

②对于每个注册的中断处理程序,dev参数必须唯一,且不能为空;

③中断处理程序必须能够区分设备是否真产生了中断,一般由状态寄存器或类似机制实现;

内核收到一个中断后,它将依次调用在该中断线上注册的每一个处理程序,因此每个处理程序都应该能为中断负责,若发现相关设备并未产生中断,应该立即退出

6.中断上下文
进程上下文,是一种内核所处的操作模式,此时内核代表进程执行,进程以进程上下文的形式连接到内核中,因此进程上下文可以睡眠,也可以调用调度程序;

中断上下文,与进程不相干,它无法被调度,所以不能睡眠(一旦睡眠,则无法返回);

中断上下文具有较为严格的时间限制,中断上下文的代码应当迅速、简洁,尽量不要去处理繁重的工作。中断打断了其他代码的执行(甚至可能是打断了在其他中断线上的另一个中断处理程序),所以ISR应该尽量快速的退出,尽量把工作从ISR中分离出来,放到下半部去执行。

中断处理在32位机上只有4K的中断栈,因此应该尽量节约内核栈空间。

7.中断处理机制的实现
中断处理系统在Linux中的实现,是非常依赖于体系结构的。
当一个中断产生,处理器会跳到一个预先定义好的入口点,每条中断线处理器都会跳转到一个唯一的对应位置。在栈中保存IRQ号,调用do_IRQ()函数。
取出栈顶的参数,计算出中断号后,do_IRQ()对所接收的中断进行应答,禁止这条线上的中断传递。然后调用handle_IRQ_event()来运行中断处理程序。

handle_IRQ_event()函数中,若为共享中断,所有该中断线上的处理程序都被执行一遍。

执行完中断处理程序,返回入口点跳到ret_from_intr()。

这个例程中检查重新调度程序是否正在挂起(need_resched被设置),如果重新调度程序正在挂起,如果内核正返回用户空间,那么schedule()会被调用;如果内核正要返回内核空间(中断了内核本身),只有在preempt_count为0时(不持有锁),schedule()才会被调用,否则抢占内核不安全。

8./proc/interrupts
procfs是一个虚拟文件系统,只存在于内核内存中。对procfs的读写都要通过内核函数实现,/proc/interrupts文件,存放的是系统中与中断相关的统计信息。分别显示中断线,中断发生次数,中断控制器,发生中断名字等。

9.中断控制
Linux内核提供了一组接口,能够禁止当前处理器的中断系统,或屏蔽掉整个机器的一条中断线的能力。

控制中断系统的原因归根结底,是需要提提供同步,通过禁止中断,可以确保某个中断处理程序不会抢占当前的代码,禁止中断还可以禁止内核抢占。

Linux支持多处理器,因此内核代码一般都需要获取某种锁,来防止其他处理器对共享数据的并发访问。锁提供保护机制,防止其他处理器的并发访问;禁止中断提供保护机制,防止来自其他中断处理程序的并发访问;

(1)禁止和激活中断

禁止/激活当前处理器上本地中断

点击(此处)折叠或打开

  1. local_irq_disable();
  2. local_irq_enable()

禁止中断之前保存中断系统状态会更安全

禁止本地中断,并保存中断状态

点击(此处)折叠或打开

  1. unsigned long flags;
  2. local_irq_save(flags);//禁止中断

  3. local_irq_restore(flags);//中断被恢复到它们原来的状态

这一对函数,应当在同一个函数内调用。

(2)禁止指定中断线

点击(此处)折叠或打开

  1. void disable_irq(unsigned int irq); //当前正在执行的所有处理程序完成后,才返回
  2. void disable_irq_nosync(unsigned int irq); //不等待ISR执行完毕
  3. void enable_irq(unsigned int irq);
  4. void synchronize_irq(unsigned int irq); //等待指定中断处理程序退出,才返回
  5. disable_irq()/enable_irq()//必须成对使用,如果enable_irq()被调用多次,只有最有一次调用后,才会真正激活中断线;

一般来说禁止共享中断线是不合适的,这就禁止了这条线上的所有中断传递。

(3)中断系统状态

点击(此处)折叠或打开

  1. irqs_disabled();//本地处理器上的中断系统被禁止,则它返回非0,否则返回0.
  2. in_interrupt();//内核处于任何类型的中断中,返回非0,说明内核正在执行isr,或下半部处理程序
  3. in_irq();// 当内核确实正在执行中断处理程序时,返回非0

你可能感兴趣的:(Linux内核设计与实现(8)---中断和中断处理)