QNX-9—QNX官网文档翻译—中断-2—Interrupt handling

QNX Software Development Platform --> OS Components --> System Architecture --> The QNX Neutrino Microkernel --> Interrupt handling

前言:

无论我们多么希望如此,计算机都不是无限快的。在实时系统中,绝对不能不必要地浪费 CPU 周期。将外部事件发生到负责对该事件作出反应的线程内代码的实际执行时间最小化也至关重要。这个时间称为延迟。

我们最关心的两种延迟形式是中断延迟和调度延迟。

延迟时间可能有很大差异,具体取决于处理器的速度和其他因素。有关更多信息,请访问我们的网站 (www.qnx.com)。

中断延迟
中断延迟是从硬件中断断言到执行设备驱动程序的中断处理程序的第一条指令的时间。

调度延迟
在某些情况下,低级硬件中断处理程序必须调度更高级别的线程来运行。在这种情况下,中断处理程序将返回并指示要传递事件。这引入了第二种形式的延迟——调度延迟——必须考虑到这一点。

嵌套中断
QNX Neutrino RTOS 完全支持嵌套中断。

中断调用
中断处理 API 包括以下内核调用:


一、Interrupt latency

中断延迟是从硬件中断断言到执行设备驱动程序中断处理程序的第一条指令的时间。

操作系统几乎一直保持中断完全启用,因此中断延迟通常微不足道。但某些关键代码段确实需要暂时禁用中断。这种最大禁用时间通常决定了最坏情况下的中断延迟 - 在 QNX Neutrino 中,这个延迟非常小。

下图说明了由已建立的中断处理程序处理硬件中断的情况。中断处理程序要么直接返回,要么返回并导致事件被传递。

Figure 1. Interrupt handler simply terminates.

上图中的中断延迟 (Til) 表示最小延迟,即中断发生时完全启用中断时发生的延迟。最坏情况下的中断延迟将是此时间加上操作系统或正在运行的系统进程禁用 CPU 中断的最长时间


二、Scheduling latency

在某些情况下,低级硬件中断处理程序必须安排更高级别的线程运行。在这种情况下,中断处理程序将返回并指示要传递事件。这引入了第二种形式的延迟——调度延迟——必须考虑到这一点。

调度延迟是用户中断处理程序的最后一条指令与驱动程序线程的第一条指令执行之间的时间。这通常意味着保存当前执行线程的上下文并恢复所需驱动程序线程的上下文所需的时间。虽然比中断延迟大,但在 QNX Neutrino 系统中,这个时间也保持较小。

Figure 1. Interrupt handler terminates, returning an event.

需要注意的是,大多数中断都会终止而不传递事件。在很多情况下,中断处理程序可以处理所有与硬件相关的问题。只有当发生重大事件时,才会传递事件来唤醒更高级别的驱动程序线程。例如,串行设备驱动程序的中断处理程序会在每次收到传输中断时向硬件提供一字节数据,并且只有当输出缓冲区几乎为空时才会触发 (devc-ser*) 中的更高级别线程。


三、Nested interrupts

QNX Neutrino RTOS 完全支持嵌套中断。

前面的场景描述了最简单且最常见的情况,即只发生一个中断。未屏蔽中断的最坏情况时序考虑必须考虑当前正在处理的所有中断的时间,因为优先级更高的未屏蔽中断将抢占现有中断

在下图中,线程 A 正在运行。中断 IRQx 导致中断处理程序 Intx 运行,而 IRQy 及其处理程序 Inty 抢占了该中断。Inty 返回一个事件,导致线程 B 运行;Intx 返回一个事件,导致线程 C 运行。只有在两个中断处理程序返回的事件触发的线程都运行后,线程 A 才会恢复运行。

Figure 1. Stacked interrupts.


四、Interrupt calls

中断处理 API 包括以下内核调用:

----------------------------------------------------------------------
函数                   描述
----------------------------------------------------------------------
InterruptAttach()      将本地函数(中断服务例程或 ISR)附加到中断向量。
InterruptAttachEvent() 在中断上生成事件,这将使一个线程就绪。没有用户中断处理程序运行。这是首选调用。
InterruptDetach()      使 InterruptAttach() 或 InterruptAttachEvent() 返回的 ID 与中断分离。
InterruptWait()        等待中断。
InterruptEnable()      启用硬件中断。
InterruptDisable()     禁用硬件中断。
InterruptMask()        屏蔽硬件中断。
InterruptUnmask()      取消屏蔽硬件中断。
InterruptLock()        保护中断处理程序和线程之间的关键代码段。自旋锁用于使此代码 SMP 安全。此函数是 InterruptDisable() 的超集,应代替它使用。
InterruptUnlock()      删除关键代码段上的 SMP 安全锁。
----------------------------------------------------------------------

