Linux内核设计与实现---中断和中断处理程序

中断和中断处理程序

  • 1 中断
    • 异常
  • 2 中断处理程序
    • 上半部与下半部的对比
  • 3 注册中断处理程序
    • 释放中断处理程序
  • 4 编写中断处理程序
      • 重入和中断处理程序
    • 共享的中断处理程序
    • 中断处理程序实例
  • 5 中断上下文
  • 6 中断处理机制的实现
  • 7 中断控制
    • 禁止和激活中断
    • 禁止指定中断线
    • 中断系统的状态
  • 8 总结

Linux内核要对连接到计算机上的所有硬件设备进行管理,要与它们进行通信。但是,处理器的速度跟外围硬件设备的速度往往不是一个数量级的,硬件的响应很慢,内核应该在此期间处理其他事物,等到硬件真正完成了请求的操作之后,再回过头来对它进行处理。中断机制让硬件在需要的时候再向内核发出信号,内核来处理硬件的请求。

1 中断

中断使得硬件得以与处理器进行通信。硬件设备生成中断的时候并不考虑与处理器的时钟同步,换句话说,中断随时都可以产生,因此,内核随时可能因为新到来的中断而被打断。

从物理学的角度看,中断是一种电信号,由硬件设备生成,并直接送入中断控制器的输入引脚上,然后再由中断控制器向处理器发送相应的信号。处理器一经检测到此信号,便中断自己的当前工作转而处理中断。此后,处理器会通知操作系统已经产生中断,这样,操作系统就可以对这个中断进行适当的处理了。

不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标识,这些中断值通常被称为中断请求(IRQ)线。特定的中断总是与特定的设备相关联,并且内核要知道这些消息。

异常

异常与中断不同,它在产生时必须考虑与处理器时钟同步。异常也常称为同步中断。在处理器执行到由于编程失误而导致的错误指令的时候,或者是在执行期间出现特殊情况(例如缺页),必须靠内核来处理的时候,处理器就会产生一个异常。

2 中断处理程序

在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序或中断服务例程(interrupt service routine,ISR)。产生中断的每个设备都有一个相应的中断处理程序。中断处理程序通常不是和特定设备关联,而是和特定中断关联的,也就是说,如果一个设备可以产生多种不同的中断,那么该设备就可以对应多个中断处理程序,相应的,该设备的驱动程序也就需要准备多个这样的函数。

在Linux中,中断处理程序看起来就是普普通通的C函数,只不过这些函数必须按照特定的类型声明,以便内核能够以标准的方式传递程序的信息。中断处理程序与其他内核函数的真正区别在于:中断处理程序是被内核调用来响应中断的,而它们运行在我们称之为中断上下文的特殊上下文中。

上半部与下半部的对比

一般把中断处理切为两个部分,中断处理程序是上半部,接受到一个中断,它就立即开始执行,但只做有严格时限的工作,例如对接受的中断进行应答或复位硬件,这些工作都是在所有中断被禁止的情况下完成的。能够被允许稍后完成的工作会被推迟到下半部去。此后,在合适的时机,下半部就开中断执行,Linux提供了实现下半部的各种机制,下一篇文章会讨论,现在我们要记住上半部是中断处理程序,只做有严格时限的工作。

3 注册中断处理程序

中断处理程序是驱动程序的组成部分。每一设备都有相关的驱动程序,如果设备使用中断,那么相应的驱动程序就注册一个中断处理程序。

驱动程序可以通过下面的函数注册并激活一个中断处理程序:

/* request_irq分配一个中断线 */
int request_irq(unsigned int irq,irqreturn_t (*handler)(int,void *,struct pt_regs *),unsigned long irqflags,const char * devname,void *dev_id)

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

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

