中断与中断处理句柄

目录

中断

中断句柄(处理机制)

注册一个中断玩

中断上下文

中断处理机制的实现

中断控制

禁止指定中断线


操作系统的一个重要的内容就是进行硬件的管理:比如说我现在写这边文章,cv其他博主的文章片段(划)。我就用到了至少明显的鼠标和键盘,我们的操作需要就需要处理这些设备发出来的中断。(可以直到的是这些操作都是异步的,换而言之,我的操作系统没法确切的知道,他只会突然被硬件或者是什么拍醒:嘿!处理我!)

中断

上面这一拍:即是中断。中断这一机制允许硬件向处理器报告自身有任务需要处理。比如说你现在正在使用键盘敲击东西或者是使用鼠标点击什么,都会引发一个外中断像超像处理器提示需要处理该事件。

同时还需要强调一点的是硬件引发了一个中断报告给处理器的时刻是随机的,处理器接收到了这个中断信号就需要交给上层的操作系统进行调度与处理。处理器自身只是知道有中断了,中断做什么的无从可知。

中断也是具有类型的!不同的中断应当被不同的标识出来。所以操作系统采用的是一种编号的方式来对中断进行标识,这样的一些中断的值经常被称为IRQ线。在经典的PC机器上,IRQ0被标记为时钟中断,IRQ1时键盘引发的中断。

中断句柄(处理机制)

为了响应中断,我们有一些独特的函数:Interrupt Handler来处理他们。 不是啥其他的函数,就是C的函数来处理这些中断。我们需要注意的是在接受一个中断的时候,我们的当前进程流将会陷入一种不同于内核态和用户态的“中断上下文”时期,在这一时期,中断是不可以被阻塞的(处于一种原子态)

为此,为了处理好中断,我们的中断处理分成了两大半:接受中断带来的数据通知上半段和基于这些通知做出相应处理的下半段。上半段的处理必须要快!举个例子:网卡接收数据时,内核必须尽快的将网卡中的数据拷贝到内核内存当中去。否则,下一个数据包来临时,将会把上一次接收的网卡的数据进行覆盖,从而导致了网络数据的丢失。

注册一个中断玩

设备可以注册一个中断处理器:

int request_irq(
    unsigned int irq, 
    irq_handle_t handler, 
    unsigned long flags, 
    const char* name, void* dev);
typedef irqreturn_t (*irq_handler_t)(int, void*); // handler函数的原型,接受两个参数,并有一个类型为irqreturn_t的返回值。

第一个参数irq表示要分配的中断号。对某些设备,如传统PC设备上的系统时钟或键盘,这个值通常是预先确定的。而对于大多数其他设备来说,这个值要么是可以通过探测获取,要么可以通过编程动态确定。

第二个参数handler是一个指针,指向处理这个中断的实际中断处理程序。只要操作系统接收到中断,该函数就被调用。

第三个参数flags可以为0,也可能是下列一个或多个标志的位掩码。其定义在<linux/interrupt.h>中。在这些标志中最重要的是:

IRQF_DISABLED:该标志被设置后,意味着内核在处理中断处理程序本身期间,要禁止所有的其它中断。如果不设置,中断处理程序可以与除本身外的其它任何中断同时运行。

IRQF_SAMPLE_RANDOM:此标志表明这个设备产生的中断对内核熵池有贡献。内核熵池负责提供从各种随机事件导出的真正地随机数。如果指定了该标志,那么来自该设备的中断间隔时间就会作为熵填充到熵池。如果你的设备以预知的速率产生中断(如系统定时器),或者可能受到外部攻击者(如联网设备)的影响,那么就不需要设置这个标志。

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

-IRQF_SHARED:此标志表明可以在多个中断处理程序之间共享中断线。在同一个给定线上注册的每个处理程序必须指定这个标志;否则,在每条线上只能有一个处理程序。

第四个参数name是与中断相关的设备的ASCII文本表示。如PC机上键盘中断对应的这个值为"keyboard"。

第五个参数dev用于共享中断线。当一个中断处理程序需要释放时,dev将提供唯一的标志信息,以便从共享中断线的诸多中断处理程序中删除指定的哪一个。如果没有这个参数,那么内核不可能知道在给定的中断线上到底要删除哪一个处理程序。如果无须共享中断线,那么将该参数赋为空值NULL就可以了,但是,如果中断线是被共享的,那就必须传递唯一的信息(除非设备又旧又破且位于ISA总线上,那么就必须支持共享中断)。另外,内核每次调用中断处理程序时,都会把这个指针传递给它。实践中往往会通过它传递驱动程序的设备结构:这个指针是唯一的,而且有可能在中断处理程序内被用到。

request_irq()函数成功执行会返回0.如果返回非0值,就表示有错误发生,在这种情况下,指定的中断处理程序不会被注册。最常见的错误是-EBUSY,它表示给定的中断线已经在使用(或者当前用户或者你没有指定IRQF_SHARED)。

request_irq()函数可能会睡眠,因此不能在中断上下文或其它不允许阻塞的代码中调用该函数。在注册的过程中,内核需要在/proc/irq文件中创建一个与中断对应的项。函数proc_mkdir()就是用来创建这个新的procfs项的。proc_mkdir()通过调用函数proc_create()对这个新的profs项进行设置,而proc_create()会调用函数kmalloc()来请求分配内存。

中断上下文