使用此 API,具有适当特权的用户级线程可以调用 InterruptAttach() 或 InterruptAttachEvent(),传递硬件中断号和线程地址空间中发生中断时要调用的函数的地址。QNX Neutrino 允许将多个 ISR 附加到每个硬件中断号 — 未屏蔽的中断可以在运行中断处理程序的执行期间得到服务

注意:

(1) 启动代码负责确保在系统初始化期间屏蔽所有中断源当对中断向量执行第一次 InterruptAttach() 或 InterruptAttachEvent() 调用时,内核会取消屏蔽它。同样,当对中断向量执行最后一次 InterruptDetach() 时,内核会重新屏蔽

(2) 您应该尽可能短地禁用中断(即,访问或处理硬件所需的最短时间)。不这样做可能会导致中断延迟增加,并且无法满足实时截止时间。

(3) 一些内核调用和库例程会重新启用中断。屏蔽的中断不受影响。

(4) 有关 InterruptLock() 和 InterruptUnlock() 的更多信息,请参阅本指南中多核处理章节中的“Critical sections”。注: 见下面补充。

(5) (QNX Neutrino 7.1 或更高版本)内核在进入和离开 ISR 时保存并恢复 FPU 上下文,因此在其中使用浮点运算是安全的

以下代码示例显示了如何将 ISR 附加到 PC 上的硬件定时器中断(操作系统也将其用于系统时钟)。由于内核的定时器 ISR 已经在处理清除中断源,因此该 ISR 可以简单地增加线程数据空间中的计数器变量并返回内核:

#include <stdio.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>
#include <sys/procmgr.h>

struct sigevent event;
volatile unsigned counter;

const struct sigevent *handler( void *area, int id ) {
    // Wake up the thread every 100th interrupt
    if ( ++counter == 100 ) {
        counter = 0;
        return( &event );
    } else
        return( NULL );
    }

int main() {
    int i;
    int id;

    // Initialize event structure
    event.sigev_notify = SIGEV_INTR;

    // Enable the INTERRUPT ability
    procmgr_ability(0, PROCMGR_ADN_ROOT|PROCMGR_AOP_ALLOW|PROCMGR_AID_INTERRUPT, PROCMGR_AID_EOL);

    // Attach ISR vector
    id=InterruptAttach(SYSPAGE_ENTRY(qtime)->intr, &handler, NULL, 0, 0 );

    for( i = 0; i < 10; ++i ) {
        // Wait for ISR to wake us up
        InterruptWait( 0, NULL );
        printf( "100 events\n" );
    }

    // Disconnect the ISR handler
    InterruptDetach(id);
    return 0;
}

通过这种方法,具有适当特权的用户级线程可以在运行时动态地将中断处理程序附加到硬件中断向量(或从硬件中断向量分离中断处理程序)。可以使用常规源代码级调试工具调试这些线程;可以通过在线程级调用 ISR 并在源代码级逐步执行它或使用 InterruptAttachEvent() 调用来调试 ISR 本身。

当硬件中断发生时,处理器将进入微内核中的中断重定向器。此代码将当前正在运行的线程上下文的寄存器推送到相应的线程表条目中,并设置处理器上下文,以便 ISR 可以访问包含 ISR 的线程的代码和数据。这允许 ISR 使用用户级线程中的缓冲区和代码来解析中断,并且如果需要线程进行更高级别的工作,则将事件排队到 ISR 所属的线程。然后,线程可以处理 ISR 已放入线程拥有的缓冲区中的数据。

由于 ISR 运行于包含它的线程的内存映射中,因此它可以直接操作映射到线程地址空间的设备,或直接执行 I/O 指令。因此,操作硬件的设备驱动程序无需链接到内核。

微内核的中断重定向器在调用用户的 ISR 之前只执行几条指令。因此,硬件中断或内核调用的进程抢占同样快,并且基本上使用相同的代码路径。

在 ISR 执行时,它具有完全的硬件访问权限(因为它是特权线程的一部分),但不能发出其他内核调用。ISR 旨在尽可能少的微秒内响应硬件中断,执行最少的工作以满足中断(例如,从 UART 读取字节),并在必要时使线程以用户指定的优先级进行调度以执行进一步的工作。