第三个参数irqflags可以为0,也可能是下列一个或多个标志的位掩码:

  • SA_INTERRUPT:此标志表明给定的中断处理程序是一个快速中断处理程序。在本地处理器上,快速中断处理程序在禁止所有中断的情况下运行
  • SA_SAMPLE_RANDOM:此标志表明这个设备产生的中断对内核熵池有贡献。内核熵池负责提供从各种随机事件导出的真正的随机数。
  • SA_SHIRQ:此标志表明可以在多个中断处理程序之间共享中断线。

第四个参数devname是给中断相关的设备起的名字。这些名字会被/proc/irq和/proc/Interrupt文件使用,以便与用户通信。

第五个参数dev_id主要用于共享中断线。如果无需共享中断线,那么将该参数赋值为NULL就可以了,但是,如果中断线是被共享的,那么必须传递唯一的信息,用来区分用的是共享中断线上的那个中断处理程序。实践中往往会通过它传递驱动程序的设备结构。

request_irq()函数可能会睡眠,因此,不能在中断上下文或其他不允许阻塞的代码中用该函数,至于request_irq()会睡眠,那是在注册的过程中,内核需要在/proc/irq文件创建一个中断对应的项,会调用kmalloc()来请求分配内存,函数kmalloc()是可以睡眠的。

在一个驱动程序中请求一个中断线,并在通过request_irq()注册中断处理程序:

if(request_irq(irqn,my_interrupt,SA_SHIRQ,"my_device",dev))
{printk(KERN_ERR "my_device:connot register IRQ %d \n",irqn);return -RIO;
}

irqn是请求的中断线,my_interrupt是中断处理程序,中断线可以共享,设备名为"my_device",而且我们通过dev_id传递dev结构体。

释放中断处理程序

卸载驱动程序时,需要注销相应的中断处理程序,并释放中断线。可以调用void free_irq(unsigned int irq,void *dev_id)来释放中断线。
如果指定的中断线不是共享的,那么,该函数删除中断处理程序的同时将禁用这条中断线。如果中断线是共享的,则仅删除dev_id所对应的处理程序,而这条中断线本身只有在删除了最后一个程序程序时才会被禁用。

4 编写中断处理程序

以下是一个典型的中断处理程序声明:

static irqreturn_t intr_handler(int irq,void *dev_id,struct pt_regs *regs);

它的类型与request_irq()参数中handler所要求的参数类型相匹配。第一个参数irq就是这个中断处理程序要响应的中断的中断线号。第二个参数dev_id是用一个通用指针,它与中断处理程序注册时传递给request_irq()的采纳数dev_id必须一致,可以用来区分共享同一个中断处理程序的多个设备,最后一个参数regs是一个指向结构的指针,该结构包含处理中断之前处理器的寄存器和状态。除了调试的时候,它们很少使用到。

中断处理程序的返回值是一个特殊类型:irqreturn_t。中断处理程序可能返回两个特殊的值,IRQ_NONE和IRQ_HANDLED。当中断处理程序检测到一个中断,但该中断对应的设备并不是在注册处理函数期间指定的产生源时,返回IRQ_NONE;当中断处理程序被正确调用,且确实是它所对应的设备产生的中断,返回IRQ_HANDLED。

重入和中断处理程序

Linux中的中断处理程序是无需重入的。当一个给定的中断处理程序在执行时,相应的中断线在所有处理器上都会被屏蔽掉,以防止在同一中断线上接受到另一个新的中断。

共享的中断处理程序

共享中断线的处理程序和非共享中断线的处理程序在注册和运行方式上比较相似,但主要有以下差异:

  • request_irq()的flags参数必须设置SA_SHIRQ标志
  • 对每个注册的中断处理程序来说,dev_id必须是唯一的。指向任意设备结构的指针就可以满足这一要求,通常会选择设备结构,因为它是唯一的,而且中断处理程序可能会用到它。不能共享就传NULL
  • 中断处理程序必须能区分它的设备是否真的产生了中断,

中断处理程序实例

