第 3 章 中断和中断处理

115
3 3 第 第第第第第第第 第 第第第第第第第 第第第第第第第 第第第第 第第第第第第第第第第 第第第第第第第第第第第第 第第第第第第第 第第第第 第第第第第第第第第第 第第第第第第第第第第第第 第第第第 第第第第第第第第第第第第第第第 第第第第第第 第第第第第 第第第第第第第 体。,、、 第第第第 第第第第第第第第第第第第第第第 第第第第第第 第第第第第 第第第第第第第 体。,、、 第第第第第第 第第第 第第第第第第第 第第第第第第第 第第第第 。,一。 第第第第第第 第第第 第第第第第第第 第第第第第第第 第第第第 。,一。 Linux Linux 第第第第第第第第第第第第第第第第第第 第第第 第第第第第第第第第第第第第第第第第第 第第第 kernel 2.4 kernel 2.4 ker ker nel 2.2.x nel 2.2.x 第第第第第第第第第第第第 第第第第第第第第第第第第第 第第第第第第第第第第第第 第第第第第第第第第第第第第 kernel kernel 2.4 2.4 第第第第第第第第第第第第第第第第

Upload: misu

Post on 15-Jan-2016

104 views

Category:

Documents


0 download

DESCRIPTION

第 3 章 中断和中断处理. 硬件中断机制是一个操作系统内核中非常重要的部分。它的设计直接影响到操作系统整体的性能。它与硬件平台和内核的其它部分,如内存管理、进程调度、设备驱动等都有很密切的关系。因此,它也是操作系统中比较复杂的一个模块。 Linux 的硬件中断机制的设计有很多独到之处,本章把 kernel 2.4 和 kernel 2.2.x 的相关机制进行详细的对比,使读者能够更好的领会最新的 kernel 2.4 中的硬件中断机制。. 3.1 硬件提供的中断机制和约定. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第  3  章 中断和中断处理

第 第 3 3 章 中断和中断处理章 中断和中断处理 硬件中断机制是一个操作系统内核中非常重要的部分。它硬件中断机制是一个操作系统内核中非常重要的部分。它

的设计直接影响到操作系统整体的性能。它与硬件平台和的设计直接影响到操作系统整体的性能。它与硬件平台和内核的其它部分,如内存管理、进程调度、设备驱动等都内核的其它部分,如内存管理、进程调度、设备驱动等都有很密切的关系。因此,它也是操作系统中比较复杂的一有很密切的关系。因此,它也是操作系统中比较复杂的一个模块。个模块。

LinuxLinux 的硬件中断机制的设计有很多独到之处,本章把的硬件中断机制的设计有很多独到之处,本章把 kekernel 2.4rnel 2.4 和和 kernel 2.2.xkernel 2.2.x 的相关机制进行详细的对比,使的相关机制进行详细的对比,使读者能够更好的领会最新的读者能够更好的领会最新的 kernel 2.4kernel 2.4 中的硬件中断机制。中的硬件中断机制。

Page 2: 第  3  章 中断和中断处理

3.1 3.1 硬件提供的中断机制和约定 硬件提供的中断机制和约定 硬中断即和硬件相关的中断也就是通常意义上的“中断处硬中断即和硬件相关的中断也就是通常意义上的“中断处

理程序”,它是直接处理由硬件发过来的中断信号的。当理程序”,它是直接处理由硬件发过来的中断信号的。当某个设备发出中断请求时,某个设备发出中断请求时, CPUCPU 停止正在执行的指令,停止正在执行的指令,转而跳到包括中断处理代码或者包括指向中断处理代码的转而跳到包括中断处理代码或者包括指向中断处理代码的转移指令所在的内存区域。这些代码一般在转移指令所在的内存区域。这些代码一般在 CPUCPU 的中断的中断方式下运行。就回去自己驱动的设备上去看看设备的状态方式下运行。就回去自己驱动的设备上去看看设备的状态寄存器以了解发生了什么事情,并进行相应的操作。当中寄存器以了解发生了什么事情,并进行相应的操作。当中断处理完毕以后,断处理完毕以后, CPUCPU 将恢复到以前的状态,继续执行将恢复到以前的状态,继续执行中断处理前正在执行的指令。 中断处理前正在执行的指令。

中断的流程如图中断的流程如图 3.13.1 所示。所示。

Page 3: 第  3  章 中断和中断处理

3.1 3.1 硬件提供的中断机制和约定硬件提供的中断机制和约定 图图 3.1 3.1 中断流程中断流程

大多数处理器在处理中断过程方式下将不会再有中断发生。但有大多数处理器在处理中断过程方式下将不会再有中断发生。但有些些 CPUCPU 的中断有自己的优先权,更高优先权的中断则可以发生。的中断有自己的优先权,更高优先权的中断则可以发生。这意味着第一级的中断处理程序必须拥有自己的堆栈,以便在处这意味着第一级的中断处理程序必须拥有自己的堆栈,以便在处理更高级别的中断前保存理更高级别的中断前保存 CPUCPU 的执行状态。 的执行状态。

Page 4: 第  3  章 中断和中断处理

3.1 3.1 硬件提供的中断机制和约定硬件提供的中断机制和约定 LinuxLinux 系统是包含内核、系统工具、完整的开发环境和应系统是包含内核、系统工具、完整的开发环境和应

用的类用的类 UnixUnix 操作系统。这个系统是由全世界各地的成千操作系统。这个系统是由全世界各地的成千上万的程序员设计和实现的。上万的程序员设计和实现的。 19841984 年,年, Richard StallmRichard Stallmanan 创立了创立了 GNUGNU 工程,其目标是开发一个完全免费的类工程,其目标是开发一个完全免费的类 UUnixnix 系统及其应用程序。系统及其应用程序。 19911991 年,芬兰赫尔辛基大学一年,芬兰赫尔辛基大学一位名叫位名叫 Linus TorvaldsLinus Torvalds的学生开始了开放源代码的的学生开始了开放源代码的 LinuxLinux雏形的设计。其目的是建立不受任何商品化软件的版权制雏形的设计。其目的是建立不受任何商品化软件的版权制约的、全世界都能自由使用的约的、全世界都能自由使用的 UnixUnix兼容产品兼容产品

由于由于 LinuxLinux 是一套具有是一套具有 UnixUnix 全部功能的免费操作系统,全部功能的免费操作系统,它在众多的软件中占有很大的优势,为广大的计算机爱好它在众多的软件中占有很大的优势,为广大的计算机爱好者提供了学习、探索以及修改计算机操作系统内核的机会者提供了学习、探索以及修改计算机操作系统内核的机会

Page 5: 第  3  章 中断和中断处理

3.1.1 3.1.1 中断产生的过程中断产生的过程 CPU CPU 在一些外部硬件的帮助下处理中断。中断处理硬件在一些外部硬件的帮助下处理中断。中断处理硬件

和具体的系统相关,但一般来说,这些硬件系统和 和具体的系统相关,但一般来说,这些硬件系统和 i386 i386 处理器的中断系统在功能上是一致的。处理器的中断系统在功能上是一致的。

图图 3.2 i386 PC 3.2 i386 PC 可编程中断控制器可编程中断控制器 8259A8259A级链示意图 级链示意图

Page 6: 第  3  章 中断和中断处理

3.1.1 3.1.1 中断产生的过程中断产生的过程 对于中断,对于中断, CPUCPU只提供两条外接引线:只提供两条外接引线: NMINMI 和和 INTRINTR;这里的;这里的

中断线是实际存在的电路,它们通过硬件接口连接到中断线是实际存在的电路,它们通过硬件接口连接到 CPUCPU外的外的设备控制器上。设备控制器上。 NMINMI 只能通过端口操作来屏蔽,它通常用于电只能通过端口操作来屏蔽,它通常用于电源掉电和物理存储器奇偶验错;源掉电和物理存储器奇偶验错; INTRINTR 可通过直接设置中断屏可通过直接设置中断屏蔽位来屏蔽,它可用来接受外部中断信号。蔽位来屏蔽,它可用来接受外部中断信号。 INTRINTR只有一条引只有一条引线,为更好的处理外部设备,线,为更好的处理外部设备, x86x86 微机通过外接两片级连了可微机通过外接两片级连了可编程中断控制器编程中断控制器 8259A8259A,以接受更多的外部中断信号。每个,以接受更多的外部中断信号。每个 88259A259A中断控制器可以管理中断控制器可以管理 88 条中断线,当两个条中断线,当两个 82598259 级联的时级联的时候共可以控制候共可以控制 1515 条中断线。在图条中断线。在图 3.23.2 表示了两个级联的中断控表示了两个级联的中断控制器,从属中断控制器的输出连接到了主中断控制器的第 制器,从属中断控制器的输出连接到了主中断控制器的第 3 3 个个中断信号输入,这样,该系统可处理的外部中断数量最多可达 中断信号输入,这样,该系统可处理的外部中断数量最多可达 15 15 个。图的右边是 个。图的右边是 i386 PC i386 PC 中各中断输入管脚的一般分配。中各中断输入管脚的一般分配。可通过对可通过对 8259A8259A的初始化,使这的初始化,使这 1515 个外接引脚对应个外接引脚对应 256256 个中个中断向量的任何断向量的任何 1515 个连续的向量。设备通过中断线向中断控制器个连续的向量。设备通过中断线向中断控制器发送高电平告诉操作系统它产生了一个中断,而操作系统会从发送高电平告诉操作系统它产生了一个中断,而操作系统会从中断控制器的状态位知道是哪条中断线上产生了中断。中断控制器的状态位知道是哪条中断线上产生了中断。

Page 7: 第  3  章 中断和中断处理

3.1.1 3.1.1 中断产生的过程中断产生的过程 8259A8259A 主要完成中断优先级排队管理、接受外部中断请求和向主要完成中断优先级排队管理、接受外部中断请求和向 CPUCPU

提供中断类型号这样一些任务。提供中断类型号这样一些任务。

由于由于 IntelIntel 公司保留公司保留 0-310-31 号中断向量用来处理异常事件,所以,硬中号中断向量用来处理异常事件,所以,硬中断必须设在断必须设在 3131 以后,以后, LinuxLinux 则在实模式下初始化时把硬中断设在则在实模式下初始化时把硬中断设在 0x0x20-0x2F20-0x2F。。

外部设备产生的中断实际是电平的变化信号,外部设备产生的中断信外部设备产生的中断实际是电平的变化信号,外部设备产生的中断信号在号在 IRQIRQ (中断请求)管脚上,这一信号首先由中断控制器处理。中(中断请求)管脚上,这一信号首先由中断控制器处理。中断控制器可以响应多个中断输入,它的输出连接到 断控制器可以响应多个中断输入,它的输出连接到 CPU CPU 的 的 INT INT 管管脚,脚, CPU CPU 在该管脚上的电平变化可通知处理器产生了中断。如果 在该管脚上的电平变化可通知处理器产生了中断。如果 CCPU PU 这时可以处理中断,这时可以处理中断, CPU CPU 会通过 会通过 INTAINTA (中断确认)管脚上的(中断确认)管脚上的信号通知中断控制器已接受中断,这时,中断控制器可将一个 信号通知中断控制器已接受中断,这时,中断控制器可将一个 8 8 位位数据放置在数据总线上,这一 数据放置在数据总线上,这一 8 8 位数据也称为中断向量号,位数据也称为中断向量号, CPU CPU 依依据中断向量号和中断描述符表(据中断向量号和中断描述符表( IDTIDT )中的信息自动调用相应的中断)中的信息自动调用相应的中断服务程序。 服务程序。

Page 8: 第  3  章 中断和中断处理

3.1.1 3.1.1 中断产生的过程中断产生的过程 中断控制器中的控制寄存器实际映射到了 中断控制器中的控制寄存器实际映射到了 CPU CPU 的 的 I/O I/O 地地址空间中,通过对寄存器的设置,可设定中断控制器屏蔽址空间中,通过对寄存器的设置,可设定中断控制器屏蔽某些中断,也可以指定中断控制器的特殊响应方式,因此,某些中断,也可以指定中断控制器的特殊响应方式,因此,中断控制器也称为可编程中断控制器。在 中断控制器也称为可编程中断控制器。在 Linux Linux 中,两中,两个中断控制器初始设置为固定优先级的中断响应方式。有个中断控制器初始设置为固定优先级的中断响应方式。有关可编程控制器的详细信息可参阅有关的资料。关可编程控制器的详细信息可参阅有关的资料。

中断处理程序得知设备发生了一个中断,但并不知道设备中断处理程序得知设备发生了一个中断,但并不知道设备发生了什么事情,只有在访问了设备上的一些状态寄存器发生了什么事情,只有在访问了设备上的一些状态寄存器以后,才能知道具体发生了什么,要怎么去处理。以后,才能知道具体发生了什么,要怎么去处理。

Page 9: 第  3  章 中断和中断处理

3.1.2 3.1.2 中断请求中断请求 设备只有对某一条确定的中断线拥有了控制权,才可以向这条中断线设备只有对某一条确定的中断线拥有了控制权,才可以向这条中断线

上发送信号。由于计算机的外部设备越来越多,所以上发送信号。由于计算机的外部设备越来越多,所以 1515 条中断线已条中断线已经不够用了。要使用中断线,就得进行中断线的申请,就是经不够用了。要使用中断线,就得进行中断线的申请,就是 IRQ(InteIRQ(Interrupt Requirement)rrupt Requirement),也常把申请一条中断线称为申请一个,也常把申请一条中断线称为申请一个 IRQIRQ 或或者是申请一个中断号。 者是申请一个中断号。

IRQIRQ 是非常宝贵的,所以建议只有当设备需要中断的时候才申请占用是非常宝贵的,所以建议只有当设备需要中断的时候才申请占用一个一个 IRQIRQ ,或者是在申请,或者是在申请 IRQIRQ 时采用共享中断的方式,这样可以让时采用共享中断的方式,这样可以让更多的设备使用中断。无论对更多的设备使用中断。无论对 IRQIRQ 的使用方式是独占还是共享,申请的使用方式是独占还是共享,申请IRQIRQ 的过程都分为的过程都分为 33 步: 步:

(( 11 ) 将所有的中断线探测一遍,看看哪些中断还没有被占用。从这) 将所有的中断线探测一遍,看看哪些中断还没有被占用。从这些还没有被占用的中断中选一个作为该设备的些还没有被占用的中断中选一个作为该设备的 IRQIRQ 。。

(( 22 )通过中断申请函数申请选定的)通过中断申请函数申请选定的 IRQIRQ ,这是要指定申请的方式是,这是要指定申请的方式是独占还是共享。独占还是共享。

(( 33 )根据中断申请函数的返回值决定怎么做:如果成功了则执行中)根据中断申请函数的返回值决定怎么做:如果成功了则执行中断,如果没成功则或者重新申请或者放弃申请并返回错误。 断,如果没成功则或者重新申请或者放弃申请并返回错误。 2222

Page 10: 第  3  章 中断和中断处理

3.1.3 3.1.3 置中断标志位置中断标志位 在处理中断的时候,中断控制器会屏蔽掉原先发送中断的在处理中断的时候,中断控制器会屏蔽掉原先发送中断的那个设备,直到它发送的上一个中断被处理完了为止。因那个设备,直到它发送的上一个中断被处理完了为止。因此如果发送中断的那个设备载中断处理期间又发送了一个此如果发送中断的那个设备载中断处理期间又发送了一个中断,那么这个中断就被永远的丢失了。 中断,那么这个中断就被永远的丢失了。

这种情况之所以发生,是因为中断控制器并不能缓冲中断这种情况之所以发生,是因为中断控制器并不能缓冲中断信息。当前面的一个中断没有处理完之前又有新的中断到信息。当前面的一个中断没有处理完之前又有新的中断到达,中断控制器就会丢掉新的中断。这个问题可以通过设达,中断控制器就会丢掉新的中断。这个问题可以通过设置主处理器置主处理器 (CPU)(CPU)上的“置中断标志位”(上的“置中断标志位”( stisti)来解决,)来解决,因为主处理器具有缓冲中断的功能。如果使用了“置中断因为主处理器具有缓冲中断的功能。如果使用了“置中断标志位”,在处理完中断以后使用标志位”,在处理完中断以后使用 stisti函数就可以使先前函数就可以使先前被屏蔽的中断得到服务。 被屏蔽的中断得到服务。

Page 11: 第  3  章 中断和中断处理

3.1.3 3.1.3 中断处理程序的不可重入性中断处理程序的不可重入性 有时候需要屏蔽中断,是出于管理上的考虑。因为中断处理程序是不有时候需要屏蔽中断,是出于管理上的考虑。因为中断处理程序是不

可重入的,所以不能并行执行同一个中断处理程序,因此在中断处理可重入的,所以不能并行执行同一个中断处理程序,因此在中断处理的过程中要屏蔽由同一个的过程中要屏蔽由同一个 IRQIRQ 来的新中断。来的新中断。

由于设备驱动程序要和设备的寄存器打交道,设备寄存器就是全局变由于设备驱动程序要和设备的寄存器打交道,设备寄存器就是全局变量。如果一个中断处理程序可以并行,很有可能会发生驱动程序锁死量。如果一个中断处理程序可以并行,很有可能会发生驱动程序锁死的情况。当驱动程序锁死的时候,操作系统并不一定会崩溃,但是锁的情况。当驱动程序锁死的时候,操作系统并不一定会崩溃,但是锁死的驱动程序所支持的那个设备就不能再使用了。因此,最简单的办死的驱动程序所支持的那个设备就不能再使用了。因此,最简单的办法就是禁止同一设备的中断处理程序并行,即设备的中断处理程序是法就是禁止同一设备的中断处理程序并行,即设备的中断处理程序是不可重入的。由于中断处理程序要求不可重入,编写可重入的中断处不可重入的。由于中断处理程序要求不可重入,编写可重入的中断处理程序则几乎是不可能的。所以通常不必编写可重入的中断处理程序。理程序则几乎是不可能的。所以通常不必编写可重入的中断处理程序。但可编写可重入的设备驱动程序。 但可编写可重入的设备驱动程序。

一旦中断的竞争条件出现,有可能会发生死锁的情况,严重时可能会一旦中断的竞争条件出现,有可能会发生死锁的情况,严重时可能会将整个系统锁死。所以一定要避免竞争条件的出现。将整个系统锁死。所以一定要避免竞争条件的出现。

Page 12: 第  3  章 中断和中断处理

3.1.4 3.1.4 时钟和定时器中断时钟和定时器中断 操作系统应该能够在将来某个时刻准时调度某个任务。所操作系统应该能够在将来某个时刻准时调度某个任务。所

以需要一种能保证准时调度某个任务运行的机制。希望支以需要一种能保证准时调度某个任务运行的机制。希望支持每种操作系统的微处理器必须包含一个可周期性中断它持每种操作系统的微处理器必须包含一个可周期性中断它的可编程间隔定时器。该定时器可以在指定的时间周期性的可编程间隔定时器。该定时器可以在指定的时间周期性地中断处理器。这个周期性中断被称为系统时钟周期,它地中断处理器。这个周期性中断被称为系统时钟周期,它像音乐中的节拍器一样来协调着系统中所有的活动。像音乐中的节拍器一样来协调着系统中所有的活动。