对于给定的硬件优先级,最坏情况的中断延迟可以直接从内核施加的中断延迟和每个中断的最大 ISR 运行时间计算出来,这些中断的硬件优先级高于所讨论的 ISR。由于可以重新分配硬件中断优先级,因此系统中最重要的中断可以设置为最高优先级。

还请注意,通过使用 InterruptAttachEvent() 调用,不会运行任何用户 ISR。相反,每次中断都会生成一个用户指定的事件;该事件通常会导致等待线程被调度运行并执行工作。当事件生成时,中断会自动屏蔽,然后由处理设备的线程在适当的时间明确取消屏蔽

注意:InterruptMask() 和 InterruptUnmask() 都是计数函数。例如,如果 InterruptMask() 被调用十次,那么 InterruptUnmask() 也必须被调用十次。

因此,硬件中断产生的工作的优先级可以按照操作系统调度的优先级而不是硬件定义的优先级执行。由于中断源在得到服务之前不会重新中断,因此可以控制中断对硬截止时间调度的关键代码区域运行时的影响。

除了硬件中断之外,微内核中的各种“事件”也可以由用户进程和线程“挂钩”。当其中一个事件发生时,内核可以向上调用用户线程中指示的函数来执行针对此事件的某些特定处理。例如,每当系统中的空闲线程被调用时,用户线程都可以让内核向上调用该线程,以便可以轻松实现特定于硬件的低功耗模式。

----------------------------------------
微内核调用            描述
----------------------------------------
InterruptHookIdle2() 当内核没有要调度的活动线程时,它会运行空闲线程,该线程可以调用用户处理程序。此处理程序可以执行特定于硬件的电源管理操作。
InterruptHookTrace() 此函数附加一个伪中断处理程序,该处理程序可以从被检测的内核接收跟踪事件。

有关中断的更多信息,请参阅《QNX Neutrino 入门》的 “Interrupts”一章和《QNX Neutrino 程序员指南》的 “Writing an Interrupt Handler” 一章。


五、补充

1. Critical sections

翻译:

QNX Software Development Platform --> OS Components --> System Architecture --> Multicore Processing --> Symmetric multiprocessing (SMP)
https://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.sys_arch/topic/smp_CRITICAL.html

为了控制对它们之间共享的数据结构的访问,线程和进程使用互斥体、condvar 和信号量的标准 POSIX 原语。这些在 SMP 系统中无需更改即可工作。

许多实时系统还需要保护对中断处理程序和拥有该处理程序的线程之间的共享数据结构的访问。线程之间使用的传统 POSIX 原语不可供中断处理程序使用。这里有两种解决方案:

(1) 一种是从中断处理程序中删除所有工作,而是在线程时完成所有工作。鉴于我们的快速线程调度,这是一个非常可行的解决方案。

(2) 在运行 QNX Neutrino RTOS 的单处理器系统中,中断处理程序可能会抢占线程,但线程永远不会抢占中断处理程序。这允许线程通过在很短的时间内禁用和启用中断来保护自己免受中断处理程序的影响。
非 SMP 系统上的线程使用以下形式的代码保护自己:

InterruptDisable()
// critical section
InterruptEnable()

或:

InterruptMask(intr)
// critical section
InterruptUnmask(intr)

不幸的是,此代码在 SMP 系统上会失败,因为线程可能在一个处理器上运行,而中断处理程序同时在另一个处理器上运行!//TODO: 这些mask和disable是每CPU的操作吗?

一个解决方案是将线程锁定到特定处理器(请参阅本章后面的“Bound Multiprocessing (BMP)”)。

更好的解决方案是使用线程和中断处理程序都可用的新互斥锁。这由以下原语提供,它们在单处理器和 SMP 机器上均可用:

InterruptLock(intrspin_t* spinlock )
尝试获取 spinlock,这是中断处理程序和线程之间共享的变量。代码将在一个紧密的循环中自旋,直到获得锁。禁用中断后,代码将获取锁(如果它是由线程获取的)。必须尽快释放锁(通常在几行 C 代码内,没有任何循环)。

InterruptUnlock(intrspin_t* spinlock ) 
释放锁并重新启用中断。

在非 SMP 系统上,无需自旋锁。

有关详细信息,请参阅《QNX Neutrino 程序员指南》中的“Multicore Processing”一章。

 

posted on 2024-05-31 14:07  Hello-World3  阅读(167)  评论(0编辑  收藏  举报

导航