让我们考察一个实际的中断处理程序,它来自RTC(real_time clock)驱动程序,可以在drivers/char/rtc.c中找到。RTC驱动程序装载时,rtc_init()函数会被调用,对这个驱动程序进行初始化。它的职责之一就是注册中断处理程序:

/** XXX Interrupt pin #7 in Espresso is shared between RTC and* PCI Slot 2 INTA# (and some INTx# in Slot 1). SA_INTERRUPT here* is asking for trouble with add-on boards. Change to SA_SHIRQ.*/if (request_irq(rtc_irq, rtc_interrupt, SA_INTERRUPT, "rtc", (void *)&rtc_port)) {/** Standard way for sparc to print irq's is to use* __irq_itoa(). I think for EBus it's ok to use %d.*/printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq);return -EIO;}

中断线号由rtc_irq指定,rtc_interrupt是我们的中断处理程序,驱动程序的名称为rtc,因为这个设备不允许共享中断线,且处理程序没有用到什么特殊的值,因此给dev_id的值是NULL。

5 中断上下文

当执行一个中断处理程序或下半部时,内核处于中断上下文中。进程上下文是一种内核所处的操作模式,此时内核代表进程执行,在进程上下文中,可以通过current宏关联到当前进程,此外,因为进程是以进程上下文的形式连接到内核的,因此,在进程上下文可以睡眠,也可以调用调度程序。

中断上下文和进程并没什么瓜葛。因为没有进程的背景,所有中断上下文不可睡眠

中断处理程序栈的设置是一个配置选项。它们共享所属中断进程的内核栈。内核栈的大小是两页。因为这种设置,中断处理程序共享别人的堆栈,所以它们从栈中获取内容非常节省空间。

6 中断处理机制的实现

中断处理在linux中的实现非常依赖体系结构,实现依赖于处理器、所使用的中断控制器的类型、体系结构的设计以及机器本身。
下图是中断从硬件到内核的步骤。
在这里插入图片描述
设备产生中断,通过总线把电信号发送给中断控制器。如果中断线是激活的,那么中断控制器就会把中断发往处理器。在大多数体系结构中,这个工作就是通过电信号给处理器的特定管脚发送一个信号。除非在处理器禁止该中断,否则,处理器会立即停止它正在做的事,关闭中断系统,然后跳到中断处理程序的入口点去运行。

对于每条中断线,处理器都会跳到对应的唯一的位置。这样,内核就可知道所接收中断的IRQ号了。入口点只是在栈中保存这个号,并存当前寄存器的值;然后,内核调用函数do_IRQ()。

do_IRQ()的声明如下:

unsigned int do_IRQ(struct pt_regs regs);

因为C的调用惯例是要把函数参数放在栈的顶部,因此pt_regs结构包含原始寄存器的值,这些值是以前在汇编入口例程中保存在栈的的,中断的值也会得到保存,所以,do_IRQ()可以将它提取出来。

计算出中断号后,do_IRQ()对所接收的中断进行应答,禁止这条线上的中断传递。do_IRQ()需要确保在这条中断线上有一个有效的处理程序,而且这个程序已经启动但是当前没有执行。如果是这样的话,do_IRQ()就调用handle_IRQ_event()来运行为这条中断线所安装的中断处理程序,在kernel/irq/handle.c文件中

/** Have got an event to handle:*/
fastcall int handle_IRQ_event(unsigned int irq, struct pt_regs *regs,struct irqaction *action)
{int ret, retval = 0, status = 0;if (!(action->flags & SA_INTERRUPT))local_irq_enable();do {ret = action->handler(irq, action->dev_id, regs);if (ret == IRQ_HANDLED)status |= action->flags;retval |= ret;action = action->next;} while (action);if (status & SA_SAMPLE_RANDOM)add_interrupt_randomness(irq);local_irq_disable();return retval;
}

7 中断控制

Linux内核提供了一组接口用于操作机器上的中断状态。这些接口为我们提供了禁止当前处理器的中断系统,或屏蔽掉整个机器的一条中断线的能力,这些接口是与体系有关的,可以在<asm/system.h>和<asm/irq.h>中找到。