除此之外,操作系统还必须具备一定的接口记录系统的时除此之外,操作系统还必须具备一定的接口记录系统的时间,并为程序提供时间服务。一般来说,操作系统和计算间,并为程序提供时间服务。一般来说,操作系统和计算机硬件一起维护着系统中的时间。机硬件一起维护着系统中的时间。 LinuxLinux 的时钟观念很简的时钟观念很简单:它表示系统启动后的以时钟周期计数的时间。在 单:它表示系统启动后的以时钟周期计数的时间。在 PC PC 机中,机中, Linux Linux 利用 利用 BIOS CMOS BIOS CMOS 中记录的时间(称为中记录的时间(称为“硬件时钟”)作为系统启动时的时间基准,而在系统运“硬件时钟”)作为系统启动时的时间基准,而在系统运行时,利用时钟周期测量系统的时间(称为“软件时行时,利用时钟周期测量系统的时间(称为“软件时钟”)。钟”)。

Page 13: 第  3  章 中断和中断处理

3.1.4 3.1.4 时钟和定时器中断时钟和定时器中断 Linux Linux 利用全局变量利用全局变量 jiffiesjiffies (瞬时)作为系统时间的测量基准,所有(瞬时)作为系统时间的测量基准,所有

的时间都从 的时间都从 1970.1.1 0:00:00 1970.1.1 0:00:00 开始计算,系统启动时,将 开始计算,系统启动时,将 CMOS CMOS 中中记录的时间转化为从 记录的时间转化为从 1970.1.1 0:00:00 1970.1.1 0:00:00 算起的 算起的 jiffies jiffies 值。值。 Linux Linux 内内核中没有任何时区的概念,核中没有任何时区的概念, Linux Linux 内核中的时间以格林尼治时间记录,内核中的时间以格林尼治时间记录,将格林尼治时间转换为本地时间的任务则由应用程序负责。将格林尼治时间转换为本地时间的任务则由应用程序负责。

Linux Linux 的 的 jiffies jiffies 值由两部分组成,分别用 值由两部分组成,分别用 32 32 位无符号整数记录自 位无符号整数记录自 1970.1.1 00:00:00 1970.1.1 00:00:00 开始的秒数以及秒数千分值。这样,开始的秒数以及秒数千分值。这样, Linux Linux 可正可正确处理的时间值最大到 确处理的时间值最大到 1970 1970 年后的年后的 138 138 年,即 年,即 2108 2108 年,而时间年,而时间的计量也可精确到千分之一秒。在到达 的计量也可精确到千分之一秒。在到达 2108 2108 年之前,人们会想出更年之前,人们会想出更好的办法来计时。好的办法来计时。

LinuxLinux 包含两种类型的系统定时器,它们都可以在某个系统时间上被包含两种类型的系统定时器,它们都可以在某个系统时间上被队列例程使用,但是它们的实现稍有区别。图 队列例程使用,但是它们的实现稍有区别。图 3.3 3.3 说明了这两种定说明了这两种定时器机制。时器机制。

Page 14: 第  3  章 中断和中断处理

3.1.4 3.1.4 时钟和定时器中断时钟和定时器中断 图 图 3.3 Linux 3.3 Linux 中的两种系统定时器 中的两种系统定时器

next

prev

expires

*data

timer_list timer_list

function()

next

prev

expires

*data

function()

timer_list

next

prev

expires

*data

function()

31 0 timer_active

...

timer_table timer_struct

0

31

...

next

*fn()

timer_struct

next

*fn()

timer_head

(a) 老定时器结构

(b) 新定时器结构

Page 15: 第  3  章 中断和中断处理

3.1.4 3.1.4 时钟和定时器中断时钟和定时器中断 第一种是老的定时器机制,它包含指向第一种是老的定时器机制,它包含指向 timer_strtimer_str

uctuct结构的结构的 3232位指针的静态数组以及当前活动定位指针的静态数组以及当前活动定时器的掩码 :时器的掩码 : time_activetime_active 。 此定时器表中的位。 此定时器表中的位置是静态定义的(类似底层部分的数据结构置是静态定义的(类似底层部分的数据结构 bh_bbh_basease)。数组中的元素通常是静态定义的,在系)。数组中的元素通常是静态定义的,在系统初始化过程中填充这些元素。其入口在系统初统初始化过程中填充这些元素。其入口在系统初始化时被加入到表中。 第二种是相对较新的定时始化时被加入到表中。 第二种是相对较新的定时器机制,它使用以定时器到期时间的升序排列的器机制,它使用以定时器到期时间的升序排列的 ttimer_listimer_list链表结构组织。链表结构组织。

Page 16: 第  3  章 中断和中断处理

3.1.4 3.1.4 时钟和定时器中断时钟和定时器中断 这两种方法都使用这两种方法都使用 jiffiesjiffies作为时间周期的终结。如果某个定时器要作为时间周期的终结。如果某个定时器要

在 在 5 5 秒之后到期,则必须将秒之后到期,则必须将 55秒时间转换成对应的 秒时间转换成对应的 jiffies jiffies 值,并值,并且将它和以且将它和以 jiffiesjiffies计数的当前系统时间相加从而得到该定时器到期计数的当前系统时间相加从而得到该定时器到期的系统时间。在每个系统时钟周期里的系统时间。在每个系统时钟周期里 ,,定时器的底层部分处理过程定时器的底层部分处理过程被标记成活动状态,当调度程序下次运行时能进行定时器队列的处被标记成活动状态,当调度程序下次运行时能进行定时器队列的处理。定时器底层部分处理过程要处理上述两种类型的系统定时器。理。定时器底层部分处理过程要处理上述两种类型的系统定时器。对老的系统定时器来说,就是检查对老的系统定时器来说,就是检查 timer_activetimer_active位是否置位。如位是否置位。如果活动定时器已经到期(到期时间大于或等于当前系统的 果活动定时器已经到期(到期时间大于或等于当前系统的 jiffiesjiffies ),),则调用对应的定时器例程,并清除 则调用对应的定时器例程,并清除 timer_active timer_active 中的相应活动位。中的相应活动位。对于新定时器,则检查链表中的 对于新定时器,则检查链表中的 timer_list timer_list 数据结构,每个到期数据结构,每个到期的定时器从链表中移出,而对应的定时器例程被调用。新的定时器的定时器从链表中移出,而对应的定时器例程被调用。新的定时器机制的优点之一是能传递一个参数给定时器例程。机制的优点之一是能传递一个参数给定时器例程。

LinuxLinux 提供了两种定时器服务。一种早期的由提供了两种定时器服务。一种早期的由 timer_structtimer_struct 等结构等结构描述,由描述,由 run_old_timesrun_old_times 函数处理。另一种“新”的服务由函数处理。另一种“新”的服务由 timertimer_list_list 等结构描述,由等结构描述,由 add_timeradd_timer 、、 del_timerdel_timer 、、 cascade_timecascade_time和和 run_timer_listrun_timer_list 等函数处理。等函数处理。

Page 17: 第  3  章 中断和中断处理

3.1.4 3.1.4 时钟和定时器中断时钟和定时器中断 早期的定时器服务利用如下数据结构:早期的定时器服务利用如下数据结构:

