QNX Easy start : chapter 4 :Neutrino and interrupts

chapter 4 :Neutrino and interrupts

在本节中,我们将研究中断,在Neutrino下如何处理中断,它们对调度和实时性的影响以及一些中断管理策略。
我们首先要问的是,“什么是中断?”中断确实听起来像是-中断正在发生的事情,并转移到另一个任务。
例如,假设您坐在办公桌前从事“ A”工作。突然,电话响了。非常重要的客户(VIC)需要您立即回答一些技能测试问题。回答问题后,您可以返回工作“ A”,或者VIC可能更改了优先级,以便将工作“ A”推到一边,然后立即开始工作“ B”。
现在让我们将其放在Neutrino下。
在任何时候,处理器都在忙于处理优先级最高的READY线程(这是处于RUNNING状态的线程)的工作
为了引起中断,计算机总线上的一块硬件声明了一条中断线(以我们的类比,这是电话响铃)。
一旦中断线被断言,内核就会跳转到一段代码,该代码设置环境以运行中断服务程序(ISR),这是一个软件,它确定检测到该中断时应发生的情况。
在硬件声明中断线与执行ISR的第一条指令之间经过的时间称为中断等待时间。
中断等待时间以微秒为单位。不同的处理器具有不同的中断等待时间。它是处理器速度,缓存体系结构,内存速度以及操作系统效率的函数。以我们的类比来说,如果您正在听耳机中的音乐而忽略了正在响铃的电话,则可能需要更长的时间才能注意到此电话“中断”。
在中微子的统治下,同样的事情可能发生。有一个处理器指令禁止中断(例如x86上的cli)。处理器在重新启用中断之前不会注意到任何中断(在x86上,这是sti操作码)。
为了避免特定于CPU的汇编语言调用,Neutrino提供了以下调用:InterruptEnable()和InterruptDisable()以及InterruptLock()和InterruptUnlock()这些将处理所有受支持平台上的所有低级详细信息。
ISR通常会执行尽可能少的工作,然后结束(按照我们的类比,这是与VIC在电话上进行的对话-我们通常不会让客户搁置并进行数小时的工作;我们只是告诉客户,“好吧,我会马上解决的!”)。
当ISR结束时,它可以告诉内核什么都不应该发生(这意味着ISR已经完全处理了该事件,并且不需要执行任何其他操作),或者内核应该执行一些可能导致线程变为READY的操作。
以我们的类比,告诉内核已处理了中断,就像告诉客户答案-我们可以返回到正在做的事情,知道客户已经回答了他们的问题。告诉内核需要执行某些操作,就像告诉客户您将尽快联系他们-电话已挂断,但可能会再次振铃。

Interrupt service routine

ISR是一段代码,负责清除中断源。这是一个关键点,尤其是结合以下事实:中断以比任何软件优先级更高的优先级运行。这意味着花在ISR中的时间量可能会对线程调度产生严重影响。您应该在ISR中花费尽可能少的时间。让我们更深入地研究一下。
在中断服务程序中使用浮点运算并不安全。

Clearing the interrupt source