禁止和激活中断

用于禁止当前处理器(仅仅是当前处理器)上的本地中断,随后又激活它们的语句是:

local_irq_disable();
local_irq_enable();

这两个函数通常以单个汇编指令来实现(取决于体系结构)。实际上,在x86中,local_irq_disable()仅仅是cli指令,而local_irq_enable()只不过是sti指令。

local_irq_disable是禁止当前处理器上的所有中断,local_irq_enable是开启当前处理器上的所有中断,即使在禁止前有些中断是关闭的,在调用local_irq_enable()后也会被激活。所以我们需要一种机制把中断恢复到以前的状态而不是简单地禁止或激活。在禁止之前保存当前的中断系统,激活时恢复就行。

unsigned long flags;
lcoal_irq_save(flags)
local_irq_restore(flags);

flags必须是unsigned long类型。local_irq_save () 是保存本地中断传递的当前状态,然后禁止本地中断传递。local_irq_restore () 是恢复本地中断到flags的状态。

禁止指定中断线

在某些情况下,只禁止整个系统中一条特定的中断线就够了。为此,Linux提供了四个接口:

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禁止中断控制器上指定的中断线,即禁止给定中断向系统中所有处理器的传递。我们来看看disable_irq的具体实现:

/***	disable_irq - disable an irq and wait for completion*	@irq: Interrupt to disable**	Disable the selected interrupt line.  Enables and disables*	are nested.  This functions waits for any pending IRQ*	handlers for this interrupt to complete before returning.*	If you use this function while holding a resource the IRQ*	handler may need you will deadlock.**	This function may be called - with care - from IRQ context.*/
void disable_irq(unsigned int irq)
{struct irqdesc *desc = irq_desc + irq;disable_irq_nosync(irq);if (desc->action)synchronize_irq(irq);
}

此函数在返回之前等待中断irq的任何挂起的 中断处理程序完成。disable_irq会阻塞,所以不能在在中断处理函数中使用,如果在中断处理程序中使用,会导致死锁,电脑会死机。

disable_irq_nosync不会阻塞,会立即返回,可在中断处理函数中使用。

enable_irq函数的参数是int型变量,代表操作中断对应的中断号

函数synchronize_irq等待一个特定的中断处理程序的退出,所以该函数也会阻塞。

这些函数的调用都可以嵌套,但要记住在一条指定的中断线上,对disable_irq()或disable_irq_nosync()的每次调用,都需要相应地调用一次enable_irq()。只有在对enable_irq()完成最后一次调用后,才真正重新激活了中断线。例如,如果disable_irq()被调用了两次,那么直达第二次调用enable_irq()后,才能真正地激活中断线。

禁止多个中断处理程序共享的中断线是不合适的。禁止中断线也就禁止了这条线上所有设备的中断传递。因此,用于新设备的驱动程序应该尽量不使用这些接口。

中断系统的状态

宏irqs_disable()定义在<asm/system.h>中,如果本地处理器上的中断系统被禁止,则它返回非0,否则返回0.
在<asm/hardirq.h>中定义了的两个宏提供了一个用来检查内核的当前上下文的接口,它们是:

in_interrupt();
in_irq();

in_interrupt用来检测内核是否处于中断上下文中,如果是的话,返回非0。说明此刻内核正在执行中断处理程序,或者正在执行下半段处理程序,如果返回0,此时内核处于进程上下文中。宏in_irq只有在内核确实正在执行中断处理程序时才返回非0.

8 总结

中断就是由硬件打断操作系统。内核提供的接口包括注册和注销中断处理程序,禁止中断,屏蔽中断线,以及检查中断系统的状态。
因为中断打断了其他代码的执行(进程,内核本身,甚至其他中断处理程序),它们必须赶快执行完。为了大量的工作与必须快速执行完之间求得平衡,内核把处理中断的工作分为两部分。中断处理程序,也就是上半部,下半部我们还没有讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/379661.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

