基本概念
ISR
1. 中断服务例程( ISR) 是响应硬件或软件中断而异步执行的函数。ISR 通常会抢占当前线程的执行,从而允许以非常低的开销进行响应。仅当所有 ISR 工作完成后,线程执行才会恢复;ISR的数量受底层硬件的限制。
2. ISR 具有以下关键属性:
1)触发 ISR 的中断请求 (IRQ) 信号。
2)与 IRQ 相关的优先级。
3)调用中断处理函数来处理中断。
4)传递给该函数的参数值。
3. 中断嵌套是指在处理一个中断的过程中,如果发生了更高优先级的中断,处理器可以暂停当前的中断处理,转而去处理这个新的中断。
多级中断处理
嵌套中断向量控制器(Nested Vectored Interrupt Controller,NVIC)是ARM Cortex-M系列处理器中用于处理中断的硬件。
如果支持嵌套中断控制器,CONFIG_MULTI_LEVEL_INTERRUPTS 则应设置为 1,CONFIG_2ND_LEVEL_INTERRUPTS根据 CONFIG_3RD_LEVEL_INTERRUPTS硬件架构进行配置。
基于此,给每个硬件分配了唯一的32位中断号。(待补充)
IRQ锁
线程可以使用IRQ 锁暂时阻止系统中的所有 IRQ 处理。在内核再次处理中断之前,线程解锁IRQ锁次数要和设置IRD锁的次数相同。
IRQ 锁是线程特定的。具体来说,如果线程 A 锁定中断,然后执行使其自身进入休眠状态的操作(例如休眠 N 毫秒),则一旦线程 A 被换出并且下一个就绪线程 B 开始运行,该线程的 IRQ 锁就不再适用。
这意味着可以在线程 B 运行时处理中断,除非线程 B 也使用自己的 IRQ 锁锁定了中断。当内核在使用 IRQ 锁的两个线程之间切换时是否可以处理中断是特定于体系结构的。当线程A最终再次成为当前线程时,内核重新建立线程A的IRQ锁。这可确保线程 A 在显式解锁其 IRQ 锁之前不会被中断。如果线程 A 没有休眠,但让更高优先级的线程 B 准备就绪,则 IRQ 锁将禁止任何可能发生的抢占。线程 B 不会运行,直到释放 IRQ 锁后到达下一个重新调度点。
零延迟中断
IRQ锁会阻止中断IRQ执行,所以会增加中断延迟。对于一些对延迟时间有要求的中断用例来说,长时间的中断延迟是不可接受的;因此内核允许此类型的中断以无法被IRQ锁阻止的优先级来执行,这些中断被称为零延迟中断。
首先需要启使能CONFIG_ZERO_LATENCY_IRQS表示启用零延迟中断(整体支持零延迟中断);其次IRQ_ZERO_LATENCY标志必须被传递给IRQ_CONNECT或 IRQ_DIRECT_CONNECT宏以将特定中断配置为零延迟(将特定中断设置为零延迟中断)。
卸载ISR工作
offloading ISR working,又叫底半部处理。
1. ISR应快速执行确保预期的系统操作,提高系统的实时性。如果需要耗时的处理,ISR应将部分或全部处理卸载到线程,从而恢复内核相应其他中断的能力。
2. 内核支持多种将中断相关处理卸载到线程的机制:
-
ISR 可以使用内核对象(例如 FIFO、LIFO 或信号量)向辅助线程发出信号以执行与中断相关的处理。
-
ISR 可以指示系统工作队列线程执行工作项。
3. 当将ISR的部分或全部处理卸载到线程时,后续的这些线程处理通常是以系统线程的形式并行工作的,当有其他更高优先级的线程或中断来时,会在处理卸载的线程之前执行。
4. 补充说明:当一个中断发生时,处理器首先会执行ISR,这个过程被称为"顶半部(top half)"处理。在顶半部处理中,ISR通常会执行一些紧急的、必须立即完成的工作,比如读取设备的状态,清除中断标志,等等。然后,ISR可能会把一些可以稍后执行的工作卸载到一个线程中去执行,这个过程被称为"底半部"处理。这个线程可能是一个专门的内核线程,也可能是一个用户空间的线程,取决于操作系统的设计。
API
IRQ_CONNECT
IRQ_CONNECT( irq_p, priority_p, isr_p, isr_param_p, flags_p) 初始化中断处理程序,要求在构建时必须知道所有参数。
参数:
irq_p:中断号
priority_p:中断优先级
isr_p:中断处理函数的地址
isr_param_p:传递给中断处理函数的参数
flags_p:特定架构的IRQ配置标志
irq_connect_dynamic
static inline int irq_connect_dynamic(unsigned int irq, unsigned int priority, void (*routine)(const void *parameter), const void *parameter, uint32_t flags)
配置动态中断。如果在构建时无法知道参数,用此函数代替IRQ_CONNECT()
irq_disconnect_dynamic
static inline int irq_disconnect_dynamic(unsigned int irq, unsigned int priority, void (*routine)(const void *parameter), const void *parameter, uint32_t flags)
断开动态中断。将其与共享中断结合使用,可以从使用同一中断线的客户端列表中删除例程/参数对。
IRQ_DIRECT_CONNECT
IRQ_DIRECT_CONNECT( irq_p, priority_p, isr_p, flags_p)初始化"直接"中断处理程序
这些 ISR 专为性能关键型中断处理而设计,不经过通用中断处理代码。它们的实现方式必须能够安全地将它们直接放入向量表中。对于用 C 语言编写的 ISR,ISR_DIRECT_DECLARE()宏将自动执行此操作。
(TODO:这段有一些内容不太理解)
与普通 Zephyr 中断相比,这种类型的中断目前有一些限制:
-
没有参数传递给 ISR。
-
不进行堆栈切换,ISR 将在中断上下文的堆栈上运行,除非体系结构自动在 HW 中进行堆栈切换。
-
中断锁定状态与 ISR 运行时硬件设置的方式相同。在进入中断锁定的 ISR 的架构上,它们将保持锁定状态。
-
调度决策现在是可选的,由使用ISR_DIRECT_DECLARE()宏实现的 ISR 的返回值控制
-
现在可以选择调用操作系统退出电源管理空闲状态。普通中断总是在 ISR 运行之前执行此操作,但现在运行时由ISR_DIRECT_PM()宏的位置控制,或者完全省略。
ISR_DIRECT_HEADER
ISR_DIRECT_HEADER ()
执行 ISR 主体之前的常见任务。
该宏必须位于所有直接中断的开头,并在 ISR 本身运行之前执行最少的特定于体系结构的任务。它不带任何参数,也没有返回值。
ISR_DIRECT_FOOTER
ISR_DIRECT_FOOTER ( check_reschedule )
退出 ISR 主体之前的常见任务。
该宏必须位于所有直接中断的末尾,并执行最少的特定于体系结构的任务,例如 EOI。它没有返回值。
在普通中断中,如果当前线程是可抢占的并且内核的就绪队列缓存中存在另一个线程准备运行,则会在中断结束时进行检查以调用 z_swap() 逻辑。现在这是可选的,并由 check_reschedule 参数控制。如果不确定,请设置为非零。在软件中执行堆栈切换和嵌套中断跟踪的系统上,仅当这是非嵌套中断时才应调用 z_swap()。
参数:
-
check_reschedule – 如果非零,则另外调用调度逻辑
ISR_DIRECT_PM
ISR_DIRECT_PM ()
执行电源管理空闲退出逻辑。
该宏可以选择在 IRQ_DIRECT_HEADER() 和 IRQ_DIRECT_FOOTER() 调用之间的某个位置调用。它执行退出电源管理空闲状态所需的任务。它不接受任何参数,也不返回任何参数。
ISR_DIRECT_DECLARE
ISR_DIRECT_DECLARE(name) name -- ISR的符合名称
用于声明直接中断服务例程的辅助宏。这将声明函数并自动包含ISR_DIRECT_FOOTER()和ISR_DIRECT_HEADER()宏。如果可能要做出调度决策,该函数应返回非零状态。
irq_lock
irq_lock()
锁定中断。该函数返回一个无符号整数"锁定密钥"(key)。"锁定密钥"必须传递给irq_unlock()以重新启用中断。
该例程可由 ISR 或线程调用。如果是线程调用,则中断锁是线程特定的;这意味着仅当线程运行时中断才保持禁用状态。如果线程执行允许另一个线程运行的操作(例如,给出信号量或休眠 N 毫秒),则中断锁不再适用,并且在其他处理发生时可以重新启用中断。当线程再次成为当前线程时,内核重新建立其中断锁;这可以确保线程在显式释放其建立的中断锁之前不会被中断。
irq_unlock
irq_unlock()
解锁中断。key值是由irq_lock()生成的锁定密钥。
irq_enable
irq_enable ( irq )
启用 IRQ。该例程启用来自源irq的中断。
参数:irq – IRQ
irq_disable
irq_disable(irq)
禁用 IRQ。该例程禁用来自源irq的中断。
参数:irq – IRQ
irq_is_enabled
irq_is_enabled ( irq )
获取 IRQ 启用状态。该例程指示是否启用来自源irq的中断。
参数:irq – IRQ 。
返回值:中断使能状态,true 或 false
示例
常规ISR
#define MY_DEV_IRQ 24 /* device uses IRQ 24 */
#define MY_DEV_PRIO 2 /* device uses interrupt priority 2 */
/* argument passed to my_isr(), in this case a pointer to the device */
#define MY_ISR_ARG DEVICE_GET(my_device)
#define MY_IRQ_FLAGS 0 /* IRQ flags */
void my_isr(void *arg)
{
... /* ISR code */
}
void my_isr_installer(void)
{
...
IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_ISR_ARG, MY_IRQ_FLAGS);
irq_enable(MY_DEV_IRQ);
...
}
动态ISR
#include <stdio.h>
#include <rtems.h>
void first_interrupt_handler(rtems_vector_number vector)
{
// 第一个中断处理程序的实现
printf("Handling first dynamic interrupt number %d\n", vector);
}
void second_interrupt_handler(rtems_vector_number vector)
{
// 第二个中断处理程序的实现
printf("Handling second dynamic interrupt number %d\n", vector);
}
int main()
{
rtems_status_code status;
// 初始化和其他代码
// 动态连接第一个中断处理程序
status = irq_connect_dynamic(vector_number, 1, first_interrupt_handler, NULL, 0);
if (status != RTEMS_SUCCESSFUL) {
printf("Failed to connect dynamic interrupt handler\n");
}
// 某些条件下,需要更改中断处理程序为第二个处理程序
if (some_condition) {
// 先断开连接
status = irq_disconnect(vector_number);
if (status != RTEMS_SUCCESSFUL) {
printf("Failed to disconnect interrupt handler\n");
}
// 动态连接第二个中断处理程序
status = irq_connect_dynamic(vector_number, 1, second_interrupt_handler, NULL, 0);
if (status != RTEMS_SUCCESSFUL) {
printf("Failed to connect dynamic interrupt handler\n");
}
}
return 0;
}
直接ISR
#include <irq.h>
#include <arch/cpu.h>
// 定义一个直接的中断服务例程
ISR_DIRECT_DECLARE(my_isr)
{
// 这里是处理中断的代码,假设我们只是简单地打印一条消息
printk("Interrupt handled!\n");
// 如果可能需要进行上下文切换,返回1
return 1;
}
void main(void)
{
// 这里是中断的IRQ号,你需要根据你的硬件来设置
int irq_num = 0;
// 这里是中断的优先级,你需要根据你的需求来设置
int priority = 0;
// 连接中断
IRQ_CONNECT(irq_num, priority, my_isr, NULL, 0);
// 启用中断
irq_enable(irq_num);
// 这里是主程序的其他代码...
}