产生中断的硬件设备将保持中断线有效,直到确定软件已处理该中断。由于硬件无法理解,因此软件必须在响应中断原因时告诉它。通常,这是通过从特定硬件端口读取状态寄存器或从特定内存位置读取数据块来完成的。
无论如何,在硬件和软件之间通常存在某种形式的肯定确认,以“取消声明”中断线路。 (有时没有确认;例如,一块硬件可能会产生一个中断,并假定该软件会处理该中断。)因为中断的运行优先级高于任何软件线程,所以我们应该在ISR中花费尽可能少的时间,以最大程度地减少对调度的影响。如果我们仅通过读取寄存器并填充该值来清除中断源,变成一个全局变量,那么我们的工作很简单。
这是由ISR对串行端口进行的处理。当字符到达时,串行端口硬件会产生一个中断。 ISR处理程序读取包含字符的状态寄存器,并将该字符填充到循环缓冲区中。
做完了总处理时间:几微秒。而且,它必须快速。考虑一下如果您接收到115 Kbaud的字符(大约每100 us一个字符)会发生什么情况;如果您花了100倍的时间处理中断,那么您将没有时间做其他事情!
不过,不要让我误导您-串行端口的中断服务例程可能需要更长的时间才能完成。
这是因为有一个后端民意测验,看是否有更多的字符在设备中等待。
显然,在我们的类比中,将在中断中花费的时间最小化可以看作是“良好的客户服务”-通过将通话中的时间保持在最少,我们可以避免给其他客户一个忙碌的信号如果处理程序需要做大量工作怎么办?
这有两种可能:

  • 清除中断源所需的时间很短,但是与硬件通信所需的工作量却很长(客户向我们询问了一个简短的问题,需要花费很长时间才能回答)。
    • 清除中断源所需的时间很长(客户对问题的描述很长且涉及很多)。
      在第一种情况下,我们想尽可能快地清除中断源,然后告诉内核让一个线程来完成与慢速硬件通信的实际工作。这样做的好处是,ISR只花了很少的钱。时间超高优先级,然后基于常规线程优先级完成其余工作。这类似于您接听电话(超高优先级),然后将实际工作委派给一位助手。在本章的后面,我们将看看ISR如何告诉内核安排其他人。
      在第二种情况下,事情变得复杂。如果ISR在退出时没有清除中断源,则内核将立即被可编程中断控制器(x86上的PIC,这是8259或等效芯片)重新中断。
      对于PIC爱好者:我们将在短期内讨论边缘敏感和电平敏感中断
      我们将继续运行I​​SR,而没有机会运行我们需要正确处理中断的线程级代码。
      什么样的大脑损坏硬件需要很长时间才能清除中断源?基本的PC软盘控制器将保持中断状态,直到您读取了许多状态寄存器值为止。不幸的是,寄存器中的数据无法立即使用,因此您必须轮询此状态数据。这可能需要几毫秒的时间(从计算机角度来说很长时间)!
      解决方案是临时屏蔽中断—从字面上告诉PIC忽略来自此特定源的中断,直到您另行告知为止。在这种情况下,即使中断线是通过硬件置位的,PIC也会忽略该中断线,并且不会将其告知处理器。这使您的ISR可以调度线程以在ISR之外处理此硬件。当线程完成从硬件的数据传输后,它可以告诉PIC取消屏蔽该中断。这样可以再次识别该硬件的中断。以我们的类推,这就像将VIC的呼叫转移给您的助手。

Telling a thread to do something

ISR如何告诉内核它现在应该安排一个线程来做一些工作?(相反,它如何告诉内核它不应该这样做?)这是典型ISR的一些伪代码:
DraggedImage.png
诀窍是返回一个事件(而不是NULL)(该类型是struct sigevent,我们在“时钟,计时器和每时每刻获得踢球”一章中谈到了)。请注意,销毁ISR的堆栈帧后,返回的事件必须是持久的。
这意味着该事件必须在ISR外部声明,或者使用area参数从持久数据区域传递到ISR,或者在ISR自身内部声明为静态事件。你的选择。如果返回一个事件,则内核会在ISR返回时将其传递给线程。
因为该事件(通过脉冲,如我们在“消息传递”一章中所讨论的,或通过信号)“警告”线程,所以这可能导致内核重新调度线程,该线程接下来将获取CPU。如果从ISR返回NULL,则内核知道在线程时不需要执行任何特殊操作,因此它不会重新调度任何线程-在ISR抢占它时正在运行的线程可以恢复运行。

Level-sensitivity versus edge-sensitivity 水平敏感度与边缘敏感度