response细节点

一、 1&#xff09;、response获得的流不需要手动关闭&#xff0c;Tomcat容器会帮你自动关闭 2&#xff09;、getWriter和getOutputStream不能同时调用 //error package com.itheima.content;import java.io.IOException; import javax.servlet.ServletException; import java…

linux内核设计与实现---下半部和推后执行的工作

下半部和推后执行的工作1 下半部为什么要用下半部下半部的环境内核定时器2 软中断软中断的实现软中断处理程序执行软中断使用软中断3 tasklettasklet的实现使用taskletksoftirqd4 工作队列工作队列的实现工作、工作队列和工作者线程之间的关系使用工作队列5 下半部机制的选择6 …

Mac VSCode配置C语言环境(可以调试)

Mac VSCode配置C语言环境c_cpp_properties.jsontasks.jsonlaunch.json新建一个文件夹&#xff0c;用vscode&#xff0c;然后再新建一个test.c文件。 #include <stdio.h>int main(void) {int a1,b1;int cab;printf("%d\n",c);return 0; }这篇文章说怎么配置c_c…

vShpere Client在win 7 RC下和2008下 无法正常连接esx主机之解决办法

vShpere Client在win 7 RC下和2008下 无法正常连接esx主机之解决办法 在win7下和2008下打开client后连接esx主机会出现2个错误提示, 第一个是 第二个是 然后就连接失败了,开始以为是CC的esx主机安装有问题,后来找了找,借助了强大google工具,终于找到解决办法.解决办法如下: 1.从…

localhost与127.0.0.1之间的关系更改

其实localhost的默认IP地址为127.0.0.1&#xff0c;因为这是一种映射关系。 更改步骤如下&#xff1a; C:\Windows\System32\drivers\etc 下的hosts 打开hosts可以看到 更改即可

Linux内核设计与实现---内核同步方法

内核同步方法1 原子操作原子整数操作原子性与顺序性的比较原子位操作2 自旋锁自旋锁是不可递归的其他针对自旋锁的操作自旋锁和下半部3 读-写自旋锁4 信号量创建和初始化信号量使用信号量5 读-写信号量6 自旋锁和信号量7 完成变量8 互斥锁互斥锁API9 禁止抢占10 顺序和屏障1 原…

UNIX环境高级编程---进程间通信总结

进程间通信1 管道匿名管道命名管道2 消息队列3 信号量POSIX信号量有名信号量无名信号量有名信号量和无名信号量的公共操作4 共享内存5 信号相关函数6 套接字针对 TCP 协议通信的 socket 编程模型针对 UDP 协议通信的 socket 编程模型针对本地进程间通信的 socket 编程模型总结L…

搜索---广度优先遍历、深度优先遍历、回溯法

参考文章&#xff1a;https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E6%90%9C%E7%B4%A2.md 广度优先搜索&#xff08;BFS&#xff09; 广度优先搜索是按层来处理顶点的&#xff0c;距离开始点最近的那些顶点首先被访问&#…

如何更改Visual Studio 2008中类文件引用的默认名称空间?

在编写程序的时候&#xff0c;如果某些名称空间经常用到&#xff0c;每次创建一个文件的时候&#xff0c;都需要手工添加名称空间&#xff0c;是不是很烦人呢&#xff1f;多说人会回答&#xff1a;是的。如果新建文件的时候就自动加上自己需要的名称空间该多好啊。&#xff1a;…

Linux内核设计与实现---内存管理

内存管理1 页2 区3 获得页获得填充为0的页释放页4 kmalloc()gfp_mask标志kfree()5 vmalloc()6 slab层slab层的设计7 slab分配器的接口8 在栈上的静态分配9 高端内核的映射永久映射临时映射10 每个CPU的分配11 新的每个CPU的接口编译时的每个CPU数据运行时每个CPU数据12 使用每个…