struct timer_struct {struct timer_struct { unsigned long expires; //unsigned long expires; //本定时器被唤醒的时刻 本定时器被唤醒的时刻 void (*fn)(void); // void (*fn)(void); // 定时器唤醒后的处理函数 定时器唤醒后的处理函数 }} struct timer_struct timer_table[32]; //struct timer_struct timer_table[32]; //最多可同时启用最多可同时启用 3232 个定时器 个定时器 unsigned long timer_active; // unsigned long timer_active; // 每位对应一定时器,置每位对应一定时器,置 11表示启用 表示启用 新的定时器服务依靠链表结构突破了新的定时器服务依靠链表结构突破了 3232 个的限制,利用如下的数据结构:个的限制,利用如下的数据结构: struct timer_list {struct timer_list { struct timer_list *next;struct timer_list *next; struct timer_list *prev;struct timer_list *prev; unsigned long expires;unsigned long expires; unsigned long data; // unsigned long data; // 用来存放当前进程的用来存放当前进程的 PCBPCB块的指针,可作为参数传块的指针,可作为参数传 void (*function)(unsigned long); void (*function)(unsigned long); 给给 function function }}

Page 18: 第  3  章 中断和中断处理

3.1.4 3.1.4 时钟和定时器中断时钟和定时器中断 系统启动核心时,调用系统启动核心时,调用 start_kernal()start_kernal() 开始各方面的初始开始各方面的初始化,在这之前,各种中断都被禁止,只有在完成必要的初化,在这之前,各种中断都被禁止,只有在完成必要的初始化后,直到执行完始化后,直到执行完 Kmalloc_init()Kmalloc_init() 后,才允许中断(后,才允许中断( ininit\main.cit\main.c)。)。

在在 CPUCPU 调度时、系统调用返回前和中断处理返回前都会调度时、系统调用返回前和中断处理返回前都会作判断调用作判断调用 do_bottom_halfdo_bottom_half函数。函数。 Do_bottom_halfDo_bottom_half函函数依次扫描数依次扫描 3232 个队列,找出需要服务的队列,执行服务个队列,找出需要服务的队列,执行服务后把对应该队列的后把对应该队列的 bh_activebh_active 的相应位置的相应位置 00。由于。由于 bh_acbh_activetive 标志中标志中 TIMER_BHTIMER_BH 对应的对应的 bitbit为为 11 ,因而系统根据服,因而系统根据服务函数入口地址数组务函数入口地址数组 bh_basebh_base找到函数找到函数 timer_bh()timer_bh() 的入的入口地址,并马上执行该函数,在函数口地址,并马上执行该函数,在函数 timer_bhtimer_bh 中,调用中,调用函数函数 run_timer_listrun_timer_list()和函数()和函数 run_old_timersrun_old_timers ()函()函数,定时执行服务。 数,定时执行服务。

Page 19: 第  3  章 中断和中断处理

3.2 Linux3.2 Linux 的中断处理的中断处理 3.2.1 Linux3.2.1 Linux 中断处理程序的特色中断处理程序的特色

考虑到中断处理的效率,考虑到中断处理的效率, LinuxLinux 的中断处理程序分为两的中断处理程序分为两个部分:上半部(个部分:上半部( top halftop half)和下半部)和下半部 (bottom half)(bottom half)。。上半部的功能是“登记中断”。当一个中断发生时,就上半部的功能是“登记中断”。当一个中断发生时,就把设备驱动程序中中断例程的下半部挂到该设备的下半把设备驱动程序中中断例程的下半部挂到该设备的下半部执行队列中去,然后就等待新的中断的到来。这样,部执行队列中去,然后就等待新的中断的到来。这样,上半部执行的速度就会很快,就可以接受所负责设备产上半部执行的速度就会很快,就可以接受所负责设备产生的更多中断。上半部之所以要快,是因为它是完全屏生的更多中断。上半部之所以要快,是因为它是完全屏蔽中断的,其它的中断只能等到这个中断处理程序执行蔽中断的,其它的中断只能等到这个中断处理程序执行完毕以后才能申请,不能得到及时的处理。快速的中断完毕以后才能申请,不能得到及时的处理。快速的中断处理程序就可以对设备产生的中断尽可能多地进行服务。处理程序就可以对设备产生的中断尽可能多地进行服务。

Page 20: 第  3  章 中断和中断处理

3.2 Linux3.2 Linux 的中断处理的中断处理 有些中断事件的处理比较复杂,中断处理程序必须多花一点时间才能有些中断事件的处理比较复杂,中断处理程序必须多花一点时间才能

够把事情做完。为了化解在短时间内完成复杂处理的矛盾,够把事情做完。为了化解在短时间内完成复杂处理的矛盾, LinuxLinux 引引入了下半部的概念。下半部和上半部最大的不同是下半部是可中断的,入了下半部的概念。下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的。上半部只是将下半部放入了它们所负责的设而上半部是不可中断的。上半部只是将下半部放入了它们所负责的设备的中断处理队列中去,然后就什么都不管了。因此,下半部几乎做备的中断处理队列中去,然后就什么都不管了。因此,下半部几乎做了中断处理程序所有的工作,包括查看设备上的寄存器以获得产生中了中断处理程序所有的工作,包括查看设备上的寄存器以获得产生中断的事件信息,并根据这些信息进行相应的处理。如果下半部不知道断的事件信息,并根据这些信息进行相应的处理。如果下半部不知道怎么去做,它就使用鸵鸟算法来解决问题,即忽略这个事件。 怎么去做,它就使用鸵鸟算法来解决问题,即忽略这个事件。

由于下半部是可中断的,所以在它运行期间,如果其它的设备产生了由于下半部是可中断的,所以在它运行期间,如果其它的设备产生了中断,这个下半部可以暂时的中断掉,等到那个设备的上半部运行完中断,这个下半部可以暂时的中断掉,等到那个设备的上半部运行完了,再回头来运行它。但是要注意,如果一个设备中断处理程序正在了,再回头来运行它。但是要注意,如果一个设备中断处理程序正在运行,无论它是运行上半部还是运行下半部,只要中断处理程序还没运行,无论它是运行上半部还是运行下半部,只要中断处理程序还没有处理完毕,在这期间设备产生的新的中断都将被忽略掉。因为中断有处理完毕,在这期间设备产生的新的中断都将被忽略掉。因为中断处理程序是不可重入的,同一个中断处理程序是不能并行的。 处理程序是不可重入的,同一个中断处理程序是不能并行的。

Page 21: 第  3  章 中断和中断处理

3.2 Linux3.2 Linux 的中断处理的中断处理 LinuxLinux 将中断处理程序划分成两个部分的一个原因,是要把中断的总将中断处理程序划分成两个部分的一个原因,是要把中断的总延迟时间最小化。延迟时间最小化。 LinuxLinux 内核定义了两种类型的中断,快速的和慢速内核定义了两种类型的中断,快速的和慢速的,这两者之间的一个区别是慢速中断自身还可以被中断,而快速中的,这两者之间的一个区别是慢速中断自身还可以被中断,而快速中断则不能。因此,当处理快速中断时,如果有其它中断到达;不管是断则不能。因此,当处理快速中断时,如果有其它中断到达;不管是快速中断还是慢速中断,它们都必须等待。为了尽可能快地处理这些快速中断还是慢速中断,它们都必须等待。为了尽可能快地处理这些其它的中断,内核就需要尽可能地将处理延迟到下半部分执行。其它的中断,内核就需要尽可能地将处理延迟到下半部分执行。

其次,当内核执行上半部分时,正在服务的这个特殊其次,当内核执行上半部分时,正在服务的这个特殊 IRQIRQ 将会被可编将会被可编程中断控制器禁止,于是,连接在同一个程中断控制器禁止,于是,连接在同一个 IRQIRQ 上的其它设备就只有等上的其它设备就只有等到该该中断处理被处理完毕后果才能发出到该该中断处理被处理完毕后果才能发出 IRQIRQ 请求。而采用请求。而采用 BottomBottom_half_half机制后,不需要立即处理的部分就可以放在下半部分处理,从机制后,不需要立即处理的部分就可以放在下半部分处理,从而,加快了处理机对外部设备的中断请求的响应速度。而,加快了处理机对外部设备的中断请求的响应速度。

Page 22: 第  3  章 中断和中断处理

3.2 Linux3.2 Linux 的中断处理的中断处理 还有一个原因是,处理程序的下半部分还可以包还有一个原因是,处理程序的下半部分还可以包

含一些并非每次中断都必须处理的操作;对这些含一些并非每次中断都必须处理的操作;对这些操作,内核可以在一系列设备中断之后集中处理操作,内核可以在一系列设备中断之后集中处理一次就可以了。即在这种情况下,每次都执行并一次就可以了。即在这种情况下,每次都执行并非必要的操作完全是一种浪费,而采用非必要的操作完全是一种浪费,而采用 Bottom_Bottom_halfhalf机制后,可以稍稍延迟并在后来只执行一次机制后,可以稍稍延迟并在后来只执行一次就行了。就行了。

在下半部中也可以进行中断屏蔽。如果某一段代在下半部中也可以进行中断屏蔽。如果某一段代码不能被中断的话。可以使用码不能被中断的话。可以使用 cticti 、、 stisti 或者是或者是 sasave_flagve_flag、、 restore_flagrestore_flag来实现。来实现。

Page 23: 第  3  章 中断和中断处理

3.2.2 3.2.2 中断的相关数据结构 中断的相关数据结构 从数据结构入手,应该说是分析操作系统源码最常用的和从数据结构入手,应该说是分析操作系统源码最常用的和

最主要的方法。因为操作系统的几大功能部件,如进程管最主要的方法。因为操作系统的几大功能部件,如进程管理、设备管理、内存管理等,都可以通过对其相应的数据理、设备管理、内存管理等,都可以通过对其相应的数据结构的分析来弄懂其实现机制。很好的掌握这种方法,对结构的分析来弄懂其实现机制。很好的掌握这种方法,对分析分析 LinuxLinux 内核大有帮助。内核大有帮助。

中断向量在保护模式下的实现机制是中断描述符表 中断向量在保护模式下的实现机制是中断描述符表 (Inter(Interrupt Descriptor Table, IDT)rupt Descriptor Table, IDT) ,中断描述符表的结构如图,中断描述符表的结构如图3.43.4 所示。中断描述符表即中断向量表相当于一个数组,所示。中断描述符表即中断向量表相当于一个数组,包含包含 256256个中断描述符,每个中断描述符个中断描述符,每个中断描述符 88位,对应硬件位,对应硬件提供的提供的 256256个中断服务例程的入口,即个中断服务例程的入口,即 256256个中断向量。个中断向量。IDTIDT的位置由的位置由 idtridtr确定,确定, idtridtr 是个是个 4848位的寄存器,高位的寄存器,高 3232位是位是 IDTIDT的基址,低的基址,低 1616 位为位为 IDTIDT的界限的界限 ((通常为通常为 2k=2562k=256*8)*8)。 。

Page 24: 第  3  章 中断和中断处理

3.2.2 3.2.2 中断的相关数据结构中断的相关数据结构 图 图 3.4 Linux 3.4 Linux 的中断处理数据结构 的中断处理数据结构

Page 25: 第  3  章 中断和中断处理

3.2.2 3.2.2 中断的相关数据结构中断的相关数据结构 在 在 i386 i386 系统中,系统中, Linux Linux 启动时要设置系统的中断描述符启动时要设置系统的中断描述符表表 IDTIDT。。 IDT IDT 中包含各个中断(以及异常,诸如浮点运中包含各个中断(以及异常,诸如浮点运算溢出)的服务程序地址,中断服务程序地址由 算溢出)的服务程序地址,中断服务程序地址由 Linux Linux 提供。每个设备驱动程序可以在图 提供。每个设备驱动程序可以在图 3.4 3.4 所示的结构(所示的结构( irqirq_action_action)中注册自己的中断及中断处理程序地址。)中注册自己的中断及中断处理程序地址。 LinuLinux x 的中断服务程序根据 的中断服务程序根据 irq_action irq_action 中的注册信息调用相中的注册信息调用相应的设备驱动程序的中断处理程序。和硬件相关的中断处应的设备驱动程序的中断处理程序。和硬件相关的中断处理代码隐藏在中断服务程序中,这样,设备驱动程序的中理代码隐藏在中断服务程序中,这样,设备驱动程序的中断处理程序可在不同平台之间方便移植。一般来说,断处理程序可在不同平台之间方便移植。一般来说, CPU CPU 在处理中断时,首先要在堆栈中保存与 在处理中断时,首先要在堆栈中保存与 CPU CPU 指令执行相指令执行相关的寄存器(例如指令计数寄存器),然后调用中断服务关的寄存器(例如指令计数寄存器),然后调用中断服务程序,中断服务程序结束时再恢复这些寄存器。程序,中断服务程序结束时再恢复这些寄存器。

Page 26: 第  3  章 中断和中断处理

3.2.2 3.2.2 中断的相关数据结构中断的相关数据结构 图图 3.5 3.5 与硬中断相关的几个数据结构的关系 与硬中断相关的几个数据结构的关系

Page 27: 第  3  章 中断和中断处理

3.2.2 3.2.2 中断的相关数据结构中断的相关数据结构 irq_action irq_action 实际是一个数组,其中包含指向 实际是一个数组,其中包含指向 irqaction irqaction 的指针,的指针,每个数组元素分别定义一个 每个数组元素分别定义一个 IRQIRQ。。 Linux Linux 内核提供相应的操作内核提供相应的操作函数,设备驱动程序可调用这些操作函数设置相应的中断处理函数,设备驱动程序可调用这些操作函数设置相应的中断处理函数。一般在系统启动时,由各个设备驱动程序通过如下途径函数。一般在系统启动时,由各个设备驱动程序通过如下途径获取相关的设备 获取相关的设备 IRQ IRQ 并设置对应的 并设置对应的 irq_action irq_action 数组元素所指数组元素所指向的 向的 irqaction irqaction 结构。结构。

由于由于 0-310-31 号中断向量已被号中断向量已被 IntelIntel 保留,就剩下保留,就剩下 32-25532-255 共共 224224个中断向量可用。在个中断向量可用。在 LinuxLinux 中,这中,这 224224 个中断向量除了个中断向量除了 0x80 0x80 (SYSCALL_VECTOR)(SYSCALL_VECTOR)用作系统调用总入口之外,其它都用在外用作系统调用总入口之外,其它都用在外部硬件中断源(包括可编程中断控制器部硬件中断源(包括可编程中断控制器 8259A8259A的的 1515 个个 irqirq ))上。实际上,当没有定义上。实际上,当没有定义 CONFIG_X86_IO_APICCONFIG_X86_IO_APIC 时,其它时,其它 223223(( 除除 0x800x80 外外 ))个中断向量,只利用了从个中断向量,只利用了从 3232 号开始的号开始的 1515 个,其个,其它它 208208 个空着未用。这些中断服务程序入口的设置将在下面详个空着未用。这些中断服务程序入口的设置将在下面详细说明。细说明。

与硬中断相关数据结构主要有三个与硬中断相关数据结构主要有三个 , , 三者关系如图三者关系如图 3.53.5 所示。 所示。

Page 28: 第  3  章 中断和中断处理

3.2.2 3.2.2 中断的相关数据结构中断的相关数据结构 (1) (1) 定义在定义在 /arch/i386/Kernel/irq.h/arch/i386/Kernel/irq.h 中的中的 struct hw_interrupt_typestruct hw_interrupt_type 数据数据结构,它是一个抽象的中断控制器。这包含一系列的指向函数的指针,这些结构,它是一个抽象的中断控制器。这包含一系列的指向函数的指针,这些函数处理控制器特有的操作:函数处理控制器特有的操作:

typenametypename:控制器的名字。 :控制器的名字。 startupstartup :允许从给定的控制器的:允许从给定的控制器的 IRQIRQ所产生的事件。 所产生的事件。 shutdownshutdown:禁止从给定的控制器的:禁止从给定的控制器的 IRQIRQ所产生的事件。 所产生的事件。 handlehandle:根据提供给该函数的:根据提供给该函数的 IRQIRQ,处理唯一的中断。 ,处理唯一的中断。 enableenable 和和 disabledisable:这两个函数基本上和:这两个函数基本上和 startupstartup和和 shutdownshutdown 相同; 相同; struct hw_interrupt_type { struct hw_interrupt_type { const char * typenameconst char * typename; ; void (*startup)(unsigned int irq)void (*startup)(unsigned int irq) ; ; void (*shutdown)(unsigned int irq)void (*shutdown)(unsigned int irq) ; ; void (*handle)(unsigned int irq, struct pt_regs * regs)void (*handle)(unsigned int irq, struct pt_regs * regs) ; ; void (*enable)(unsigned int irq)void (*enable)(unsigned int irq) ; ; void (*disable)(unsigned int irq)void (*disable)(unsigned int irq) ; ; }}; ;

Page 29: 第  3  章 中断和中断处理

3.2.2 3.2.2 中断的相关数据结构中断的相关数据结构 (2) (2) 定义在定义在 /arch/i386/Kernel/irq.h/arch/i386/Kernel/irq.h 中的另外一个数据结中的另外一个数据结构是构是 irq_desc_tirq_desc_t ,它具有如下成员: ,它具有如下成员:

statusstatus :一个整数。代表:一个整数。代表 IRQIRQ的状态:的状态: IRQIRQ是否被禁止是否被禁止了,有关了,有关 IRQIRQ的设备当前是否正被自动检测,等等。 的设备当前是否正被自动检测,等等。

handlerhandler:指向:指向 hw_interrupt_typehw_interrupt_type 的指针。 的指针。 actionaction:指向:指向 irqactionirqaction结构组成的队列的头。正常情况结构组成的队列的头。正常情况

下每个下每个 IRQIRQ 只有一个操作,因此链接列表的正常长度是只有一个操作,因此链接列表的正常长度是 11(或者(或者 00 )。但是,如果)。但是,如果 IRQIRQ 被两个或者多个设备所共享,被两个或者多个设备所共享,那么这个队列中就有多个操作。 那么这个队列中就有多个操作。

depthdepth:: irq_desc_tirq_desc_t 的当前用户的个数。主要是用来保的当前用户的个数。主要是用来保证在中断处理过程中证在中断处理过程中 IRQIRQ不会被禁止。不会被禁止。

Page 30: 第  3  章 中断和中断处理

3.2.2 3.2.2 中断的相关数据结构中断的相关数据结构 irq_descirq_desc 是是 irq_desc_t irq_desc_t 类型的数组。对于每一个类型的数组。对于每一个 IRQIRQ都都

有一个数组入口,即数组把每一个有一个数组入口,即数组把每一个 IRQIRQ 映射到和它相关的映射到和它相关的处理程序和处理程序和 irq_desc_tirq_desc_t 中的其它信息。 中的其它信息。

typedef struct { typedef struct { unsigned int statusunsigned int status ; ; // IRQ status - IRQ_INPROGRES// IRQ status - IRQ_INPROGRES

S, IRQ_DISABLED S, IRQ_DISABLED struct hw_interrupt_type *handlerstruct hw_interrupt_type *handler; ; // handle/enabl// handle/enabl

e/disable functions e/disable functions struct irqaction *actionstruct irqaction *action; ; // IRQ action list // IRQ action list unsigned int depthunsigned int depth; ; // Disable depth for nested irq d// Disable depth for nested irq d

isables isables } irq_desc_t} irq_desc_t; ;

Page 31: 第  3  章 中断和中断处理

3.2.2 3.2.2 中断的相关数据结构中断的相关数据结构 (3) (3) 定义在定义在 include/linux/ interrupt.hinclude/linux/ interrupt.h 中的中的 struct irqactionstruct irqaction 数据结构包数据结构包

含了内核接收到特定含了内核接收到特定 IRQIRQ之后应采取的操作,其成员如下:之后应采取的操作,其成员如下:

handlerhandler:是一指向某个函数的指针。该函数就是所在结构对相应中断:是一指向某个函数的指针。该函数就是所在结构对相应中断的处理函数。 的处理函数。

flagsflags :取值只有:取值只有 SA_INTERRUPTSA_INTERRUPT (中断可嵌套),(中断可嵌套), SA_SAMPLE_RANSA_SAMPLE_RANDOMDOM (这个中断是源于物理随机性的),和(这个中断是源于物理随机性的),和 SA_SHIRQSA_SHIRQ (这个(这个 IRQIRQ和和其它其它 struct irqactionstruct irqaction共享)。 共享)。

maskmask:在:在 x86x86或者体系结构无关的代码中不会使用(除非将其设置为或者体系结构无关的代码中不会使用(除非将其设置为00 );只有在);只有在 SPARC64SPARC64 的移植版本中要跟踪有关软盘的信息时才会使用的移植版本中要跟踪有关软盘的信息时才会使用它。 它。

namename:产生中断的硬件设备的名字。因为不止一个硬件可以共享一个:产生中断的硬件设备的名字。因为不止一个硬件可以共享一个 IIRQRQ。 。

dev_iddev_id:标识硬件类型的一个唯一的:标识硬件类型的一个唯一的 IDID。。 LinuxLinux支持的所有硬件设备支持的所有硬件设备的每一种类型,都有一个由制造厂商定义的在此成员中记录的设备的每一种类型,都有一个由制造厂商定义的在此成员中记录的设备 IDID。 。

nextnext:如果:如果 IRQIRQ是共享的,那么这就是指向队列中下一个是共享的,那么这就是指向队列中下一个 struct irqactistruct irqactionon结构的指针。通常情况下,结构的指针。通常情况下, IRQIRQ不是共享的,因此这个成员就为空。 不是共享的,因此这个成员就为空。

Page 32: 第  3  章 中断和中断处理

3.2.2 3.2.2 中断的相关数据结构中断的相关数据结构 struct irqaction { struct irqaction { void (*handler)(int, void *, struct pt_regs *)void (*handler)(int, void *, struct pt_regs *) ; ; unsigned long flagsunsigned long flags ; ; unsigned long maskunsigned long mask; ; const char *nameconst char *name; ; void *dev_idvoid *dev_id; ; struct irqaction *nextstruct irqaction *next; ; }};;

Page 33: 第  3  章 中断和中断处理

3.2.3 3.2.3 中断向量表中断向量表 IDTIDT 的初始化的初始化 LinuxLinux 内核在初始化阶段完成了对页式虚拟管理的初始化内核在初始化阶段完成了对页式虚拟管理的初始化

后,调用后,调用 trap_init()trap_init() 和和 init_IRQ()init_IRQ() 两个函数进行中断机制两个函数进行中断机制的初始化。其中的初始化。其中 trap_init()trap_init() 主要是对一些系统保留的中断主要是对一些系统保留的中断向量的初始化,而向量的初始化,而 init_IRQ()init_IRQ()则主要是用于外设的中断。则主要是用于外设的中断。

Page 34: 第  3  章 中断和中断处理

3.2.3 3.2.3 中断向量表中断向量表 IDTIDT 的初始化的初始化 void _init trap_init(void)void _init trap_init(void) {{ #ifdef CONFIG_EISA#ifdef CONFIG_EISA if (isa_readl(0x0FFFD9) == if (isa_readl(0x0FFFD9) == 'E+('I'<<8)+('S'<<16)'E+('I'<<8)+('S'<<16)++ ('A'<<24))('A'<<24)) EISA_bus = 1;EISA_bus = 1; #endif#endif set_trap_gate(0,÷_error);set_trap_gate(0,÷_error); set_trap_gate(1,&debug);set_trap_gate(1,&debug); set_intr_gate(2,&nmi);set_intr_gate(2,&nmi); set_system_gate(3,&int3); //int3-5 can be called from all set_system_gate(3,&int3); //int3-5 can be called from all set_system_gate(4,&overflow);set_system_gate(4,&overflow);

Page 35: 第  3  章 中断和中断处理

3.2.3 3.2.3 中断向量表中断向量表 IDTIDT 的初始化的初始化 set_system_gate(5,&bounds);set_system_gate(5,&bounds); set_trap_gate(6,&invalid_op)set_trap_gate(6,&invalid_op) set_trap_gate(7set_trap_gate(7,, device_not_available)device_not_available) ;; set_trap_gate(8,&double_fault)set_trap_gate(8,&double_fault) ;; set_trap_gate(9,&coprocessor_segment_overrun)set_trap_gate(9,&coprocessor_segment_overrun) ;; set_trap_gate(10,&invalid_TSS)set_trap_gate(10,&invalid_TSS) ;; set_trap_gate(11,&segment_not_present)set_trap_gate(11,&segment_not_present) ;; set_trap_gate(12,&stack_segment)set_trap_gate(12,&stack_segment) ;; set_trap_gate(13,&general_protection)set_trap_gate(13,&general_protection) ;; set_trap_gate(14,&page_fault)set_trap_gate(14,&page_fault) ;; set_trap_gate(15,&spurious_interrupt_bug)set_trap_gate(15,&spurious_interrupt_bug) ;; set_trap_gate(16,&coprocessor_error)set_trap_gate(16,&coprocessor_error) ;; set_trap_gate(17,&alignment_check)set_trap_gate(17,&alignment_check) ;; set_trap_gate(18,&machine_check)set_trap_gate(18,&machine_check) ;; set_trap_gate(19,&simd_coprocessor_error)set_trap_gate(19,&simd_coprocessor_error) ;;

Page 36: 第  3  章 中断和中断处理

3.2.3 3.2.3 中断向量表中断向量表 IDTIDT 的初始化的初始化 set_system_gate(SYSCALL_VECTOR,&system_call);set_system_gate(SYSCALL_VECTOR,&system_call); // default LDT is a single_entry callgate to lcall7 for iBCS// default LDT is a single_entry callgate to lcall7 for iBCS // and a callgate to lcall27 for Solaris/x86 binaries// and a callgate to lcall27 for Solaris/x86 binaries set_call_gate(&default_ldt[0],lcall7);set_call_gate(&default_ldt[0],lcall7); set_call_gate(&default_ldt[4],lcall27);set_call_gate(&default_ldt[4],lcall27); _set_gate(a,12,3,addr);_set_gate(a,12,3,addr); //12 //12 即二进制的即二进制的 1100b1100b,类型码为,类型码为 100100 ,即调用门,即调用门 }}

这些函数都调用同一个子程序这些函数都调用同一个子程序 _set_gate(),_set_gate(),第一个参数用以设置中断第一个参数用以设置中断描述符表描述符表 idt_tableidt_table 中的第中的第 nn项,第二个参数对应于门格式中的项,第二个参数对应于门格式中的 DD 位位加类型位段,第三个参数是对应的加类型位段,第三个参数是对应的 DPLDPL 位段。位段。

Page 37: 第  3  章 中断和中断处理

3.2.3 3.2.3 中断向量表中断向量表 IDTIDT 的初始化的初始化 #define _set_gate(gate_addr,type,dpl,addr) #define _set_gate(gate_addr,type,dpl,addr) do {do { int _d0,_d1;int _d0,_d1; _asm_ _volatile_("movw %%dx,%%ax")_asm_ _volatile_("movw %%dx,%%ax") "movw %4,%%dx"movw %4,%%dx " " "mov1 %%eax,%0"mov1 %%eax,%0 " " "mov1 %%edx,%1" "mov1 %%edx,%1" :"=m" (*((long * ) (gate_addr))), :"=m" (*((long * ) (gate_addr))), "=m" (*(1+(long *)(gate_addr))),"=&a" (_d0),"=&d" (_d1)"=m" (*(1+(long *)(gate_addr))),"=&a" (_d0),"=&d" (_d1) :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), "3" ((char *) (addr)),"2" (_KERNEL_CS << 16)); "3" ((char *) (addr)),"2" (_KERNEL_CS << 16)); }while(0) }while(0)

Page 38: 第  3  章 中断和中断处理

3.2.3 3.2.3 中断向量表中断向量表 IDTIDT 的初始化的初始化 在第一个“:”到第二个“:”之间为输出部,有在第一个“:”到第二个“:”之间为输出部,有 44 个约束输个约束输

出,将有出,将有 44 个变量会被改变,分别为个变量会被改变,分别为 %0%0 、、 %1%1 、、 %2%2 和和 %3%3相结合。其中相结合。其中 %0%0 和和 %1%1 都是内存单元,分别和都是内存单元,分别和 gate_addrgate_addr 、、gate_addr+1gate_addr+1结合,结合, %2%2 于局部变量于局部变量 _d0_d0 结合,存放在寄存器结合,存放在寄存器%%eax%%eax 中;中; %3%3 于局部变量于局部变量 _d1_d1 结合,存放在寄存器结合,存放在寄存器 %%edx%%edx中。中。

第二个“:”之后的部分是输入部,输出部已经定义了第二个“:”之后的部分是输入部,输出部已经定义了 %0-%3%0-%3 ,,输入部中的第一个变量为输入部中的第一个变量为 %4%4 ,而紧接着的第二、第三个变量,而紧接着的第二、第三个变量分别等价于输出部的分别等价于输出部的 %3%3 和和 %2%2 。输入部中说明的个输入变量。输入部中说明的个输入变量地值,包括地值,包括 %3%3 和和 %2%2 ,都会在引用这些变量之前设置好。在,都会在引用这些变量之前设置好。在执行指令部的代码之前,会先将执行指令部的代码之前,会先将 %%eax%%eax 设成设成 (_KERNEL_CS<(_KERNEL_CS<<16)<16) ,把,把 %%edx%%edx 设为设为 addraddr ,, %4%4 变量设置为变量设置为 (0x8000+(dpl(0x8000+(dpl<<13)+type<<8)<<13)+type<<8),则,则 %4%4 变量对应于门结构中的第变量对应于门结构中的第 32-4732-47 位,位,其其 PP位为位为 11 。。

Page 39: 第  3  章 中断和中断处理

3.2.3 3.2.3 中断向量表中断向量表 IDTIDT 的初始化的初始化 指令部第一条指令“指令部第一条指令“ movw %%dx,%%ax”movw %%dx,%%ax” ,将,将 %%dx%%dx

的低的低 1616 位移入位移入 %%ax%%ax 的低的低 1616 位,这样,在位,这样,在 %%eax%%eax 中,中,其高其高 1616 位为位为 _KERNEL_CS,_KERNEL_CS,而低而低 1616 位为位为 addraddr 的低的低 1616 位,位,形成了门结构中的第形成了门结构中的第 0-310-31位。位。

第二条指令“第二条指令“ movx %4 ,%%dx”movx %4 ,%%dx” ,将,将 %4%4放入放入 %%dx%%dx中,也就是将门结构中的第中,也就是将门结构中的第 32-4732-47位放在位放在 %%dx%%dx 中,而中,而对于对于 %%edx%%edx 来说,就对应着门结构中的高来说,就对应着门结构中的高 3232位。位。

第三条指令“第三条指令“ mov1 %%eax,%0”mov1 %%eax,%0” ,将,将 %%edx%%edx写入变写入变量量 %0%0中,即中,即 *gate_addr*gate_addr 。。

第第 44条指令“条指令“ mov1 %%eax,%1”mov1 %%eax,%1” 将将 %%edx%%edx写入变量写入变量%1%1 中中 ,,即即 *(gate_addr+1)*(gate_addr+1)。。

将第三、第将第三、第 44条指令合起来看,就是将整个门结构的内容条指令合起来看,就是将整个门结构的内容都写道都写道 *gate_addr*gate_addr 中去了。中去了。

Page 40: 第  3  章 中断和中断处理

3.2.3 3.2.3 中断向量表中断向量表 IDTIDT 的初始化的初始化 void _inti init_IRQ(void)void _inti init_IRQ(void) {{ int i;int i; #ifndef CONFIG_X86_VISWS-APIC#ifndef CONFIG_X86_VISWS-APIC init_ISA_irqs();init_ISA_irqs(); #else#else init_VISWS_APIC_irqs();init_VISWS_APIC_irqs(); #endif#endif // Cover the whole vector space,no vector can escape// Cover the whole vector space,no vector can escape // us. (some of these will be overridden and become// us. (some of these will be overridden and become // 'special' SMP interrupts)// 'special' SMP interrupts) for (i=0;i< NR_IRQS;i++) {for (i=0;i< NR_IRQS;i++) { int vector = FIRST_EXTERNAL_VECTOR + i;int vector = FIRST_EXTERNAL_VECTOR + i; if (vector != SYSCALL_VECTOR)if (vector != SYSCALL_VECTOR) set_intr_gate(vector,interrupt[i]);set_intr_gate(vector,interrupt[i]); }}

Page 41: 第  3  章 中断和中断处理

3.2.3 3.2.3 中断向量表中断向量表 IDTIDT 的初始化的初始化 #ifdef CONFIG_SMP#ifdef CONFIG_SMP // IRQ0 must ve given a fixed assignment and initialized,// IRQ0 must ve given a fixed assignment and initialized, // because it's used before the I0-APIC is set up.// because it's used before the I0-APIC is set up. set_intr-gate(FIRST_DEVICE_VECTOR,interrupt[0]);set_intr-gate(FIRST_DEVICE_VECTOR,interrupt[0]); // The reschedule interrupt is a CPU-to-CPU reschedule-helper I// The reschedule interrupt is a CPU-to-CPU reschedule-helper I

PI,driven by wakeup.PI,driven by wakeup. set_intr_gate(RESCHEDULE_VECTOR,reschedule_interrupt);set_intr_gate(RESCHEDULE_VECTOR,reschedule_interrupt); // IPI for generic function call // IPI for generic function call set_intr_gate(CALL_FUCTION_VECTOR,call_funtion_interrupt);set_intr_gate(CALL_FUCTION_VECTOR,call_funtion_interrupt); #endif#endif

Page 42: 第  3  章 中断和中断处理

3.2.3 3.2.3 中断向量表中断向量表 IDTIDT 的初始化的初始化 #ifdef CONFIG_X86_LOCAL_APIC#ifdef CONFIG_X86_LOCAL_APIC // IPI vectors for APIC spurious and error interrupts // IPI vectors for APIC spurious and error interrupts set_intr_gate(SPURIOUS_APIC_VECTOR,spurious_interrupt);set_intr_gate(SPURIOUS_APIC_VECTOR,spurious_interrupt); set_intr_gate(ERROR_APIC_VECTOR,error_interrupt);set_intr_gate(ERROR_APIC_VECTOR,error_interrupt); #endif#endif // Set the clock to HZ Hz, we already have a valid vector now;// Set the clock to HZ Hz, we already have a valid vector now; outb_p(0x34,0x43); // binary, mode 2, LSB/MSB ,ch 0outb_p(0x34,0x43); // binary, mode 2, LSB/MSB ,ch 0 outb_p(LATCH & 0xff,0x40);// LSB outb_p(LATCH & 0xff,0x40);// LSB outb(LATCH >> 8, 0x40); // MSB outb(LATCH >> 8, 0x40); // MSB #ifndef CONFIG_VISWS#ifndef CONFIG_VISWS setup_irq(2, &irq2)setup_irq(2, &irq2) #endif#endif // External FPU? Set up irq13 if so,for original braindamaged IBM FERR coupli// External FPU? Set up irq13 if so,for original braindamaged IBM FERR coupli

ng.ng. if (boot_cpu_data.hard_math && !cpu_has_fpu)if (boot_cpu_data.hard_math && !cpu_has_fpu) setup_irq(13,&irq13);setup_irq(13,&irq13);

Page 43: 第  3  章 中断和中断处理

3.2.3 3.2.3 中断向量表中断向量表 IDTIDT 的初始化的初始化 i386i386 体系支持体系支持 256256 个中断向量。扣除了为个中断向量。扣除了为 CPUCPU 保留的向量后,保留的向量后,

很难说剩下的中断向量是否够用。所以,很难说剩下的中断向量是否够用。所以, LinuxLinux 系统中为每个系统中为每个中断向量设置一个队列,根据每个中断源所使用的中断向量,中断向量设置一个队列,根据每个中断源所使用的中断向量,将其中断服务程序挂到相应的队列中。数组将其中断服务程序挂到相应的队列中。数组 irq_desc[]irq_desc[]中的每中的每个元素则是这样一个队列头部以及控制结构。当中断发生时,个元素则是这样一个队列头部以及控制结构。当中断发生时,首先执行中断向量相对应的一段总服务程序,根据具体中断源首先执行中断向量相对应的一段总服务程序,根据具体中断源的设备号在其所属队列中找到特定的服务程序加以执行。的设备号在其所属队列中找到特定的服务程序加以执行。

首先对首先对 PCPC 的中断控制器的中断控制器 8259A8259A的初始化,并初始化了数组的初始化,并初始化了数组 iiriirq_desc[]q_desc[]。接着从。接着从 FIRST_EXTERNAL_VECTORFIRST_EXTERNAL_VECTOR 开始,设立开始,设立 NNR_IRQSR_IRQS 个中断向量的 个中断向量的 IDTIDT 表项。常数表项。常数 FIRST_EXTERNAL_VEFIRST_EXTERNAL_VECTORCTOR 定义为 定义为 0x200x20 ,而,而 NR_IRQSNR_IRQS 则为则为 224224 。其中还跳过了用。其中还跳过了用于系统调用的向量于系统调用的向量 0x800x80 。。

Page 44: 第  3  章 中断和中断处理

3.2.3 3.2.3 中断向量表中断向量表 IDTIDT 的初始化的初始化 忽略多处理器忽略多处理器 SMPSMP 结构和结构和 SG1SG1 工作站的特殊处理,剩下的就是对系工作站的特殊处理,剩下的就是对系

统时钟的初始化。在统时钟的初始化。在 PCPC 中,定时器中,定时器 //计数器芯片计数器芯片 82548254 共有三个通共有三个通道,通道道,通道 00 是一个产生实时时钟信号的系统计时器,而程序中要设置是一个产生实时时钟信号的系统计时器,而程序中要设置的也就是通道的也就是通道 00 。用于控制。用于控制 82548254 的端口共有的端口共有 44 个,前三个分别对应个,前三个分别对应于单个通道的端口,最后一个通道对应于于单个通道的端口,最后一个通道对应于 82548254 的控制字寄存器端口。的控制字寄存器端口。

outb_p(0x34,0x43);//outb_p(0x34,0x43);//设置通道的工作方式设置通道的工作方式 //// 选通通道选通通道 00 ,先读写高字节,后读写低字节,工作于方式,先读写高字节,后读写低字节,工作于方式 22 ,二进,二进

制数制数 outb_p(LATCH&0xff,0x40);outb_p(LATCH&0xff,0x40); //// 写入低字节写入低字节 outb(LATCH>>8,0x40);outb(LATCH>>8,0x40); //// 写入高字节写入高字节 ////设置通道设置通道 00 的记数值的记数值

Page 45: 第  3  章 中断和中断处理

3.2.3 3.2.3 中断向量表中断向量表 IDTIDT 的初始化的初始化 到此已经设置好了到此已经设置好了 IDTIDT,也有了一个中断向量:,也有了一个中断向量: 00号中断号中断

时钟中断。虽然该中断服务的入口地址已经设置到中断向时钟中断。虽然该中断服务的入口地址已经设置到中断向量表中,但还没有把量表中,但还没有把 00号中断具体的中断服务程序挂到号中断具体的中断服务程序挂到 00号中断的队列中去。此时,这些中断地队列都是空的,即号中断的队列中去。此时,这些中断地队列都是空的,即使开了中断并产生了时钟中断,也只不过是让它在中断处使开了中断并产生了时钟中断,也只不过是让它在中断处理的总服务程序中空跑一趟。理的总服务程序中空跑一趟。

设置好了中断向量表,中断队列都还是空的。想要中断程设置好了中断向量表,中断队列都还是空的。想要中断程序生效,下一步就要初始化中断请求队列,并把具体的中序生效,下一步就要初始化中断请求队列,并把具体的中断服务程序挂到中断队列中去。断服务程序挂到中断队列中去。

Page 46: 第  3  章 中断和中断处理

3.2.4 3.2.4 中断请求队列的初始化中断请求队列的初始化 通用中断门是多个中断源共用的,在系统运行的过程中允通用中断门是多个中断源共用的,在系统运行的过程中允许动态改变。因此,在许动态改变。因此,在 IDTIDT的初始化阶段只是为每个中断的初始化阶段只是为每个中断向量准备一个中断请求队列,即中断请求队列的数组向量准备一个中断请求队列,即中断请求队列的数组 irq_irq_desc[]desc[] 。中断请求队列头部的数据结构是在。中断请求队列头部的数据结构是在 include/linuinclude/linux/irq.hx/irq.h 中定义的。中定义的。

Page 47: 第  3  章 中断和中断处理

3.2.4 3.2.4 中断请求队列的初始化中断请求队列的初始化 struct hw_interrupt_type {struct hw_interrupt_type { const char * typename; //const char * typename; //赋予控制器的人工可赋予控制器的人工可

读的名字读的名字 unsigned int (*startup)(unsigned int irq); //unsigned int (*startup)(unsigned int irq); //允许从给允许从给

定的控制器的定的控制器的 IRQIRQ事件发生事件发生 void (*shutdown)(unsigned int irq); //void (*shutdown)(unsigned int irq); // 禁止从给定禁止从给定

的控制器的的控制器的 IRQIRQ事件发生事件发生 void (*enable)(unsigned int irq);void (*enable)(unsigned int irq); void (*ack)(unsigned int irq);void (*ack)(unsigned int irq); void (*end)(unsigned int irq);void (*end)(unsigned int irq); void (*set_affinity)(unsiged int irq,unsigned long masvoid (*set_affinity)(unsiged int irq,unsigned long mas

k);k); };};

Page 48: 第  3  章 中断和中断处理

3.2.4 3.2.4 中断请求队列的初始化中断请求队列的初始化 typedef struct hw_interupt_type hw_irq_controller;typedef struct hw_interupt_type hw_irq_controller; typedef struct {typedef struct { unsigned int status; // IRQ status unsigned int status; // IRQ status hw_irq_controller *handler;hw_irq_controller *handler; struct irqaction *action; //IRQ action list struct irqaction *action; //IRQ action list unsigned int depth; // nested irq disables unsigned int depth; // nested irq disables //irq_desc_t //irq_desc_t 当前用户的个数。用于保证在事件处理过程当前用户的个数。用于保证在事件处理过程

中中 IRQIRQ不会被禁止不会被禁止 spinlock_t lock;spinlock_t lock; }_cacheline_aligned irq_desc_t;}_cacheline_aligned irq_desc_t; extern irq_desc_t irq_desc[NR_IRQS];extern irq_desc_t irq_desc[NR_IRQS];

Page 49: 第  3  章 中断和中断处理

3.2.4 3.2.4 中断请求队列的初始化中断请求队列的初始化 每个队列头中,用指针每个队列头中,用指针 actionaction 来维持一个由中断服务程序描述项构来维持一个由中断服务程序描述项构

成的单链表,还有一个指针成的单链表,还有一个指针 handlerhandler 指向另一个数据结构指向另一个数据结构 hw_interrhw_interrupt_typeupt_type 。。 Hw_interrupt_typeHw_interrupt_type 中的一些函数指针,用于该队列的,中的一些函数指针,用于该队列的,而不是用于具体的中断源的服务。而不是用于具体的中断源的服务。

这些函数都是在这些函数都是在 init_ISA_irqsinit_ISA_irqs中设置好的。中设置好的。 void_init init //ISA_irqsvoid_init init //ISA_irqs中设置好的中设置好的 {{ int i;int i; init_8259A(0);init_8259A(0); for (i=0;i< } irq_desc[i].handler="&i8259A_irq_type;" * demand ofor (i=0;i< } irq_desc[i].handler="&i8259A_irq_type;" * demand o

n in filled IRQs PCI *?high? { else interrupts; INTA-cycle old-style 1n in filled IRQs PCI *?high? { else interrupts; INTA-cycle old-style 16 (i6 (i

Page 50: 第  3  章 中断和中断处理

3.2.4 3.2.4 中断请求队列的初始化中断请求队列的初始化 先对先对 8259A8259A进行初始化,将开头进行初始化,将开头 1616个中断请求队列的个中断请求队列的 hh

andlerandler 指针设置成指向数据结构指针设置成指向数据结构 i8259A_irq_typei8259A_irq_type 。。 struct irqcation {struct irqcation { void (*handler)(int,void *,struct pt_regs *);void (*handler)(int,void *,struct pt_regs *); ////指向具体中断服务程序指向具体中断服务程序 unsigned long flags;unsigned long flags; unsigned long mask;unsigned long mask; const char *name;const char *name; void *dev_id;void *dev_id; struct irqaction *next;struct irqaction *next; };};

Page 51: 第  3  章 中断和中断处理

3.2.4 3.2.4 中断请求队列的初始化中断请求队列的初始化 IDTIDT 表初始化之初,每个中断服务队列都是空的。真正的中断服务要表初始化之初,每个中断服务队列都是空的。真正的中断服务要

到具体设备的初始化程序将其中断服务程序通过到具体设备的初始化程序将其中断服务程序通过 reques_irq()reques_irq()向系统向系统“登记”,挂进某个中断请求队列。“登记”,挂进某个中断请求队列。

int request_irq(unsigned int irq,int request_irq(unsigned int irq, void (*handler)(int,void*, struct pt_regs *),void (*handler)(int,void*, struct pt_regs *), unsigned long irqflags,unsigned long irqflags, const char * devname,const char * devname, void *dev_id)void *dev_id) 参数表中的参数表中的 irqirq 为中断请求队列的序号,对应中断控制器的一个通道。为中断请求队列的序号,对应中断控制器的一个通道。

这个中断请求号和这个中断请求号和 CPUCPU 所用的中断向量是不同的,中断请求号所用的中断向量是不同的,中断请求号 00 相相当于中断向量当于中断向量 0x200x20 。。 Ireflags Ireflags 是一些标志位,其中的是一些标志位,其中的 SA_SHIRQSA_SHIRQ 标标志与其它中断源共用该中断请求通道。此时,必须提供一个志与其它中断源共用该中断请求通道。此时,必须提供一个 dev_iddev_id以供区别。 以供区别。

在在 request_irqrequest_irq 中分配并设置了中分配并设置了 irqactionirqaction结构,便调用结构,便调用 setup_irqsetup_irq将其链入响应的中断请求队列。 将其链入响应的中断请求队列。

Page 52: 第  3  章 中断和中断处理

3.3 Linux2.4 3.3 Linux2.4 的软中断处理机制的软中断处理机制 根据与软件相关或和硬件相关,根据与软件相关或和硬件相关, LinuxLinux 的中断可的中断可

分为软中断和硬中断两种。软中断是一种“信号分为软中断和硬中断两种。软中断是一种“信号机制”,机制”, LinuxLinux 通过信号来产生对进程的各种中通过信号来产生对进程的各种中断操作,现在知道的信号共有断操作,现在知道的信号共有 3131 个。 个。

一般来说,软中断是由内核机制的触发事件引起一般来说,软中断是由内核机制的触发事件引起的(例如进程运行超时),但是不可忽视有大量的(例如进程运行超时),但是不可忽视有大量的软中断也是由于和硬件有关的中断引起的,例的软中断也是由于和硬件有关的中断引起的,例如当打印机端口产生一个硬件中断时,会通知和如当打印机端口产生一个硬件中断时,会通知和硬件相关的硬中断,硬中断就会产生一个软中断硬件相关的硬中断,硬中断就会产生一个软中断并送到操作系统内核里,内核就会根据这个软中并送到操作系统内核里,内核就会根据这个软中断唤醒在打印机任务队列中的睡眠进程。 断唤醒在打印机任务队列中的睡眠进程。

Page 53: 第  3  章 中断和中断处理

3.3 Linux2.4 3.3 Linux2.4 的软中断处理机制的软中断处理机制 软中断是利用硬件中断的概念,用软件方式进行模拟,实软中断是利用硬件中断的概念,用软件方式进行模拟,实

现宏观上的异步执行效果。硬中断是外部设备对现宏观上的异步执行效果。硬中断是外部设备对 CPUCPU 的的中断,软中断通常是硬中断服务程序对内核的中断,信号中断,软中断通常是硬中断服务程序对内核的中断,信号则是由内核(或其它进程)对某个进程的中断。软中断的则是由内核(或其它进程)对某个进程的中断。软中断的一种典型应用是所谓的“下半部”(一种典型应用是所谓的“下半部”( bottom halfbottom half),它),它的得名来自于将硬件中断处理分离成“上半部”和“下半的得名来自于将硬件中断处理分离成“上半部”和“下半部”两个阶段的机制:上半部在屏蔽中断的上下文中运行,部”两个阶段的机制:上半部在屏蔽中断的上下文中运行,用于完成关键性的处理动作;而下半部则处理相对不是非用于完成关键性的处理动作;而下半部则处理相对不是非常紧急的,比较耗时的动作。因此,下半部由系统自行安常紧急的,比较耗时的动作。因此,下半部由系统自行安排运行时机,不在中断服务上下文中执行。排运行时机,不在中断服务上下文中执行。 bottom halfbottom half的应用也是激励内核发展出目前的软中断机制的原因。软的应用也是激励内核发展出目前的软中断机制的原因。软中断是中断是 LinuxLinux 系统原“底半处理”的升级,在原有的基础系统原“底半处理”的升级,在原有的基础上发展的新的处理方式,以适应多上发展的新的处理方式,以适应多 CPU CPU 、多线程的软中、多线程的软中断处理。要了解软中断,必须要先了解原来的底半处理的断处理。要了解软中断,必须要先了解原来的底半处理的处理机制。 处理机制。

Page 54: 第  3  章 中断和中断处理

3.3.2 3.3.2 底半处理 底半处理 在在 LinuxLinux 内核中,内核中, bottom halfbottom half通常用通常用 "bh""bh"表示,最初用于在特权表示,最初用于在特权

级较低的上下文中完成中断服务的非关键耗时动作,现在也用于一切级较低的上下文中完成中断服务的非关键耗时动作,现在也用于一切可在低优先级的上下文中执行的异步动作。最早的可在低优先级的上下文中执行的异步动作。最早的 bottom halfbottom half实现实现是借用中断向量表的方式,在目前的是借用中断向量表的方式,在目前的 2.4.x2.4.x 内核中仍然可以看到: 内核中仍然可以看到:

static void (*bh_base[32])(void);// kernel/softirq.c static void (*bh_base[32])(void);// kernel/softirq.c 系统定义了一个函数指针数组,共有系统定义了一个函数指针数组,共有 3232 个函数指针,采用数组索引个函数指针,采用数组索引来访问,与此相对应的是一套函数: 来访问,与此相对应的是一套函数:

void init_bh(int nr,void (*routine)(void)); //void init_bh(int nr,void (*routine)(void)); // 为第为第 nrnr 个函数指针赋个函数指针赋值为值为 routine routine

void remove_bh(int nr); //void remove_bh(int nr); //动作与动作与 init_bh()init_bh()相反,卸下相反,卸下 nrnr函数指针 函数指针

void mark_bh(int nr); //void mark_bh(int nr); //标志第标志第 nrnr 个个 bottom halfbottom half可执行可执行了 了

Page 55: 第  3  章 中断和中断处理

3.3.2 3.3.2 底半处理底半处理 由于历史的原因,由于历史的原因, bh_basebh_base 各个函数指针位置大多有了预定义的意义,在各个函数指针位置大多有了预定义的意义,在v2.4.xv2.4.x 内核里有这样一个枚举: 内核里有这样一个枚举:

enum {enum { TIMER_BH = 0, TIMER_BH = 0, TQUEUE_BH, TQUEUE_BH, DIGI_BH, DIGI_BH, SERIAL_BH, SERIAL_BH, RISCOM8_BH, RISCOM8_BH, SPECIALIX_BH, SPECIALIX_BH, AURORA_BH, AURORA_BH, ESP_BH, ESP_BH, SCSI_BH, SCSI_BH, IMMEDIATE_BH, IMMEDIATE_BH, CYCLADES_BH, CYCLADES_BH, CM206_BH, CM206_BH, JS_BH, JS_BH, MACSERIAL_BH, MACSERIAL_BH, ISICOM_BH ISICOM_BH}; };

Page 56: 第  3  章 中断和中断处理

3.3.2 3.3.2 底半处理底半处理 并约定某个驱动使用某个并约定某个驱动使用某个 bottom halfbottom half 位位置,比如串口中断就约定使用置,比如串口中断就约定使用 SERIAL_BHSERIAL_BH,,现在用得多的主要是现在用得多的主要是 TIMER_BHTIMER_BH、、 TQUETQUEUE_BHUE_BH 和和 IMMEDIATE_BHIMMEDIATE_BH,但语义已经,但语义已经很不一样了,因为整个很不一样了,因为整个 bottom halfbottom half 的使的使用方式已经很不一样了,这三个函数仅仅用方式已经很不一样了,这三个函数仅仅是在接口上保持了向下兼容,在实现上一是在接口上保持了向下兼容,在实现上一直都在随着内核的软中断机制在变。在直都在随着内核的软中断机制在变。在 2.4.2.4.xx 内核里,它用的是内核里,它用的是 tasklettasklet 机制。 机制。

Page 57: 第  3  章 中断和中断处理

3.3.2 3.3.2 与底半处理相关的数据结构与底半处理相关的数据结构 : :

某些特殊时刻并不能在内核中执行操作。例如中断处理过某些特殊时刻并不能在内核中执行操作。例如中断处理过程中。当中断发生时,处理器将停止当前正在执行的指令程中。当中断发生时,处理器将停止当前正在执行的指令 ,, 操作系统将中断发送到相应的设备驱动程序上去处理。操作系统将中断发送到相应的设备驱动程序上去处理。此时系统中其他程序都不能运行此时系统中其他程序都不能运行 , , 因此,在这段时间内,因此,在这段时间内,设备驱动程序要以最快的速度完成中断处理,设备驱动的设备驱动程序要以最快的速度完成中断处理,设备驱动的中断处理过程不宜过长。中断处理过程不宜过长。 Linux Linux 内核利用底层处理过程帮内核利用底层处理过程帮助实现中断的快速处理。对于在中断处理过程之外进行的助实现中断的快速处理。对于在中断处理过程之外进行的其他大部分工作,其他大部分工作, LinuxLinux底层部分处理机制可以让设备驱底层部分处理机制可以让设备驱动和动和 LinuxLinux 内核其他部分将这些工作进行排序以延迟执行。内核其他部分将这些工作进行排序以延迟执行。

Page 58: 第  3  章 中断和中断处理

3.3.2 3.3.2 与底半处理相关的数据结构与底半处理相关的数据结构 ::

系统中最多可以有系统中最多可以有 3232 个不同的底层处理过程;个不同的底层处理过程; bh_basebh_base是指向这些过程入口的指针数组。而是指向这些过程入口的指针数组。而 bh_activebh_active 和 和 bh_mbh_maskask 用来表示那些处理过程已经安装以及那些处于活动状用来表示那些处理过程已经安装以及那些处于活动状态。如果态。如果 bh_maskbh_mask 的第的第 NN位置位则表示位置位则表示 bh_basebh_base 的 第的 第NN 个元素包含底层部分处理例程。如果个元素包含底层部分处理例程。如果 bh_activebh_active 的第的第 NN位置位,则表示第位置位,则表示第 NN 个底层处理过程例程,可在调度器认个底层处理过程例程,可在调度器认为合适的时刻调用。这些索引被定义成静态的;定时器底为合适的时刻调用。这些索引被定义成静态的;定时器底层部分处理例程具有最高优先级(索引值为层部分处理例程具有最高优先级(索引值为 00 ), 控制台), 控制台底层部分处理例程其次(索引值为底层部分处理例程其次(索引值为 11)。典型的底层部分)。典型的底层部分处理例程含有与之相连的任务链表。例如 处理例程含有与之相连的任务链表。例如 immediateimmediate底底层部分处理例程通过立即任务队列(层部分处理例程通过立即任务队列( tq_immediatetq_immediate)执)执行需要被立刻执行的任务。行需要被立刻执行的任务。

Page 59: 第  3  章 中断和中断处理

3.3.2 3.3.2 与底半处理相关的数据结构与底半处理相关的数据结构 ::

图图 3.63.6给出了一个与底层部分处理相关的内核数据结构。给出了一个与底层部分处理相关的内核数据结构。bh_base bh_base 代表的指针数组中可包含 代表的指针数组中可包含 32 32 个不同的底层处个不同的底层处理过程。理过程。 bh_mask bh_mask 和 和 bh_active bh_active 的数据位分别代表对的数据位分别代表对应的底层处理过程是否安装和激活。如果 应的底层处理过程是否安装和激活。如果 bh_mask bh_mask 的第 的第 N N 位为 位为 11 ,则说明 ,则说明 bh_base bh_base 数组的第 数组的第 N N 个元素包含某个元素包含某个底层处理过程的地址;如果 个底层处理过程的地址;如果 bh_active bh_active 的第 的第 N N 位为 位为 11 ,,则说明必须由调度程序在适当的时候调用第 则说明必须由调度程序在适当的时候调用第 N N 个底层处个底层处理过程。理过程。

Page 60: 第  3  章 中断和中断处理

3.3.2 3.3.2 与底半处理相关的数据结构与底半处理相关的数据结构 ::

图图 3.6 3.6 底半处理数据结构 底半处理数据结构 31 0

31 0

bh_active

bh_mask

...

bh_base

底层处理程序 (定时器)

0

31

Page 61: 第  3  章 中断和中断处理

3.3.2 3.3.2 与底半处理相关的数据结构与底半处理相关的数据结构 ::

bh_basebh_base 数组的索引是静态定义的,定时器的底半处理过数组的索引是静态定义的,定时器的底半处理过程的地址保存在第 程的地址保存在第 0 0 个元素中,控制台底半处理过程的个元素中,控制台底半处理过程的地址保存在第 地址保存在第 1 1 个元素中,等等。当 个元素中,等等。当 bh_mask bh_mask 和 和 bh_bh_active active 表明第 表明第 N N 个底半处理过程已被安装且处于活动状个底半处理过程已被安装且处于活动状态,则调度程序会调用第 态,则调度程序会调用第 N N 个底半处理过程,该底半处个底半处理过程,该底半处理过程最终会处理与之相关的任务队列中的各个任务。因理过程最终会处理与之相关的任务队列中的各个任务。因为调度程序从第 为调度程序从第 0 0 个元素开始依次检查每个底半处理过个元素开始依次检查每个底半处理过程,因此,第 程,因此,第 0 0 个底半处理过程具有最高的优先级,第 个底半处理过程具有最高的优先级,第 31 31 个底半处理过程的优先级最低。 个底半处理过程的优先级最低。

内核中的某些底半处理过程是和特定设备相关的,而其它内核中的某些底半处理过程是和特定设备相关的,而其它一些则更一般一些。表一些则更一般一些。表 3.13.1列出了内核中通用的底半处理列出了内核中通用的底半处理过程。 过程。

Page 62: 第  3  章 中断和中断处理

3.3.2 3.3.2 与底半处理相关的数据结构与底半处理相关的数据结构 ::

当某个设备驱动程序,或内核的其它部分需要将任务排队当某个设备驱动程序,或内核的其它部分需要将任务排队进行处理时,它将任务添加到适当的系统队列中(例如,进行处理时,它将任务添加到适当的系统队列中(例如,添加到系统的定时器队列中),然后通知内核,表明需要添加到系统的定时器队列中),然后通知内核,表明需要进行底半处理。为了通知内核,只需将 进行底半处理。为了通知内核,只需将 bh_active bh_active 的相的相应数据位置为 应数据位置为 11 。例如,如果驱动程序在 。例如,如果驱动程序在 immediate immediate 队队列中将某任务排队,并希望运行 列中将某任务排队,并希望运行 IMMEDIATE IMMEDIATE 底半处理过底半处理过程来处理排队任务,则只需将 程来处理排队任务,则只需将 bh_active bh_active 的第 的第 8 8 位置为 位置为 11 。在每个系统调用结束并返回调用进程之前,调度程序。在每个系统调用结束并返回调用进程之前,调度程序要检验 要检验 bh_active bh_active 中的每个位,如果有任何一位为 中的每个位,如果有任何一位为 11 ,,则相应的底半处理过程被调用。每个底半处理过程被调用则相应的底半处理过程被调用。每个底半处理过程被调用时,时, bh_active bh_active 中的相应为被清除。中的相应为被清除。 bh_active bh_active 中的置中的置位只是暂时的,在两次调用调度程序之间 位只是暂时的,在两次调用调度程序之间 bh_active bh_active 的的值才有意义,如果 值才有意义,如果 bh_active bh_active 中没有置位,则不需要调中没有置位,则不需要调用任何底半处理过程。 用任何底半处理过程。

Page 63: 第  3  章 中断和中断处理

3.3.2 3.3.2 与底半处理相关的数据结构与底半处理相关的数据结构 ::

表表 3.1 Linux 3.1 Linux 中通用的底半处理过程中通用的底半处理过程

TIMER_BHTIMER_BH(定时器)(定时器) 在每次系统的周期性定时器中断中,该在每次系统的周期性定时器中断中,该底半处理过程被标记为活动状态,并用底半处理过程被标记为活动状态,并用来驱动内核的定时器队列机制。来驱动内核的定时器队列机制。

CONSOLE_BHCONSOLE_BH(控制台)(控制台) 该处理过程用来处理控制台消息。该处理过程用来处理控制台消息。

TQUEUE_BHTQUEUE_BH(( TTY TTY 消息队列)消息队列) 该处理过程用来处理 该处理过程用来处理 tty tty 消息。消息。

NET_BHNET_BH(网络)(网络) 用于一般网络处理用于一般网络处理 ,,作为网络层的一部作为网络层的一部分分

IMMEDIATE_BHIMMEDIATE_BH(立即)(立即) 这是一个一般性处理过程,许多设备驱这是一个一般性处理过程,许多设备驱动程序利用该过程对自己要在随后处理动程序利用该过程对自己要在随后处理的任务进行排队。 的任务进行排队。

Page 64: 第  3  章 中断和中断处理

3.3.2 3.3.2 与底半处理相关的数据结构与底半处理相关的数据结构 ::

bh_mask_countbh_mask_count:计数器。对每个:计数器。对每个 enable/disableenable/disable 请求嵌套对进行请求嵌套对进行计数。这些请求通过调用计数。这些请求通过调用 enable_bhenable_bh 和和 disable_bhdisable_bh 实现。每个禁止实现。每个禁止请求都增加计数器;每个使能请求都减小计数器。当计数器达到请求都增加计数器;每个使能请求都减小计数器。当计数器达到 00 时,时,所有未完成的禁止语句都已经被使能语句所匹配了,因此下半部分最所有未完成的禁止语句都已经被使能语句所匹配了,因此下半部分最终被重新使能(其定义见终被重新使能(其定义见 kernel/softirq.ckernel/softirq.c)。)。

bh_maskbh_mask 和和 bh_activebh_active :它们共同决定下半部分是否运行。它们两:它们共同决定下半部分是否运行。它们两个都有个都有 3232 位,而每一个下半部分都占用一位。当一个上半部分(或位,而每一个下半部分都占用一位。当一个上半部分(或者一些其它代码)决定其下半部分需要运行时,通过设置者一些其它代码)决定其下半部分需要运行时,通过设置 bh_activebh_active中的一位来标记下半部分。不管是否做这样的标记,下半部分都可以中的一位来标记下半部分。不管是否做这样的标记,下半部分都可以通过清空通过清空 bh_maskbh_mask 中的相关位来使之失效。因此,对中的相关位来使之失效。因此,对 bh_maskbh_mask 和和bh_activebh_active 进行位进行位 ANDAND 运算就能够表明应该运行哪一个下半部分。运算就能够表明应该运行哪一个下半部分。如果位与运算的结果是如果位与运算的结果是 00 ,就没有下半部分需要运行。,就没有下半部分需要运行。

Page 65: 第  3  章 中断和中断处理

3.3.2 3.3.2 与底半处理相关的数据结构与底半处理相关的数据结构 ::

bh_basebh_base:是一组简单的指向下半部分处理函数:是一组简单的指向下半部分处理函数的指针。的指针。

bh_basebh_base 代表的指针数组中可包含 代表的指针数组中可包含 32 32 个不同的个不同的底半处理程序。底半处理程序。 bh_mask bh_mask 和 和 bh_active bh_active 的数据的数据位分别代表对应的底半处理过程是否安装和激活。位分别代表对应的底半处理过程是否安装和激活。如果 如果 bh_mask bh_mask 的第 的第 N N 位为 位为 11 ,则说明 ,则说明 bh_babh_base se 数组的第 数组的第 N N 个元素包含某个底半处理过程的个元素包含某个底半处理过程的地址;如果 地址;如果 bh_active bh_active 的第 的第 N N 位为 位为 11 ,则说明,则说明必须由调度程序在适当的时候调用第 必须由调度程序在适当的时候调用第 N N 个底半处个底半处理过程。 理过程。 

Page 66: 第  3  章 中断和中断处理

3.3.2 3.3.2 与底半处理相关的数据结构与底半处理相关的数据结构 ::

由此可见,没有必要每次中断都调用下半部分;只有由此可见,没有必要每次中断都调用下半部分;只有 bh_bh_mask mask 和 和 bh_activebh_active 的对应位的与为的对应位的与为 11 时,才必须执行时,才必须执行下半部分下半部分 (do_botoom_half)(do_botoom_half)。所以,如果在上半部分中。所以,如果在上半部分中(也可能在其它地方)决定必须执行对应的半部分,那么(也可能在其它地方)决定必须执行对应的半部分,那么可以通过设置可以通过设置 bh_activebh_active 的对应位,来指明下半部分必须的对应位,来指明下半部分必须执行。当然,如果执行。当然,如果 bh_activebh_active 的对应位被置位,也不一定的对应位被置位,也不一定会马上执行下半部分,因为还必须具备另外两个条件:首会马上执行下半部分,因为还必须具备另外两个条件:首先是先是 bh_maskbh_mask 的相应位也必须被置位,另外,就是处理的相应位也必须被置位,另外,就是处理的时机,如果下半部分已经标记过需要执行了,现在又再的时机,如果下半部分已经标记过需要执行了,现在又再次标记,那么内核就简单地保持这个标记;当情况允许的次标记,那么内核就简单地保持这个标记;当情况允许的时候,内核就对它进行处理。如果在内核有机会运行其下时候,内核就对它进行处理。如果在内核有机会运行其下半部分之前给定的设备就已经发生了半部分之前给定的设备就已经发生了 100100次中断,那么内次中断,那么内核的上半部分就运行核的上半部分就运行 100100次,下半部分运行次,下半部分运行 11次。 次。

Page 67: 第  3  章 中断和中断处理

3.3.3 3.3.3 任务队列 任务队列 原始的原始的 bottom halfbottom half机制有几个很大的局限,主要的是个机制有几个很大的局限,主要的是个

数限制在数限制在 3232 个以内,随着系统硬件越来越多,软中断的个以内,随着系统硬件越来越多,软中断的应用范围越来越大,这个数目显然是不够用的。此外,每应用范围越来越大,这个数目显然是不够用的。此外,每个个 bottom halfbottom half上只能挂接一个函数,也是不够用的。因上只能挂接一个函数,也是不够用的。因此,在此,在 2.0.x2.0.x 内核里已经在用内核里已经在用 task queuetask queue(任务队列)的(任务队列)的办法对其进行了扩充,下面介绍的是办法对其进行了扩充,下面介绍的是 2.4.x2.4.x 中的实现。 中的实现。

task queuetask queue 是在系统队列数据结构的基础上建立的,下是在系统队列数据结构的基础上建立的,下面就是面就是 task queuetask queue 的数据结构,其定义位于的数据结构,其定义位于 include/lininclude/linux/tqueue.hux/tqueue.h 中: 中:

Page 68: 第  3  章 中断和中断处理

3.3.3 3.3.3 任务队列任务队列 struct tq_struct {struct tq_struct {struct list_head list; // struct list_head list; // 链表结构 链表结构 unsigned long sync; // unsigned long sync; // 初始为初始为 00,入队,入队时原子的置时原子的置 11 ,以避免重复入队 ,以避免重复入队 void (*routine)(void *); // void (*routine)(void *); // 激活时调用的激活时调用的函数 函数 void *data; // routine(data) void *data; // routine(data) }; };

typedef struct list_head task_queue; typedef struct list_head task_queue;

Page 69: 第  3  章 中断和中断处理

3.3.3 3.3.3 任务队列任务队列 在使用时,按照下列步骤进行: 在使用时,按照下列步骤进行: DECLARE_TASK_QUEUE(my_tqueue); DECLARE_TASK_QUEUE(my_tqueue); // // 定义一个定义一个 my_tqueuemy_tqueue ,实际上就是一个以,实际上就是一个以 tq_structtq_struct 为元素的为元素的 lislis

t_headt_head 队列 队列 //// 说明并定义一个说明并定义一个 tq_structtq_struct 变量变量 my_task;my_task;queue_task(&my_task,&my_tqueue); // queue_task(&my_task,&my_tqueue); // 将将 my_taskmy_task注册到注册到 my_tmy_tqueuequeue 中 中 run_task_queue(&my_tqueue); // run_task_queue(&my_tqueue); // 在适当的时候手工启动在适当的时候手工启动 my_tqumy_tqueue eue

大多数情况下,都没有必要调用大多数情况下,都没有必要调用 DECLARE_TASK_QUEUE()DECLARE_TASK_QUEUE()来定义来定义自己的自己的 task queuetask queue ,因为系统已经预定义了三个,因为系统已经预定义了三个 task queuetask queue : :

tq_timertq_timer ,由时钟中断服务程序启动;,由时钟中断服务程序启动;tq_immediatetq_immediate ,在中断返回前以及,在中断返回前以及 schedule()schedule() 函数中启动;函数中启动;tq_disktq_disk ,内存管理模块内部使用。,内存管理模块内部使用。

一般使用一般使用 tq_immediatetq_immediate 就可以完成大多数异步任务了。 就可以完成大多数异步任务了。

Page 70: 第  3  章 中断和中断处理

3.3.3 3.3.3 任务队列任务队列 run_task_queue(task_queue *list)run_task_queue(task_queue *list) 函数可用于启动函数可用于启动 listlist

中挂接的所有中挂接的所有 tasktask ,可以手动调用,也可以挂接在上面,可以手动调用,也可以挂接在上面提到的提到的 bottom halfbottom half向量表中启动。以向量表中启动。以 run_task_queuerun_task_queue()() 作为作为 bh_base[nr]bh_base[nr]的函数指针,实际上就是扩充了每个的函数指针,实际上就是扩充了每个bottom halfbottom half的函数句柄数,而系统预定义的的函数句柄数,而系统预定义的 tq_timertq_timer和和 tq_immediatetq_immediate 分别挂接在分别挂接在 TQUEUE_BHTQUEUE_BH和和 IMMEDIAIMMEDIATE_BHTE_BH 上(注意,上(注意, TIMER_BHTIMER_BH 没有这样使用,但没有这样使用,但 TQUETQUEUE_BHUE_BH 也是在也是在 do_timer()do_timer() 中启动的),从而可以扩充中启动的),从而可以扩充 bbottom halfottom half的个数。因此,并不需要手工调用的个数。因此,并不需要手工调用 run_task_run_task_queue()queue() (这原本就不合适),而只需调用(这原本就不合适),而只需调用 mark_bh(IMmark_bh(IMMEDIATE_BH)MEDIATE_BH),让,让 bottom halfbottom half机制在合适的时候调度机制在合适的时候调度它。 它。

Page 71: 第  3  章 中断和中断处理

3.3.4 tasklet 3.3.4 tasklet

上面介绍的上面介绍的 task queuetask queue 是以是以 bottom halfbottom half为基础;而在为基础;而在Linux2.4.xLinux2.4.x 中,中, bottom halfbottom half则以新引入的则以新引入的 tasklettasklet为实为实现基础。引入现基础。引入 tasklettasklet 最主要的考虑是为了更好的支持最主要的考虑是为了更好的支持 SSMPMP(多(多 CPUCPU),提高),提高 SMPSMP 的利用率:不同的的利用率:不同的 tasklettasklet可以同时运行于不同的可以同时运行于不同的 CPUCPU 上。在上。在 tasklettasklet 的源码注释中的源码注释中还说明了几点特性,可归结为一点,即同一个还说明了几点特性,可归结为一点,即同一个 tasklettasklet只只会在一个会在一个 CPUCPU 上运行。上运行。

Page 72: 第  3  章 中断和中断处理

3.3.4 tasklet3.3.4 tasklet tasklettasklet 其实就是一个函数。它的结构如下: 其实就是一个函数。它的结构如下: struct tasklet_structstruct tasklet_struct {{ struct tasklet_struct *next; // struct tasklet_struct *next; // 队列指针 队列指针 unsigned long state; // taskletunsigned long state; // tasklet 的状态,按位操作,目前定义了的状态,按位操作,目前定义了两个位的含义:两个位的含义:

//TASKLET_STATE_SCHED//TASKLET_STATE_SCHED (第(第 00 位)或位)或 TASKLET_STATE_RUNTASKLET_STATE_RUN(第(第 11 位) 位)

atomic_t count; // atomic_t count; // 引用计数,通常用引用计数,通常用 11 表示表示 disabled disabled void (*func)(unsigned long); // void (*func)(unsigned long); // 函数指针 函数指针 unsigned long data;// func(data) unsigned long data;// func(data) }; };

Page 73: 第  3  章 中断和中断处理

3.3.4 tasklet3.3.4 tasklet 把上面的结构与把上面的结构与 tq_structtq_struct 比较,可以看出,比较,可以看出, tasklettasklet扩扩充了一点功能,主要是充了一点功能,主要是 statestate属性,用于属性,用于 CPUCPU间的同步。间的同步。

tasklettasklet 的使用相当简单: 的使用相当简单: void my_tasklet_func(unsigned long); //void my_tasklet_func(unsigned long); //定义一个处理定义一个处理函数函数

DECLARE_TASKLET(my_tasklet,my_tasklet_func,datDECLARE_TASKLET(my_tasklet,my_tasklet_func,data); //a); //定义一个定义一个 tasklettasklet结构结构 my_taskletmy_tasklet ,与,与 //my_taskl//my_tasklet_func(data)et_func(data) 函数相关联,相当于函数相关联,相当于 DECLARE_TASK_QUDECLARE_TASK_QUEUE() EUE()

tasklet_schedule(&my_tasklet); //tasklet_schedule(&my_tasklet); //登记登记 my_taskletmy_tasklet ,,允许系统在适当的时候进行调度运行,相当于允许系统在适当的时候进行调度运行,相当于 //queue_t//queue_task(&my_task,&tq_immediate)ask(&my_task,&tq_immediate)和和 mark_bh(IMMEDIATmark_bh(IMMEDIATE_BH) E_BH)

Page 74: 第  3  章 中断和中断处理

3.3.4 tasklet3.3.4 tasklet 可见可见 tasklettasklet 的使用比的使用比 task queuetask queue 更简单,而且更简单,而且 tasklettasklet 还能更好的还能更好的支持支持 SMPSMP 结构,因此,在结构,因此,在 Linux 2.4.xLinux 2.4.x 内核中,内核中, tasklettasklet 是推荐的异是推荐的异步任务执行机制。除了以上提到的使用步骤外,步任务执行机制。除了以上提到的使用步骤外, tasklettasklet 机制还提供机制还提供了另外一些调用接口: 了另外一些调用接口:

DECLARE_TASKLET_DISABLED(name,function,data); DECLARE_TASKLET_DISABLED(name,function,data); ////和和 DECLARE_TASKLET()DECLARE_TASKLET()类似,不过即使被调度到也不会马上运行,类似,不过即使被调度到也不会马上运行,

必须等到必须等到 enable enable tasklet_enable(struct tasklet_struct *); // tasklettasklet_enable(struct tasklet_struct *); // tasklet 使能 使能 tasklet_disble(struct tasklet_struct *); // tasklet_disble(struct tasklet_struct *); // 禁用禁用 tasklettasklet ,只要,只要 tastas

kletklet 还没运行,则会推迟到它被还没运行,则会推迟到它被 enable enable tasklet_init(struct tasklet_struct *,void (*func)(unsigned long),untasklet_init(struct tasklet_struct *,void (*func)(unsigned long),unsigned long); // signed long); // 类似类似 DECLARE_TASKLET() DECLARE_TASKLET()

tasklet_kill(struct tasklet_struct *); // tasklet_kill(struct tasklet_struct *); // 清除指定清除指定 tasklettasklet 的可调度的可调度位,即不允许调度该位,即不允许调度该 tasklettasklet ,但不做,但不做 //tasklet//tasklet 本身的清除 本身的清除

Page 75: 第  3  章 中断和中断处理

3.3.4 tasklet3.3.4 tasklet 在在 Linux 2.4.xLinux 2.4.x 内核中, 内核中, bottom halfbottom half则是利用则是利用 tasklettasklet 机制实现的,机制实现的,

它表现在所有的它表现在所有的 bottom halfbottom half动作都以一类动作都以一类 tasklettasklet 的形式运行,这的形式运行,这类类 tasklettasklet 与一般使用的与一般使用的 tasklettasklet 不同。不同。

在在 Linux 2.4.xLinux 2.4.x 内核里,系统定义了两个内核里,系统定义了两个 tasklettasklet 队列的向量表,每个队列的向量表,每个向量对应一个向量对应一个 CPUCPU(向量表大小为系统能支持的(向量表大小为系统能支持的 CPUCPU 最大个数,最大个数, 2.2.4.x4.x 的的 SMPSMP 为为 3232 ),组成一个),组成一个 tasklettasklet 链表: 链表:

struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned;struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned;struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_alignstruct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned; ed;

另外,对于另外,对于 3232 个个 bottom halfbottom half,系统也定义了对应的,系统也定义了对应的 3232 个个 tasklettasklet结构: 结构:

struct tasklet_struct bh_task_vec[32];struct tasklet_struct bh_task_vec[32];

Page 76: 第  3  章 中断和中断处理

3.3.4 tasklet3.3.4 tasklet 在软中断子系统初始化时,这组在软中断子系统初始化时,这组 tasklettasklet 的动作被初始化的动作被初始化为为 bh_action(nr)bh_action(nr),而,而 bh_action(nr)bh_action(nr)就会去调用就会去调用 bh_babh_base[nr]se[nr]的函数指针,从而与的函数指针,从而与 bottom halfbottom half的语义挂钩。的语义挂钩。 mmark_bh(nr)ark_bh(nr) 被实现为调用被实现为调用 tasklet_hi_schedule(bh_taskltasklet_hi_schedule(bh_tasklet_vec+nr)et_vec+nr),在这个函数中,,在这个函数中, bh_tasklet_vec[nr]bh_tasklet_vec[nr]将被将被挂接在挂接在 tasklet_hi_vec[cpu]tasklet_hi_vec[cpu]链上(其中链上(其中 cpucpu为当前为当前 CPUCPU编号,也就是说哪个编号,也就是说哪个 CPUCPU 提出了提出了 bottom halfbottom half的请求,的请求,则在哪个则在哪个 CPUCPU 上执行该请求),然后激发上执行该请求),然后激发 HI_SOFTIRQHI_SOFTIRQ软中断信号,从而在软中断信号,从而在 HI_SOFTIRQHI_SOFTIRQ的中断响应中启动运行。的中断响应中启动运行。

tasklet_schedule(&my_tasklet)tasklet_schedule(&my_tasklet)将把将把 my_taskletmy_tasklet挂接挂接到到 tasklet_vec[cpu]tasklet_vec[cpu]上,激发上,激发 TASKLET_SOFTIRQTASKLET_SOFTIRQ,在,在TASKLET_SOFTIRQTASKLET_SOFTIRQ的中断响应中执行。的中断响应中执行。 HI_SOFTIRQHI_SOFTIRQ和和 TASKLET_SOFTIRQTASKLET_SOFTIRQ是是 softirqsoftirq子系统中的术语。 子系统中的术语。

Page 77: 第  3  章 中断和中断处理

3.3.5 3.3.5 软中断 软中断 从前面的讨论可以看出,从前面的讨论可以看出, task queuetask queue 基于基于 bottom halfbottom half,, bottom hbottom h

alfalf基于基于 tasklettasklet ,而,而 tasklettasklet 则基于则基于 softirqsoftirq 。可以说,。可以说, softirqsoftirq 沿用沿用的是最早的的是最早的 bottom halfbottom half思想,但在这个“思想,但在这个“ bottom half”bottom half” 机制之上,机制之上,已经实现了一个更加庞大和复杂的软中断子系统。 已经实现了一个更加庞大和复杂的软中断子系统。

struct softirq_actionstruct softirq_action {{ void(*action)(struct softirq_action *);void(*action)(struct softirq_action *); void*data;void*data; };}; static struct softirq_action softirq_vec[32] __cacheline_aligned;static struct softirq_action softirq_vec[32] __cacheline_aligned; 这个这个 softirq_vec[ ]softirq_vec[ ] 仅比仅比 bh_base[]bh_base[] 增加了增加了 action()action() 函数的参数,在函数的参数,在

执行上,执行上, softirqsoftirq 比比 bottom halfbottom half的限制更少。 的限制更少。

Page 78: 第  3  章 中断和中断处理

3.3.5 3.3.5 软中断软中断 和和 bottom halfbottom half类似,系统也通过以下枚举预定类似,系统也通过以下枚举预定

义了几个义了几个 softirq_vec[ ]softirq_vec[ ] 结构的用途: 结构的用途: enumenum {{ HI_SOFTIRQ=0,HI_SOFTIRQ=0, NET_TX_SOFTIRQ,NET_TX_SOFTIRQ, NET_RX_SOFTIRQ,NET_RX_SOFTIRQ, TASKLET_SOFTIRQTASKLET_SOFTIRQ };};

Page 79: 第  3  章 中断和中断处理

3.3.5 3.3.5 软中断软中断 HI_SOFTIRQHI_SOFTIRQ 被用于实现被用于实现 bottom halfbottom half,, TASKLET_SOFTIRQTASKLET_SOFTIRQ 用于用于公共的公共的 tasklettasklet 使用,使用, NET_TX_SOFTIRQNET_TX_SOFTIRQ 和和 NET_RX_SOFTIRQNET_RX_SOFTIRQ 用用于网络子系统的报文收发。在软中断子系统初始化(于网络子系统的报文收发。在软中断子系统初始化( softirq_initsoftirq_init()() )时,调用了)时,调用了 open_softirq()open_softirq()对对 HI_SOFTIRQHI_SOFTIRQ 和和 TASKLET_SOFTITASKLET_SOFTIRQRQ 做了初始化: 做了初始化:

void open_softirq(int nr, void (*action)(struct softirq_action*), vovoid open_softirq(int nr, void (*action)(struct softirq_action*), void *data) id *data)

open_softirq()open_softirq()会填充会填充 softirq_vec[nr]softirq_vec[nr],将,将 actionaction 和和 datadata 设为传设为传入的参数。入的参数。

TASKLET_SOFTIRQTASKLET_SOFTIRQ 填充为填充为 tasklet_action(NULL)tasklet_action(NULL),, HI_SOFTIRQHI_SOFTIRQ填充为填充为 tasklet_hi_action(NULL)tasklet_hi_action(NULL)。在。在 do_softirq()do_softirq() 函数中,这两个函数中,这两个函数会被调用,分别启动函数会被调用,分别启动 tasklet_vec[cpu]tasklet_vec[cpu]和和 tasklet_hi_vec[cpu]tasklet_hi_vec[cpu]链上的链上的 tasklettasklet 运行。 运行。

static inline void __cpu_raise_softirq(int cpu, int nr) static inline void __cpu_raise_softirq(int cpu, int nr) 这个函数用来激活软中断,实际上就是第这个函数用来激活软中断,实际上就是第 cpucpu 号号 CPUCPU 的第的第 nrnr 号软中号软中

断的断的 activeactive 位置位置 11 。在。在 do_softirq()do_softirq()中将判断这个中将判断这个 activeactive 位。位。 taskltasklet_schedule()et_schedule()和和 tasklet_hi_schedule()tasklet_hi_schedule()都会调用这个函数。 都会调用这个函数。

Page 80: 第  3  章 中断和中断处理

3.3.5 3.3.5 软中断软中断 do_softirq()do_softirq()有有 44 个执行时机,分别是:个执行时机,分别是:

(1) (1) 从系统调用中返回(从系统调用中返回( arch/i386/Kernel/entry.S::ENTRY(ret_froarch/i386/Kernel/entry.S::ENTRY(ret_from_sys_call)m_sys_call) ))

(2) (2) 从异常中返回(从异常中返回( arch/i386/Kernel/entry.S::ret_from_exceptioarch/i386/Kernel/entry.S::ret_from_exceptionn 标号)标号)

(3) (3) 调度程序中(调度程序中( kernel/sched.c::schedule()kernel/sched.c::schedule() )) (4) (4) 以及处理完硬件中断之后(以及处理完硬件中断之后( kernel/irq.c::do_IRQ()kernel/irq.c::do_IRQ() )。)。 它将遍历所有的它将遍历所有的 softirq_vecsoftirq_vec ,依次启动其中的,依次启动其中的 action()action()。需要注意。需要注意

的是,软中断服务程序,不允许在硬中断服务程序中执行,也不允许的是,软中断服务程序,不允许在硬中断服务程序中执行,也不允许在软中断服务程序中嵌套执行,但允许多个软中断服务程序同时在多在软中断服务程序中嵌套执行,但允许多个软中断服务程序同时在多个个 CPUCPU 上并发。 上并发。

Page 81: 第  3  章 中断和中断处理

3.3.6 3.3.6 使用示例 使用示例 softirqsoftirq作为一种底层机制,很少由内核程序员直接使用。作为一种底层机制,很少由内核程序员直接使用。

因此,下面仅对其余几种软中断机制给出使用范例。 因此,下面仅对其余几种软中断机制给出使用范例。

1.bottom half 1.bottom half 原有的原有的 bottom halfbottom half用法在用法在 drivers/char/serial.cdrivers/char/serial.c 中还能中还能

看到,包括三个步骤: 看到,包括三个步骤: init_bh(SERIAL_BH,do_serial_bh); //init_bh(SERIAL_BH,do_serial_bh); //在串口设备的初在串口设备的初始化函数始化函数 rs_init()rs_init() 中,中, do_serial_bh()do_serial_bh() 是处理函数是处理函数mark_bh(SERIAL_BH); //mark_bh(SERIAL_BH); //在在 rs_sched_event()rs_sched_event() 中,中,这个函数由中断处理例程调用这个函数由中断处理例程调用remove_bh(SERIAL_BH); //remove_bh(SERIAL_BH); //在串口设备的结束函数在串口设备的结束函数rs_fini()rs_fini() 中调用 中调用

Page 82: 第  3  章 中断和中断处理

3.3.6 3.3.6 使用示例使用示例 尽管逻辑上还是这么三步,但在尽管逻辑上还是这么三步,但在 do_serial_bh()do_serial_bh() 函数中的函数中的

动作却是启动一个动作却是启动一个 task queuetask queue:: run_task_queue(&tq_run_task_queue(&tq_serial)serial),而在,而在 rs_sched_event()rs_sched_event() 中,中, mark_bh()mark_bh()之前调之前调用的则是用的则是 queue_task(...,&tq_serial)queue_task(...,&tq_serial),也就是说串口,也就是说串口 bobottom halfttom half已经结合已经结合 task queuetask queue 使用了。而那些更通用一使用了。而那些更通用一些的些的 bottom halfbottom half,比如,比如 IMMEDIATE_BHIMMEDIATE_BH ,更是必须要,更是必须要与与 task queuetask queue结合使用,而且一般情况下,结合使用,而且一般情况下, task queuetask queue也很少独立使用,而是与也很少独立使用,而是与 bottom halfbottom half 结合,这在下面的结合,这在下面的task queuetask queue 使用示例中可以清楚地看到。 使用示例中可以清楚地看到。

Page 83: 第  3  章 中断和中断处理

3.3.6 3.3.6 使用示例使用示例 2.task queue 2.task queue 一般来说,程序员很少自己定义一般来说,程序员很少自己定义 task queuetask queue ,而是结合,而是结合 bottom halfbottom half,,

直接使用系统预定义的直接使用系统预定义的 tq_immediatetq_immediate 等,尤以等,尤以 tq_immediatetq_immediate 使用使用最多。下节是选自最多。下节是选自 drivers/block/floppy.cdrivers/block/floppy.c 的代码段: 的代码段:

static struct tq_struct floppy_tq; //static struct tq_struct floppy_tq; //定义一个定义一个 tq_structtq_struct结构变量结构变量 flofloppy_tqppy_tq,不需要作其它初始化动作 ,不需要作其它初始化动作

static void schedule_bh( void (*handler)(void*) )static void schedule_bh( void (*handler)(void*) ){{floppy_tq.routine = (void *)(void *) handler;floppy_tq.routine = (void *)(void *) handler; // //指定指定 floppy_tqfloppy_tq的调用函数为的调用函数为 handlerhandler ,不需要考虑,不需要考虑 floppy_tfloppy_tqq中的其它域中的其它域queue_task(&floppy_tq, &tq_immediate);queue_task(&floppy_tq, &tq_immediate); // //将将 floppy_tqfloppy_tq加入到加入到 tq_immediatetq_immediate 中中mark_bh(IMMEDIATE_BH);mark_bh(IMMEDIATE_BH); // //激活激活 IMMEDIATE_BHIMMEDIATE_BH,由上所述可知,这实际上将引发一个软,由上所述可知,这实际上将引发一个软中断来执行中断来执行

// tq_immediate// tq_immediate 中挂接的各个函数中挂接的各个函数} }

Page 84: 第  3  章 中断和中断处理

3.3.6 3.3.6 使用示例使用示例 当然,也可以定义并使用自己的当然,也可以定义并使用自己的 task queuetask queue ,而不用,而不用 tqtq_immediate_immediate ,在,在 drivers/char/serial.cdrivers/char/serial.c 中提到的中提到的 tq_seritq_serialal 就是串口驱动自己定义的: 就是串口驱动自己定义的:

static DECLARE_TASK_QUEUE(tq_serial); static DECLARE_TASK_QUEUE(tq_serial); 此时就需要自行调用此时就需要自行调用 run_task_queue(&tq_serial)run_task_queue(&tq_serial)来启来启

动其中的函数,因此并不常用。 动其中的函数,因此并不常用。

Page 85: 第  3  章 中断和中断处理

3.3.6 3.3.6 使用示例使用示例 3.tasklet 3.tasklet

这是比这是比 task queuetask queue 和和 bottom halfbottom half更加强大的一套软中断机制,使更加强大的一套软中断机制,使用上也相对简单,见下面代码段: 用上也相对简单,见下面代码段:

1:void foo_tasklet_action(unsigned long t);1:void foo_tasklet_action(unsigned long t); 2:unsigned long stop_tasklet;2:unsigned long stop_tasklet; 3:DECLARE_TASKLET(foo_tasklet, foo_tasklet_action, 0);3:DECLARE_TASKLET(foo_tasklet, foo_tasklet_action, 0); 4:void foo_tasklet_action(unsigned long t)4:void foo_tasklet_action(unsigned long t) 5:{5:{ 6://do something6://do something 7:7: 8://reschedule8://reschedule 9:if(!stop_tasklet)9:if(!stop_tasklet) 10:tasklet_schedule(&foo_tasklet);10:tasklet_schedule(&foo_tasklet);

Page 86: 第  3  章 中断和中断处理

3.3.6 3.3.6 使用示例使用示例 11:}11:} 12:void foo_init(void)12:void foo_init(void) 13:{13:{ 14:stop_tasklet=0;14:stop_tasklet=0; 15:tasklet_schedule(&foo_tasklet);15:tasklet_schedule(&foo_tasklet); 16:}16:} 17:void foo_clean(void)17:void foo_clean(void) 18:{18:{ 19:stop_tasklet=1;19:stop_tasklet=1; 20:tasklet_kill(&foo_tasklet);20:tasklet_kill(&foo_tasklet); 21:}21:}

Page 87: 第  3  章 中断和中断处理

3.3.6 3.3.6 使用示例使用示例 这是个利用一个反复执行的这是个利用一个反复执行的 tasklettasklet 来完成一定的工作的比较完整的来完成一定的工作的比较完整的

代码段。首先在第代码段。首先在第 33 行定义行定义 foo_taskletfoo_tasklet 与相应的动作函数与相应的动作函数 foo_taskfoo_tasklet_actionlet_action 相关联,并指定相关联,并指定 foo_tasklet_action()foo_tasklet_action()的参数为的参数为 00 。虽然。虽然此处以此处以 00 为参数,但也同样可以指定有意义的其它参数值,但需要注为参数,但也同样可以指定有意义的其它参数值,但需要注意的是,这个参数值在定义的时候必须是有固定值的变量或常数(如意的是,这个参数值在定义的时候必须是有固定值的变量或常数(如上例),也就是说可以定义一个全局变量,将其地址作为参数传给上例),也就是说可以定义一个全局变量,将其地址作为参数传给 fofoo_tasklet_action()o_tasklet_action(),例如: ,例如:

int flags;int flags; DECLARE_TASKLET(foo_tasklet,foo_tasklet_action,&flags);DECLARE_TASKLET(foo_tasklet,foo_tasklet_action,&flags); void foo_tasklet_action(unsigned long t)void foo_tasklet_action(unsigned long t) {{ int flags=*(int *)t;int flags=*(int *)t; ...... } }

Page 88: 第  3  章 中断和中断处理

3.3.6 3.3.6 使用示例使用示例 这样就可以通过改变这样就可以通过改变 flagsflags的值将信息带入的值将信息带入 tasklettasklet 中。中。

直接在直接在 DECLARE_TASKLETDECLARE_TASKLET处填写处填写 flagsflags,, gccgcc 会报会报 "ini"initializer element is not constant"tializer element is not constant"错。 错。

第第 99 、、 1010行是一种行是一种 RESCHEDULERESCHEDULE的技术。知道,一个的技术。知道,一个 ttaskletasklet 执行结束后,它就从执行队列里删除了,要想重新执行结束后,它就从执行队列里删除了,要想重新让它转入运行,必须重新调用让它转入运行,必须重新调用 tasklet_schedule()tasklet_schedule() ,调用,调用的时机可以是某个事件发生的时候,也可以是像这样在的时机可以是某个事件发生的时候,也可以是像这样在 tataskletsklet 动作中。而这种动作中。而这种 reschedulereschedule技术将导致技术将导致 tasklettasklet永永远运行,因此在子系统退出时,应该有办法停止远运行,因此在子系统退出时,应该有办法停止 tasklettasklet 。。stop_taskletstop_tasklet变量和变量和 tasklet_kill()tasklet_kill() 就是干这个的。 就是干这个的。

Page 89: 第  3  章 中断和中断处理

3.3.6 3.3.6 使用示例使用示例 下面,进入软中断处理部份(下面,进入软中断处理部份( softirq.csoftirq.c):): 由由 softirq.csoftirq.c 的代码阅读中,可以知道,在系统的初始化的代码阅读中,可以知道,在系统的初始化

过程中(过程中( softirq_init()softirq_init() ),它使用了两个数组:),它使用了两个数组: bh_tasbh_task_vec[32],softirq_vec[32]k_vec[32],softirq_vec[32]。其中,。其中, bh_task_vec[32]bh_task_vec[32] 填填入了入了 3232 个个 bh_action()bh_action() 的入口地址,但的入口地址,但 soft_vec[32]soft_vec[32]中,中,只有只有 softirq_vec[0],softirq_vec[0], 和和 softirq_vec[3]softirq_vec[3]分别填入了分别填入了 taskletasklet_action()t_action() 和和 tasklet_hi_action()tasklet_hi_action()的地址。其余的保留它的地址。其余的保留它用。用。

Page 90: 第  3  章 中断和中断处理

3.3.6 3.3.6 使用示例使用示例 当发生软中断时,系统并不急于处理,只是将相应的当发生软中断时,系统并不急于处理,只是将相应的 CPUCPU 的中断状的中断状

态结构中的态结构中的 active active 的相应的位置位,并将相应的处理函数挂到相应的相应的位置位,并将相应的处理函数挂到相应的队列的队列 ,, 然后等待调度时机来临(如:然后等待调度时机来临(如: schedule(),schedule(),系统调用返回异常时,硬中断处理结束时等),系统调用系统调用返回异常时,硬中断处理结束时等),系统调用 do_softirqdo_softirq()()来测试来测试 activeactive 位,再调用被激活的进程在这处过程中,软中断的处位,再调用被激活的进程在这处过程中,软中断的处理与底半处理有了差别,理与底半处理有了差别, active active 和和 maskmask 不再对应不再对应 bh_base[nr], bh_base[nr], 而而是对应是对应 softirq_vec[32]softirq_vec[32]。在。在 softirq.csoftirq.c 中,只涉及了中,只涉及了 softirq_vec[0]softirq_vec[0]、、softirq_vec[3]softirq_vec[3]。这两者分别调用了。这两者分别调用了 tasklet_action()tasklet_action()和和 tasklet_hi_tasklet_hi_action()action()来进行后续处理。这两个过程比较相似,大致如下: 来进行后续处理。这两个过程比较相似,大致如下:

(( 11 ) 锁) 锁 CPUCPU 的的 tasklet_vec[cpu]tasklet_vec[cpu]链表,取出链表,将原链表清空,链表,取出链表,将原链表清空,解锁,还给系统。解锁,还给系统。

(( 22 ) 对链表进行逐个处理。) 对链表进行逐个处理。 (( 33 ) 有无法处理的,() 有无法处理的,( task_trylock(t)task_trylock(t) 失败,可能有别的进程锁失败,可能有别的进程锁

定),插回系统链表。至此,系统完成了一次软中断的处理。 定),插回系统链表。至此,系统完成了一次软中断的处理。

Page 91: 第  3  章 中断和中断处理

3.3.7 3.3.7 讨论讨论 (( 11) ) bh_base[]bh_base[] 依然存在,但应在何处调用?依然存在,但应在何处调用? (( 22) ) tasklet_vec[cpu]tasklet_vec[cpu]队列是何时挂上的? 队列是何时挂上的?

再次考察再次考察 softirq.c softirq.c 的的 bh_action()bh_action() 部份,发现有两个判部份,发现有两个判断语句:断语句:

(( 11)) if(!spin_trylock(&global_bh_lock))goto:rescue if(!spin_trylock(&global_bh_lock))goto:rescue 指明如果指明如果 global_bh_lock global_bh_lock 不能被锁上不能被锁上 (( 已被其它进程锁已被其它进程锁上上 )),则转而执行,则转而执行 rescuerescue ,将,将 bh_base[nr]bh_base[nr] 挂至挂至 tasklettasklet_hi_vec[cpu]_hi_vec[cpu]队列中。等候中断调度。队列中。等候中断调度。

(( 22)) if(!hardirq_trylock(cpu)) goto tescue unlock if(!hardirq_trylock(cpu)) goto tescue unlock 此此时有硬中断发生,放入队列推迟执行。若为空闲,现在执时有硬中断发生,放入队列推迟执行。若为空闲,现在执行。 行。

Page 92: 第  3  章 中断和中断处理

3.3.7 3.3.7 讨论讨论 这部分对应底半处理的程序,这部分对应底半处理的程序, bh_base[ ]bh_base[ ]的延时处理正的延时处理正

是底半处理的特点。如果没有其它函数挂入是底半处理的特点。如果没有其它函数挂入 tasklet_hi_vtasklet_hi_vec[cpu]ec[cpu]队列,那队列,那 tasklet_hi_vec[cpu]tasklet_hi_vec[cpu]就完全对应着就完全对应着 bhbh_base[ ]_base[ ] 底半处理。底半处理。

在在 bh_action()bh_action() 中,把中,把 bh_ation()bh_ation()挂入挂入 tasklet_hi_vec[ctasklet_hi_vec[cpu]pu]的是的是 mark_bh()mark_bh()。在整个源码树中查找,发现调用。在整个源码树中查找,发现调用mark_bh()mark_bh()的函数很多。可以理解为,在软中断产生之时,的函数很多。可以理解为,在软中断产生之时,相关的函数会调用相关的函数会调用 mark_bh()mark_bh(),将,将 bh_actionbh_action挂上挂上 taskltasklet_hi_vecet_hi_vec队列,而队列,而 bh_action()bh_action() 的作用是在发现的作用是在发现 bh_babh_base[nr]se[nr] 暂时无法处理时重返队列。暂时无法处理时重返队列。

由此,可推测由此,可推测 tasklet_vectasklet_vec队列的挂接应与此相似,查看队列的挂接应与此相似,查看interrupt.hinterrupt.h ,找到,找到 tasklet_schedule()tasklet_schedule() 函数: 函数:

Page 93: 第  3  章 中断和中断处理

3.3.7 3.3.7 讨论讨论 157 static inline void tasklet_schedule(struct tasklet_struct *t)157 static inline void tasklet_schedule(struct tasklet_struct *t) 158 {158 { 159 if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {159 if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) { 160 int cpu = smp_processor_id();160 int cpu = smp_processor_id(); 161 unsigned long flags;161 unsigned long flags; 162162 163 local_irq_save(flags);163 local_irq_save(flags); 164 t->next = tasklet_vec[cpu].list;164 t->next = tasklet_vec[cpu].list; 165 tasklet_vec[cpu].list = t; 165 tasklet_vec[cpu].list = t; /*插入队列。/*插入队列。 166 __cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);166 __cpu_raise_softirq(cpu, TASKLET_SOFTIRQ); 167 local_irq_restore(flags);167 local_irq_restore(flags); 168 }168 } 169 } 169 }

Page 94: 第  3  章 中断和中断处理

3.3.7 3.3.7 讨论讨论 它为它为 tasklet_vec[cpu]tasklet_vec[cpu]队列的建立起着重要的作用,在源队列的建立起着重要的作用,在源

码树中,它亦被多个模块调用。码树中,它亦被多个模块调用。 至此,可以描绘一幅完整的软中断处理图了。至此,可以描绘一幅完整的软中断处理图了。 下面讨论下面讨论 do_softirq()do_softirq() 的的 softirq_vec[32]softirq_vec[32],在,在 interrupt.interrupt.

hh 中有如下定义: 中有如下定义: 56 enum56 enum 57 {57 { 58 HI_SOFTIRQ=0,58 HI_SOFTIRQ=0, 59 NET_TX_SOFTIRQ,59 NET_TX_SOFTIRQ, 60 NET_RX_SOFTIRQ,60 NET_RX_SOFTIRQ, 61 TASKLET_SOFTIRQ61 TASKLET_SOFTIRQ 62 }; 62 };

Page 95: 第  3  章 中断和中断处理

3.3.7 3.3.7 讨论讨论 这这 44 个变量都是个变量都是 softirq_vec[]softirq_vec[] 的下标,的下标, do_softirq()do_softirq() 也也

将会处理将会处理 NETNET__ TXTX__ SOFTIRQSOFTIRQ和和 NETNET__ RXRX__ SOFTISOFTIRQRQ。。

在在 hi_tasklethi_tasklet(也就是一般用于(也就是一般用于 bhbh 的)的处理中,在处的)的处理中,在处理完当前的队列后,会将补充的队列重新挂上,然后标记理完当前的队列后,会将补充的队列重新挂上,然后标记(不管是否补充队列里面有(不管是否补充队列里面有 tasklettasklet): ):

local_irq_disable();local_irq_disable(); t->next = tasklet_hi_vec[cpu].list;t->next = tasklet_hi_vec[cpu].list; tasklet_hi_vec[cpu].list = t;tasklet_hi_vec[cpu].list = t; __cpu_raise_softirq(cpu, HI_SOFTIRQ);__cpu_raise_softirq(cpu, HI_SOFTIRQ); local_irq_enable(); local_irq_enable();

Page 96: 第  3  章 中断和中断处理

3.3.7 3.3.7 讨论讨论 因此,对因此,对 mark_bhmark_bh 不需要设置这个不需要设置这个 activeactive位。对于一般的位。对于一般的 tasklettasklet 也一样: 也一样: local_irq_disable();local_irq_disable(); t->next = tasklet_vec[cpu].list;t->next = tasklet_vec[cpu].list; tasklet_vec[cpu].list = t;tasklet_vec[cpu].list = t; __cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ); local_irq_enable(); local_irq_enable(); 其它的设置,可以检索上面的其它的设置,可以检索上面的 __cpu_raise_softirq __cpu_raise_softirq :: bottom half, softirq, tasklet, tqueuebottom half, softirq, tasklet, tqueue [bottom half][bottom half] bh_base[32]bh_base[32] || \/\/ bh_action();bh_action(); || \/\/ bh_task_vec[32];bh_task_vec[32]; | mark_bh(), tasklet_hi_schedule()| mark_bh(), tasklet_hi_schedule() \/\/

Page 97: 第  3  章 中断和中断处理

3.3.7 3.3.7 讨论讨论 task_hi_action task_hi_action bh_basebh_base 对应的是对应的是 3232 个函数,这些函数在个函数,这些函数在 bh_action()bh_action()中调用中调用static void bh_action(unsigned long nr)static void bh_action(unsigned long nr){{int cpu = smp_processor_id(); int cpu = smp_processor_id();

if (!spin_trylock(&global_bh_lock))if (!spin_trylock(&global_bh_lock))goto resched; goto resched;

if (!hardirq_trylock(cpu))if (!hardirq_trylock(cpu))goto resched_unlock; goto resched_unlock;

if (bh_base[nr])if (bh_base[nr])bh_base[nr](); bh_base[nr]();

hardirq_endlock(cpu);hardirq_endlock(cpu);spin_unlock(&global_bh_lock);spin_unlock(&global_bh_lock);return; return;

resched_unlock:resched_unlock:spin_unlock(&global_bh_lock);spin_unlock(&global_bh_lock);resched:resched:mark_bh(nr);mark_bh(nr);} }

Page 98: 第  3  章 中断和中断处理

3.3.7 3.3.7 讨论讨论 在软中断初始化时,将在软中断初始化时,将 bh_action()bh_action() 放到放到 bb

h_task_vec[32]h_task_vec[32]中,中, bh_task_vec[32]bh_task_vec[32]中中元素的类型是元素的类型是 tasklet_struct,tasklet_struct,系统使用系统使用 mmark_bh()ark_bh()或或 task_hi_schedule()task_hi_schedule() 函数将它函数将它挂到挂到 task_hi_vec[]task_hi_vec[]的对列中,在系统调用的对列中,在系统调用do_softirq()do_softirq()时执行。 时执行。

Page 99: 第  3  章 中断和中断处理

3.3.7 3.3.7 讨论讨论 static inline void mark_bh(int nr)static inline void mark_bh(int nr){{tasklet_hi_schedule(bh_task_vec+nr);tasklet_hi_schedule(bh_task_vec+nr);} }

static inline void tasklet_hi_schedule(struct tasklet_struct *t)static inline void tasklet_hi_schedule(struct tasklet_struct *t){{if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {int cpu = smp_processor_id();int cpu = smp_processor_id();unsigned long flags; unsigned long flags;

local_irq_save(flags);local_irq_save(flags);t->next = tasklet_hi_vec[cpu].list;t->next = tasklet_hi_vec[cpu].list;tasklet_hi_vec[cpu].list = t;tasklet_hi_vec[cpu].list = t;__cpu_raise_softirq(cpu, HI_SOFTIRQ);__cpu_raise_softirq(cpu, HI_SOFTIRQ);local_irq_restore(flags);local_irq_restore(flags);}}} }

[softirq][softirq]softirq_vec[32];softirq_vec[32];struct softirq_actionstruct softirq_action{{void (*action)(struct softirq_action *);void (*action)(struct softirq_action *);void *data;void *data;}; };

Page 100: 第  3  章 中断和中断处理

3.3.7 3.3.7 讨论讨论 软中断对应一个软中断对应一个 softirq_actionsoftirq_action 的结构,在的结构,在 do_softirq()do_softirq()中调用相应中调用相应

的的 action()action() 做处理。做处理。软中断初始化时只设置了软中断初始化时只设置了 00 ,, 33 两项,对应的两项,对应的 actionaction 是是 task_hi_acttask_hi_actionion 和和 task_action. task_action.

1: task_hi_action1: task_hi_action/\/\||tasklet_hi_vec[NR_CPU] tasklet_hi_vec[NR_CPU]

struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_alignstruct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;ed;struct tasklet_headstruct tasklet_head{{struct tasklet_struct *list;struct tasklet_struct *list;} __attribute__ ((__aligned__(SMP_CACHE_BYTES))); } __attribute__ ((__aligned__(SMP_CACHE_BYTES)));

task_hi_actiontask_hi_action 处理的对象是一个处理的对象是一个 tasklettasklet 的队列,每个的队列,每个 CPUCPU 都有一都有一个对应的个对应的 tasklettasklet 队列,队列,它在它在 tasklet_hi_scheduletasklet_hi_schedule 中动态添加。 中动态添加。

Page 101: 第  3  章 中断和中断处理

3.3.7 3.3.7 讨论讨论 3: task_action3: task_action/\/\||tasklet_vec[NR_CPU] tasklet_vec[NR_CPU]

[tasklet][tasklet]struct tasklet_structstruct tasklet_struct{ {

struct tasklet_struct *next;struct tasklet_struct *next;unsigned long state;unsigned long state;atomic_t count;atomic_t count;void (*func)(unsigned long);void (*func)(unsigned long);unsigned long data;unsigned long data;};};

Page 102: 第  3  章 中断和中断处理

3.3.7 3.3.7 讨论讨论 从上面的分析来看,从上面的分析来看, tasklettasklet只是一个调用实体,在只是一个调用实体,在 do_sdo_softirq()oftirq() 中被调用。中被调用。 softirqsoftirq的组织和结构才是最重要的。 的组织和结构才是最重要的。

Page 103: 第  3  章 中断和中断处理

3.4 3.4 中断处理全过程 中断处理全过程 对于对于 0-310-31 号中断向量,被保留用来处理异常事件;号中断向量,被保留用来处理异常事件; 0x800x80

中断向量用来作为系统调用的总入口点;而其它中断向量,中断向量用来作为系统调用的总入口点;而其它中断向量,则用来处理外部设备中断;这三者的处理过程都是不一样则用来处理外部设备中断;这三者的处理过程都是不一样的。 的。

Page 104: 第  3  章 中断和中断处理

3.4.1 3.4.1 异常的处理全过程异常的处理全过程 IntelIntel公司保留公司保留 0-310-31 号中断向量用来处理异常事件,异常号中断向量用来处理异常事件,异常

的处理程序由操作系统提供。并在初始化时把处理程序的的处理程序由操作系统提供。并在初始化时把处理程序的入口等级在对应的中断向量表项中。当产生一个异常时,入口等级在对应的中断向量表项中。当产生一个异常时,处理机就会自动把控制转移到相应的处理程序的入口,运处理机就会自动把控制转移到相应的处理程序的入口,运行相应的处理程序,进行相应的处理后,返回原中断处。行相应的处理程序,进行相应的处理后,返回原中断处。对于这对于这 3232 个处理异常的中断向量,个处理异常的中断向量, LinuxLinux只提供了表只提供了表 3.23.2中的中的 0-170-17号中断向量的处理程序;号中断向量的处理程序; 17-3117-31 号中断向量空号中断向量空着未用。 着未用。

Page 105: 第  3  章 中断和中断处理

3.4.1 3.4.1 异常的处理全过程异常的处理全过程 表表 3.2 3.2 中断向量和异常事件对应表 中断向量和异常事件对应表

中断向量号 中断向量号 异常事件  异常事件   LinuxLinux 的处理程序的处理程序

00 除法错误除法错误 Divide_errorDivide_error

11 调试异常调试异常 DebugDebug

22 NMINMI中断中断 NmiNmi

33 单字节,单字节, int 3int 3 Int3Int3

44 溢出溢出 OverflowOverflow

55 边界监测中断边界监测中断 BoundsBounds

66 无效操作码无效操作码 Invalid_opInvalid_op

77 设备不可用设备不可用 Device_not_availableDevice_not_available

88 双重故障双重故障 Double_faultDouble_fault

99 协处理器段溢出协处理器段溢出 Coprocessor_segment_overrunCoprocessor_segment_overrun

1010 无效无效 TSSTSS Incalid_tssIncalid_tss

1111 缺段中断缺段中断 Segment_not_presentSegment_not_present

1212 堆栈异常堆栈异常 Stack_segmentStack_segment

1313 页异常页异常 General_protectionGeneral_protection

1414 一般保护异常一般保护异常 Page_faultPage_fault

1515 Spurious_interrupt_bugSpurious_interrupt_bug

1616 协处理器出错协处理器出错 Coprocessor_errorCoprocessor_error

1717 对齐检查中断对齐检查中断 Alignment_checkAlignment_check

Page 106: 第  3  章 中断和中断处理

3.4.2 3.4.2 外部设备中断处理的全过程外部设备中断处理的全过程 对于对于 0-310-31 号和号和 0x800x80之外的中断向量,主要用来处理外部之外的中断向量,主要用来处理外部

设备中断;在系统完成初始化后,其中断处理过程如下: 设备中断;在系统完成初始化后,其中断处理过程如下: 外部中断是外部硬件外部中断是外部硬件 ((如时钟如时钟 )) 引起的中断,当外部设备引起的中断,当外部设备需要处理机进行中断服务时,它就会通过中断控制器要求需要处理机进行中断服务时,它就会通过中断控制器要求处理机进行中断服务。如果 处理机进行中断服务。如果 CPU CPU 这时可以处理中断,这时可以处理中断, CCPUPU 将根据中断控制器提供的中断向量号和中断描述符表将根据中断控制器提供的中断向量号和中断描述符表(( IDTIDT )中的登记的地址信息,自动跳转到相应的)中的登记的地址信息,自动跳转到相应的 interrinterrupt[i]upt[i]地址,即中断入口;在进行一些简单的但必要的处地址,即中断入口;在进行一些简单的但必要的处理后,最后都会调用函数理后,最后都会调用函数 do_IRQ , do_IRQdo_IRQ , do_IRQ 函数调用 函数调用 dodo_8259A_IRQ _8259A_IRQ 而而 do_8259A_IRQdo_8259A_IRQ在进行必要的处理后,在进行必要的处理后,将调用已与此将调用已与此 IRQIRQ 建立联系建立联系 irqactionirqaction 中的处理函数,以中的处理函数,以进行相应的中断处理。最后处理机将跳转到进行相应的中断处理。最后处理机将跳转到 ret_from_intret_from_intrr 进行必要处理后,整个中断处理结束返回。 进行必要处理后,整个中断处理结束返回。

Page 107: 第  3  章 中断和中断处理

3.4.2 3.4.2 外部设备中断处理的全过程外部设备中断处理的全过程 一个完整的外部中断包括一个完整的外部中断包括 44 件工作: 件工作: (1) (1) 将外部设备和中断芯片相应的管脚接上;将外部设备和中断芯片相应的管脚接上; (2) (2) 对中断芯片设置,使得特定管脚的中断能映射到对中断芯片设置,使得特定管脚的中断能映射到 CPU CPU IDTIDT 特定的位置;特定的位置;

(3) (3) 在程序中包含这些中断入口;在程序中包含这些中断入口; (4) (4) 在中断向量表里设置向量相应的入口地址。 在中断向量表里设置向量相应的入口地址。 这些工作需要在外部中断流程里描述。 这些工作需要在外部中断流程里描述。 由于硬件设备可能共用一个中断,在统一的函数中会有相由于硬件设备可能共用一个中断,在统一的函数中会有相

应的结构来处理,也就是有应的结构来处理,也就是有 1616个结构分别处理相应的个结构分别处理相应的 1616个中断,特定的硬件驱动需要将自己的处理函数挂接到特个中断,特定的硬件驱动需要将自己的处理函数挂接到特定的结构上。 定的结构上。

Page 108: 第  3  章 中断和中断处理

3.4.2 3.4.2 外部设备中断处理的全过程外部设备中断处理的全过程 因为因为 PCIPCI的设备驱动程序总是知道它们的中断号,在的设备驱动程序总是知道它们的中断号,在 PCIPCI结构中不会有问题。但对于结构中不会有问题。但对于 ISAISA结构来说,一个设备驱动结构来说,一个设备驱动程序可能不知道设备将使用哪一个中断。程序可能不知道设备将使用哪一个中断。 LinuxLinux 系统通过系统通过允许设备驱动程序探测自己的中断来解决这个问题。 允许设备驱动程序探测自己的中断来解决这个问题。

首先,设备驱动程序使得设备产生一个中断。然后,允许首先,设备驱动程序使得设备产生一个中断。然后,允许系统中所有没有指定的中断,这意味着设备挂起的中断将系统中所有没有指定的中断,这意味着设备挂起的中断将会通过中断控制器传送。会通过中断控制器传送。 Linux Linux 系统读取中断状态寄存器系统读取中断状态寄存器然后将它的值返回到设备驱动程序。一个非然后将它的值返回到设备驱动程序。一个非 0 0 的结果意味的结果意味着在探测期间发生了一个或者多个的中断。设备驱动程序着在探测期间发生了一个或者多个的中断。设备驱动程序现在可以关闭探测,这时所有还未被指定的中断将继续被现在可以关闭探测,这时所有还未被指定的中断将继续被禁止。 禁止。

Page 109: 第  3  章 中断和中断处理

3.4.2 3.4.2 外部设备中断处理的全过程外部设备中断处理的全过程 某个某个 ISAISA设备驱动程序知道了它的中断号以后,就可以请设备驱动程序知道了它的中断号以后,就可以请

求对中断的控制了。求对中断的控制了。 PCI PCI 结构的系统中断比结构的系统中断比 I S A I S A 结构的结构的系统中断要灵活得多。系统中断要灵活得多。 ISAISA设备使用中断插脚经常使用跳设备使用中断插脚经常使用跳线设置,所以在设备驱动程序中是固定的。但线设置,所以在设备驱动程序中是固定的。但 PCI PCI 设备是设备是在系统启动过程中在系统启动过程中 PCIPCI 初始化时由初始化时由 PCI BIOSPCI BIOS 或或 PCIPCI子系子系统分配的。每一个统分配的。每一个 PCI PCI 设备都有可能使用设备都有可能使用 AA、、 BB、、 CC 或或者者 DD这这 4 4 个中断插脚中的一个。默认情况下设备使用插个中断插脚中的一个。默认情况下设备使用插脚脚 AA。。

每个每个 PCIPCI插槽的插槽的 PCIPCI中断中断 AA、、 BB、、 CC 和和 DD是通过路由选是通过路由选择连接到中断控制器上的。所以择连接到中断控制器上的。所以 PCIPCI插槽插槽 44 的插脚的插脚 AA可能可能连接到中断控制器的插脚连接到中断控制器的插脚 6 6 ,, PCI PCI 插槽插槽 4 4 的插脚的插脚 B B 可能可能连接到中断控制器的插脚连接到中断控制器的插脚 77,以此类推。 ,以此类推。

Page 110: 第  3  章 中断和中断处理

3.4.2 3.4.2 外部设备中断处理的全过程外部设备中断处理的全过程 PCIPCI的设置代码将中断控制器的插脚号写入到每个设备的的设置代码将中断控制器的插脚号写入到每个设备的 PCIPCI设置头设置头

中。中。 PCIPCI的设置代码根据所知道的的设置代码根据所知道的 PCIPCI中断路由拓扑结构、中断路由拓扑结构、 PCIPCI设备设备使用的插槽,以及正在使用的使用的插槽,以及正在使用的 PCIPCI中断的插脚号来决定中断号,也就中断的插脚号来决定中断号,也就是是 IRQIRQ 号。 号。

系统中可以有很多的系统中可以有很多的 PCIPCI中断源,例如当系统使用了中断源,例如当系统使用了 PCI-PCIPCI-PCI 桥时。桥时。这时,中断源的数目可能超过了系统可编程中断控制器上插脚的数目。这时,中断源的数目可能超过了系统可编程中断控制器上插脚的数目。在这种情况下,某些在这种情况下,某些 PCIPCI设备之间就不得不共享一个中断,也就是说,设备之间就不得不共享一个中断,也就是说,中断控制器上的某一个插脚可以接收来自几个设备的中断。在中断控制器上的某一个插脚可以接收来自几个设备的中断。在 LinuxLinux系统中,第一个中断源请求者应宣布它使用的中断是否可以被共享,系统中,第一个中断源请求者应宣布它使用的中断是否可以被共享,以实现中断在几个设备之间的共享。中断共享使得以实现中断在几个设备之间的共享。中断共享使得 irq_actionirq_action 数组中数组中的同一个入口指向几个设备的的同一个入口指向几个设备的 irqactionirqaction结构。当一个共享的中断有结构。当一个共享的中断有中断发生时,中断发生时, LinuxLinux 系统将会调用和此中断有关的所有中断处理程序。系统将会调用和此中断有关的所有中断处理程序。在任何时候,即使自身没有中断需要处理,所有可以共享中断的设备在任何时候,即使自身没有中断需要处理,所有可以共享中断的设备驱动程序的中断处理程序都可能被调用。 驱动程序的中断处理程序都可能被调用。

Page 111: 第  3  章 中断和中断处理

3.4.3 3.4.3 后续处理后续处理 后续处理有一个重要的下半部分,即时钟中断的后续处理有一个重要的下半部分,即时钟中断的

下半部分。后续处理主要完成下面的工作: 下半部分。后续处理主要完成下面的工作:

(1) bottom_half(1) bottom_half 在开中断的状态下,它们继续完成中断处理。因在开中断的状态下,它们继续完成中断处理。因

此中断中的处理函数需要在一个此中断中的处理函数需要在一个 3232位变量中设置位变量中设置特定的特定的 bitbit 来告诉来告诉 do_softirqdo_softirq要执行哪个要执行哪个 bottobottom_halfm_half(或可将该(或可将该 3232位数想像成一个新的中断位数想像成一个新的中断向量表,设置向量表,设置 bitbit 相当于产生中断,下半部分相相当于产生中断,下半部分相当于当于 handlerhandler)) bottom_halfbottom_half有的时候需要借助有的时候需要借助task_queue task_queue 结构来完成更多的工作, 结构来完成更多的工作,

Page 112: 第  3  章 中断和中断处理

3.4.3 3.4.3 后续处理后续处理 (( 22) 进程是否能切换) 进程是否能切换 task_queuetask_queue:: task_queue task_queue 是一个链表,每个节点是一是一个链表,每个节点是一

个函数指针,这样,一 个 个函数指针,这样,一 个 bottom_half bottom_half 就可以执行一个就可以执行一个链表上的函数列了。当然 链表上的函数列了。当然 task_queue task_queue 不一定只在 不一定只在 bottbottom_half om_half 中应用中应用 , , 在一些驱动中也直接调用 在一些驱动中也直接调用 run_task_qrun_task_queue ueue 来执行一个特定的队列。来执行一个特定的队列。

2.4.x2.4.x 版的内核增加了版的内核增加了 softirq_action taskletsoftirq_action tasklet 。如果内核。如果内核需要在某个中断产生后执行它的函数,只需要在它下半部需要在某个中断产生后执行它的函数,只需要在它下半部分调用的 分调用的 task_queue task_queue 上挂上它的函数。上挂上它的函数。

(3) (3) 是否需要进行切换是否需要进行切换 因为 因为 LinuxLinux 是非抢占的,所以如果返回的代码段是内核级是非抢占的,所以如果返回的代码段是内核级

的话,就不允许进行切换。如果能切换判断一下是否需要的话,就不允许进行切换。如果能切换判断一下是否需要切换切换 , , 如果是就切换。 如果是就切换。

Page 113: 第  3  章 中断和中断处理

3.4.3 3.4.3 后续处理后续处理 (( 44)信号处理)信号处理 看是否有信号要处理,如果要调用 看是否有信号要处理,如果要调用 do_sigdo_sig

nal nal 时钟中断的下半部分。时钟中断的下半部分。 在上面许多的外部中断中,有一个特殊的在上面许多的外部中断中,有一个特殊的

中断的处理 中断的处理 timer_interrupt, timer_interrupt, 它的下半部它的下半部分主要处理分主要处理 ::时间计算和校准定时器工作。时间计算和校准定时器工作。

Page 114: 第  3  章 中断和中断处理

练习与思考练习与思考 11 .. CPUCPU 在什么情况下才响应中断?中断处理过程一般包括那些步骤?在什么情况下才响应中断?中断处理过程一般包括那些步骤?

22 . . 80868086 的中断系统分为哪几种类型的中断?其优先顺序如何?的中断系统分为哪几种类型的中断?其优先顺序如何?

33 . . 80868086 系统中,若端口地址为系统中,若端口地址为 02C0H02C0H ,, 02C2H02C2H 的的 8259A8259A是单是单片、全嵌套工作方式、非特殊屏蔽和非特殊结束方式、中断请求信号片、全嵌套工作方式、非特殊屏蔽和非特殊结束方式、中断请求信号边沿触发。当中断响应时,边沿触发。当中断响应时, 8259A8259A 输出的中断类型号为输出的中断类型号为 08H08H 。试给。试给出出 8259A8259A 初始化程序。初始化程序。

4.4.假设你正在设计一种先进的计算机体系结构,它使用硬件而不是中假设你正在设计一种先进的计算机体系结构,它使用硬件而不是中断来完成进程切换断来完成进程切换 ,,则则 CPUCPU需要哪些信息?请描述用硬件完成进程切需要哪些信息?请描述用硬件完成进程切换的工作过程。换的工作过程。

5.5. 目前的计算机上,中断处理程序至少有一小部分用汇编语言编写,目前的计算机上,中断处理程序至少有一小部分用汇编语言编写,为什么?为什么?

Page 115: 第  3  章 中断和中断处理

练习与思考练习与思考 6.6. 可否使每个进程拥有自己的可否使每个进程拥有自己的 cachecache ??

7. 7. 给出一个框架,来描述一个可禁止中断的操作系统如给出一个框架,来描述一个可禁止中断的操作系统如何实现信号量。何实现信号量。

8. 8. 什么是中断向量、中断向量表、向量地址?什么是中断向量、中断向量表、向量地址?

9. 9. 中断有哪些类型?它在操作系统中起的作用如何? 中断有哪些类型?它在操作系统中起的作用如何?

10. 10. 为实现为实现 CPUCPU 与外部设备并行工作,必须引入的基础与外部设备并行工作,必须引入的基础硬件是什么? 硬件是什么?