我们还缺少另外一个难题。可以将大多数PIC编程为在电平敏感或边缘敏感模式下运行。
在电平敏感模式下,当中断线处于“开”状态时,它被视为由PIC断言。 (这对应于下图中的标签“ 1”。)
DraggedImage-1.png
我们可以看到,这将导致上面用软盘控制器示例描述的问题。只要ISR完成,内核就会告诉PIC,“好吧,我已经处理了该中断。请告诉我下次该中断被激活”(第2步图)。用技术术语来说,内核将中断结束End Of Interrupt(EOI)发送到PIC。
PIC查看中断线,如果中断线仍处于活动状态,则会立即重新中断内核(步骤3)。我们可以通过将PIC编程为边缘敏感edge-sensitive模式来解决此问题。在这种模式下,PIC仅在活动的边沿上注意到中断an active-going edge
DraggedImage-2.png
即使ISR无法清除中断源,当内核发送EOI到PIC(图中的第2步),PIC不会重新中断内核re-interrupt the kernel,因为在EOI之后没有另一个活动的边沿过渡。为了识别该线路上的另一个中断,该线路必须首先变为非活动状态(步骤4),然后变为活动状态(步骤1)。
好吧,看来我们所有的问题都已经解决了!只需对所有中断使用边沿敏感。Simply use edge-sensitive for all interrupts.不幸的是,边缘敏感模式有其自身的问题。
假设您的ISR无法清除中断原因。当内核向PIC发出EOI时,硬件将仍然具有中断线。但是,由于PIC在边缘敏感模式下运行,因此它永远不会看到该设备发出的另一个中断。
现在什么样的bozo会写一个ISR忘记清除中断源?不幸的是,它并不是那么干。考虑一种情况,其中两个设备(例如SCSI总线适配器和以太网卡)在允许的硬件总线体系结构上共享同一条中断线。
(现在,您问的是,“谁来设置这样的机器?!?”,这确实发生了,尤其是在PIC上的中断源数量不足的情况下!)在这种情况下,有两个ISR例程将被附加到相同的中断向量(顺便说一句,这是合法的),并且每当它从PIC获得该硬件中断级别的中断时,内核就会依次调用它们。
DraggedImage-3.png
在这种情况下,由于在关联的ISR运行时只有一个硬件设备处于活动状态(SCSI设备),因此它可以正确清除中断源(步骤2)。请注意,无论内核如何运行以太网设备的ISR(在步骤3中),因为它不知道以太网硬件是否也需要服务,因此它始终运行整个链。chain
DraggedImage-4.png
这就是问题所在。
以太网设备首先中断。这导致断言线被断言(PIC记录了活动沿),内核调用了链中的第一个中断处理程序(SCSI磁盘驱动器;图中的步骤1)。 SCSI磁盘驱动程序的ISR查看其硬件,并说:“不,不是我。哦,好了,请忽略它”(步骤2)。然后内核调用了链中的下一个ISR,即以太网ISR(第3步)。以太网ISR查看了硬件并说:“嘿!这是我的硬件触发了中断。我将清除它。”不幸的是,在清除它时,SCSI设备产生了一个中断(步骤4)。
当以太网ISR完成清除中断源(步骤5)时,由于SCSI硬件设备,中断线仍然有效。但是,以边沿敏感模式编程的PIC在识别另一个中断之前正在寻找一个非活动到活动的转换(在复合线上)。这不会发生,因为内核已经调用了两个中断服务程序,现在正在等待来自PIC的另一个中断。
在这种情况下,级别敏感的解决方案将是合适的,因为当以太网ISR完成并且内核向PIC发出EOI时,PIC会发现一个事实,即总线上仍然有一个中断处于活动状态并重新中断内核。然后,内核将遍历ISR链,这一次SCSI驱动程序将有机会运行并清除中断源。
对边缘敏感还是对电平敏感的选择取决于硬件和启动代码。某些硬件将仅支持其中一种。支持任何一种模式的硬件将由启动代码编程为另一种。您必须查阅系统随附的BSP(板级支持软件包)文档才能获得明确的答案。

Writing interrupt handlers

让我们看看如何设置中断处理程序-调用,特征和一些策略。

Attaching an interrupt handler

要附加到中断源,可以使用InterruptAttach()或InterruptAttachEvent()

 #include <sys/neutrino.h>
int InterruptAttachEvent (int intr,
				const struct sigevent *event, unsigned flags);
int InterruptAttach (int intr,
				const struct sigevent *
				(*handler) (void *area, int id),
				const void *area, int size, unsigned flags);

intr参数指定您希望将指定处理程序附加到哪个中断。传递的值由启动代码定义,启动代码在Neutrino启动之前就初始化了PIC(还有其他功能)。
此时,两个函数InterruptAttach()和InterruptAttachEvent()不同。首先,让我们来看一下InterruptAttachEvent()。那我们回到 InterruptAttach()