多语言开发 之 通过基页类及Session 动态响应用户对语言的选择

在用户通过UserLogin.aspx登录系统时 提供其对语言的选择选择后 将所选存入Session 以便登录系统后的其他页面进行按语言显示当然相关页面需要支持多语言具体信息可参看使用 根据语言环境不同 而显示不同的 资源本地化 ASP.NET 网页 App_Code下定义基页类 BasePage.cs Codeusin…

Linux内核设计与实现---虚拟文件系统

虚拟文件系统1 通用文件系统2 文件系统抽象层3 Unix文件系统4 VFS对象及其数据结构其他VFS对象5 超级快对象超级块操作6 索引节点对象索引节点操作7 目录项对象目录项状态目录项缓存目录项操作8 文件对象9 和文件系统相关的数据结构10 和进程相关的数据结构11 Linux中的文件系统…

Java里面的几种路径的区别

1&#xff0c;相对路径 相对路径就是指由这个文件所在的路径引起的跟其它文件&#xff08;或文件夹&#xff09;的路径关系。 也就是说&#xff1a; 对于如图所示&#xff1a;一news.html为例 在WEB15工程下的WebContent下的WEB-INF下的news.html 当我访问的news.html的时候…

Linux内核设计与实现---块I/O层

块I/O层1 解刨一个块设备2 缓冲区和缓冲区头3 bio结构体新老方法对比4 请求队列5 I/O调度程序I/O调度程序的工作Linus电梯最终期限I/O调度程序预测I/O调度程序完全公正的排队I/O调度程序空操作的I/O调度程序I/O调度程序的选择系统中能够 随机访问 固定大小数据片的设备被称为块…

算法---数

数1 最大公约数2 最小公约数3 进制转换4 阶乘统计阶乘尾部0的个数5 字符串加法减法二进制加法6 多数投票问题数组中出现次数多于n/2的元素7 相遇问题改变数组元素使所有元素都相同1 最大公约数 欧几里得算法&#xff1a;两个整数的最大公约数等于其中较小的那个数和两数相除余…

Linux内核设计与实现---进程地址空间

进程地址空间1 内存描述符分配内存描述符销毁内存描述符mm_struct与内核线程2 内存区域VMA标志VMA操作内存区域的树形结构和内存区域的链表结构3 操作内存区域find_vma()find_vma_prev()find_vma_intersection()4 mmap()和do_mmap()&#xff1a;创建地址空间mmap&#xff08;&a…

JavaScript中带有示例的Math.log10()方法

JavaScript | Math.log10()方法 (JavaScript | Math.log10() Method) Math operations in JavaScript are handled using functions of math library in JavaScript. In this tutorial on Math.log10() method, we will learn about the log10() method and its working with e…

JSP技术

一、jsp脚本和注释 jsp脚本&#xff1a; 1&#xff09;<%java代码%> ----- 内部的java代码翻译到service方法的内部 2&#xff09;<%java变量或表达式> ----- 会被翻译成service方法内部out.print() 3&#xff09;<%!java代码%> ---- 会被翻译成servlet的成…

EL技术

1&#xff0e;EL 表达式概述 EL&#xff08;Express Lanuage&#xff09;表达式可以嵌入在jsp页面内部&#xff0c;减少jsp脚本的编写&#xff0c;EL 出现的目的是要替代jsp页面中脚本的编写。 2&#xff0e;EL从域中取出数据(EL最重要的作用) jsp脚本&#xff1a;<%requ…

SVN+AnkhSVN端配置

对于ankhSVN我想很多人不陌生&#xff0c;因为经常使用&#xff0c;但是我还是发现很多人并不怎么会配置&#xff0c;或者完全不知道其需要配置&#xff0c;如果不配置的话&#xff0c;当两个人同时需要修改某个文件的时候就容易中弹了。SVN默认是不支持“锁定-编辑-解锁”的&a…