在接受一个中断的时候,我们的当执行一个中断处理程序时,内核处于中断上下文中(interrupt context)。而中断上下文和进程没有什么关系。与current宏也是不相干的(尽管它会指向被中断的进程)。因为没有后备进程,所以中断上下文不可以睡眠,否则又怎能再对它重新调度呢?因此,不能从中断上下文中调用某些函数。如果一个函数睡眠,就不能在你的中断处理程序中使用它---这是对什么样的函数可以在中断处理程序中使用的限制。

中断上下文具有严格的时间限制,因为它打断了其它代码。中断处理程序打断了其它代码(甚至可能是打断了在其它中断线上的另一个中断处理程序)。正是因为这种异步执行的特性,所以所有的中断处理程序必须尽可能地迅速、简洁。尽量把工作从中断处理程序中分离出来,放在下半部来执行,因为下半部可以在更合适的时间运行。

中断处理程序栈的设置是一个配置选项。曾经,中断处理程序并不具有自己的栈,而是共享所中断的进程的内核栈。内核栈大小两页,32位系统8KB,64位系统16KB,对空间的使用很节约。在2.6的早期版本中,增加了一个选项,把栈的大小从两页减少到一页,减轻了内存的压力(因为系统原来需要两页连续的且不可换出的内核内存)。为了应对栈大小的减小,中断处理程序拥有了自己的栈,每个处理器一个,大小为一页,这个栈就称为中断栈,尽管中断栈的大小是原先共享栈的一半,但平均可用栈空间大得多。

中断处理机制的实现

简而言之:设备产生中断,通过总线把电信号发送给中断控制器,如果中断线是激活的(它们是允许被屏蔽的),那么中断控制器就会把中断发往处理器。除非处理器上禁止该中断,否则处理器会立即停止它正在做的事,关闭中断系统,然后跳到内存中预定义的位置开始执行那里的代码。这个预定义的位置是由内核设置的,是中断处理程序的入口点。

在内核中,中断的旅程开始于预定义入口点,这类似于系统调用通过预定义的异常句柄进入内核。对于每条中断线,处理器都会跳到对应的一个唯一的位置。这样内核就可知道所接收中断的IRQ号了。初始入口点只是在栈中保存这个号,并存放当前寄存器的值(这些值属于被中断的任务)

然后,内核调用函数do_IRQ(),从这开始,检查该中断线上是否有中断处理程序:1-是则调用handle_IRQ_event(),接着在该线上运行所有中断处理程序(不符合设备的立即返回,符合设备dev的执行),接着调用ret_from_intr(),接着返回内核运行中断的代码;2-否则直接调用ret_from_intr(),再返回内核运行中断的代码。

我们可以在/proc/interrupts文件上看看我们的中断号分配。

输出了一大堆。该文件存放的是系统中与中断相关的统计信息。

中断控制

Linux内核提供了一组接口用于操作机器上的中断状态。这些接口为我们提供了能够禁止当前处理器的中断系统,或屏蔽掉整个机器的一条中断线的能力

这些例程都是与体系结构相关的,可以在<asm/system.h>和<asm/irq.h>中找到。(翻对于arch下的文件夹的文件)

一般来说,控制中断系统的原因归根结底是需要提供同步。通过禁止中断,可以确保某个中断处理程序不会抢占当前的代码。 此外,禁止中断还可以禁止内核抢占(禁止内核定时器的中断信号,定时器的执行程序就不会执行,所以不会更新进程的时间片)。然而,不管是禁止中断还是禁止内核抢占,都没有提供任何保护机制来防止来自其他处理器的并发访问。Linux支持多处理器,因此,内核代码一般都需要获取某种锁,防止来自其它处理器对共享数据的并发访问。获取这些锁的同时也伴随着禁止本地中断。锁提供保护机制防止来自其它处理器的并发访问;禁止中断提供保护机制防止来自其它中断处理程序的并发访问。

// 禁止和激活中断:
local_irq_disable();
// 禁止中断
local_irq_enable();

如果调用local_irq_disable()例程之前已经禁止了中断,那么该例程往往会带来潜在的风险;同样相应的local_irq_enable()例程也存在潜在的危险,因为它将无条c件地激活中断。

所以需要一种将中断恢复到以前的状态而不是简单地禁止或激活。

unsigned long flags;
local_irq_save(flags);
//禁止中断
local_irq_restore(flags);;  //恢复到之前的状态

禁止指定中断线

在某些情况下,只禁止整个系统中一条特定的中断线就够了,这就是屏蔽掉(masking out)一条中断线。

void disable_irq( unsigned int irq);
void disable_irq_nosync( unsigned int irq);
void enable_irq( unsigned int irq);
void synchronize_irq( unsigned int irq);

前两个函数禁止中断控制器上指定的中断线,即禁止给定中断向系统中所有处理器的传递。另外,函数只有在当前正在执行的所有处理程序完成后,disable_irq()才能返回,确保指定线上不再传递新的中断,同时还要确保所有已经开始执行的处理程序已经全部退出。函数disable_irq_nosync()不会等待当前中断处理程序执行完毕。

函数synchronize_irq()等待一个特定的中断处理程序的退出。如果该处理程序正在执行,那么该函数必须退出后才能返回。

对这些函数的调用可以嵌套。但在一条指定的中断线上,对disable_irq()或disable_irq_nosync()的每次调用,都需要相应地调用一次enable_irq()。只有在对enable_irq()完成最后一次调用后,才真正激活了中断线。

所有这三个函数可以从中断或进程上下文中调用,而且不会睡眠。

禁止多个中断处理程序共享的中断线是不合适的,这会禁止这条线上所有设备的中断传递。

Reference

Linux内核7-中断和中断处理(上半部) - 吉吉boy - 博客园 (cnblogs.com)

  • 9
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值