Attaching with InterruptAttachEvent()

InterruptAttachEvent()函数接受两个附加参数:参数事件(它是应传递的struct sigevent的指针)和标志参数。 InterruptAttachEvent()告诉内核,每当检测到中断时都应返回该事件,并且应该屏蔽中断级别。请注意,由内核来解释事件并弄清楚应该使哪个线程准备就绪READY。

Attaching with InterruptAttach()

使用InterruptAttach(),我们可以指定一组不同的参数。 handler参数是要调用的函数的地址。从原型中可以看到,handler()返回一个struct sigevent,它指示要返回的事件类型,并带有两个参数。
第一个传递的参数是area,它只是传递给InterruptAttach()的area参数。第二个参数id是中断的标识,也是从InterruptAttach()返回的值。这用于识别中断以及屏蔽,取消屏蔽,锁定或解锁中断。 InterruptAttach()的第四个参数是大小,它指示传入的数据区域的大小(以字节为单位)。最后,flags参数与为InterruptAttachEvent()传递的参数相同;我们将稍后讨论。

Now that you’ve attached an interrupt

至此,您已经调用了InterruptAttachEvent()或InterruptAttach()。
由于附加中断并不是您希望每个人都能做的事情,因此Neutrino仅允许启用了“ I / O特权”的线程来执行中断(请参见Neutrino库参考中的ThreadCtl()函数)。只有从root帐户运行或setuid()到root的线程才能获得“ I / O特权”,因此我们实际上限制了这种root用户的能力。
这是一个将ISR附加到硬件中断向量的代码片段,我们已在代码示例中通过常量HW SERIAL IRQ标识了该代码:

 #include <sys/neutrino.h>
                       int interruptID;
const struct sigevent * intHandler (void *arg, int id) {
... }
                       int
                       main (int argc, char **argv)
                       {
...
interruptID = InterruptAttach (HW_SERIAL_IRQ,
                                                         intHandler,
                                                         &event,
														 sizeof (event),
                                                         0);
if (interruptID == -1) {
fprintf (stderr, "%s: can’t attach to IRQ %d\n",
                                       progname, HW_SERIAL_IRQ);
                              perror (NULL);
                              exit (EXIT_FAILURE);
                          }
...
                          return (EXIT_SUCCESS);
                       }

这将在ISR(称为intHandler()的例程;有关详细信息,请参见下文)与硬件中断向量HW SERIAL IRQ之间建立关联。此时,如果在该中断向量上发生中断,则将分派我们的ISR。当我们调用InterruptAttach()时,内核会在PIC级别取消屏蔽中断源(除非已被取消屏蔽,如果多个ISRmultiple ISRs共享同一个中断,情况就是这样)。

Detaching an interrupt handler

完成ISR后,我们可能希望打破ISR与中断向量之间的关联:

int
InterruptDetach (int id);

我之所以说“可以”是因为处理中断的线程通常在服务器中找到,并且服务器通常永远挂在身边。因此可以想象,结构良好的服务器永远不会发出InterruptDetach()函数调用。当线程或进程死亡时,操作系统还将删除线程或进程可能与之关联的所有中断处理程序。因此,简单地退出main()的末尾,调用exit()或由于SIGSEGV而退出,将自动将您的ISR与中断向量分离。 (当然,您可能希望更好地处理此问题,并阻止设备生成中断。如果另一个设备正在共享中断,则没有两种解决方法-必须清理,否则您将不会
如果在边缘敏感模式下运行,将获得更多的中断;如果在级别敏感模式level-sensitive mode下运行,则将不断产生大量的ISR分派ISR dispatches。)
继续上面的示例,如果要分离,将使用以下代码:

void
terminateInterrupts (void) {
                          InterruptDetach (interruptID);
                       }

如果这是与该中断向量相关联的最后一个ISR,则内核将自动在PIC级别屏蔽中断源,以便它不会生成中断。

猜你喜欢

转载自blog.csdn.net/chenshiming1995/article/details/105063985