异常
:异常是指任何打断处理器正常执行,并且迫使处理器进入一个由有特权的特殊指令执行的事件。可以分为同步异常和异步异常。同步异常:由内部事件(像处理器指令运行产生的事件)引起的异常。(算术运算、校准异常)
异步异常:主要是指由于外部异常源产生的异常,是一个由外部硬件装置产生的事件引起的异步异常。
中断
:是指中央处理器 CPU 正在处理某件事的时候,外部发生了某一事件,请求 CPU 迅速处理,CPU暂时中断当前的工作,转入处理所发生的事件,处理完后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。(属于异步异常)中断一般用于处理比较紧急的事件,并且只是做简单的处理,比如标记一下,中断需要快进快出,他会中断CPU的处理过程,使其立即停止来响应当前的中断请求。这样可以使CPU把大量时间耗费在等待、查询外设状态的操作上,可以大大提高系统实时性以及执行效率。
FreeRTOS 源码中有许多处临界段的地方,临界段虽然保护了关键代码的执行不被打断,但也会影响系统的实时,任何使用了操作系统的中断响应都不会比裸机快。
比如,某个时候有一个任务在运行中,并且该任务部分程序将中断屏蔽掉,也就是进入临界段中,这个时候如果有一个紧急的中断事件被触发,这个中断就会被挂起,不能得到及时响应,必须等到中断开启才可以得到响应,如果屏蔽中断时间超过了紧急中断能够容忍的程度,具有很大的危害。所以操作系统的中断在某些时候会有适当的中断延迟,所以进入临界段的时候,也需要快进快出。RTOS也能允许一些高优先级的中断不被屏蔽掉,能够及时做出响应,这些中断不受系统管理,也不允许调用RTOS中与中断相关的任何API函数接口。
RTOS的中断管理支持:
与中断相关的硬件可以划分为三类:外设、中断控制器、CPU 本身。
Nested Vectored Interrupt Controller
)。与中断相关的名词:
中断号:每个中断请求信号都会有特定的标志,使得计算机能够判断是哪个设备提出的中断请求,这个标志就是中断号。
中断请求:“紧急事件”需向 CPU 提出申请,要求 CPU 暂停当前执行的任务,转而处理该“紧急事件”,这一申请过程称为中断请求。
中断优先级:为使系统能够及时响应并处理所有中断,系统根据中断时间的重要性和紧迫程度,将中断源分为若干个级别,称作中断优先级。
中断处理程序:当外设产生中断请求后,CPU 暂停当前的任务,转而响应中断申请,即执行中断处理程序。
中断触发:中断源发出并送给 CPU 控制信号,将中断触发器置“1”,表明该中断源产生了中断,要求 CPU 去响应该中断,CPU 暂停当前任务,执行相应的中断处理程序。
中断触发类型:外部中断申请通过一个物理信号发送到 NVIC,可以是电平触发或边沿触发。
中断向量:中断服务程序的入口地址。
中断向量表:存储中断向量的存储区,中断向量与中断号对应,中断向量在中断向量表中按照中断号顺序存储。
临界段:代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开中断。
当中断产生时,处理机将按如下的顺序执行:
- 保存当前处理机状态信息
- 载入异常或中断处理函数到 PC寄存器
- 把控制权转交给处理函数并开始执行
- 当处理函数执行完成时,恢复处理器状态信息
- 从异常或中断中返回到前一个程序执行点
中断使得 CPU 可以在事件发生时才给予处理,而不必让 CPU 连续不断地查询是否有相应的事件发生。通过两条特殊指令:关中断和开中断可以让处理器不响应或响应中断,在关闭中断期间,通常处理器会把新产生的中断挂起,当中断打开时立刻进行响应,所以会有适当的延时响应中断,故用户在进入临界区的时候应快进快出。
中断发生的环境有两种情况:在任务的上下文中,在中断服务函数处理上下文中。
即使操作系统的响应很快了,但对于中断的处理仍然存在着中断延迟响应的问题,即有时无法第一时间响应中断。中断延迟是指从硬件中断发生到开始执行中断处理程序第一条指令之间的这段时间,具体为系统接收到中断信号到操作系统作出响应,并完成换到转入中断服务程序的时间。
中断时间:(外部)硬件(设备)发生中断,到系统执行中断服务子程序(ISR)的第一条指令的时间。
等待中断打开时间:当前系统正在处理一个中断(高优先级),此时另一个中断到来了(低优先级),系统也不会立即响应,等待处理完这个中断才会处理后来的中断。
关闭中断时间:在操作系统中,很多时候我们会主动进入临界段,系统不允许当前状态被中断打断,故而在临界区发生的中断会被挂起,直到退出临界段时候打开中断。
中断延迟 = 中断时间 + 等待中断打开时间(不一定存在)+ 关闭中断时间(不一定存在)
RTOS中所有的中断都由 RTOS 的软件管理,硬件来了中断时,由软件决定是否响应,可以挂起中断,延迟响应或者不响应。所以需要我们自己配置中断,使能中断,编写中断服务函数,在中断服务函数中使用内核 IPC 通信机制,一般建议使用信号量、消息或事件标志组等标志事件的发生,将事件发布给处理任务,等退出中断后再由相关处理任务具体处理中断。
用 户 可 以 自 定 义 配 置 系 统 可 管 理 的 最 高 中 断 优 先 级 的 宏 定 义configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
, 它 是 用 于 配 置 内 核 中 的basepri
寄存器的,当 basepri
设置为某个值的时候,NVIC
不会响应比该优先级低的中断,而优先级比之更高的中断则不受影响。
也就是说比所设置为5时,0~4的中断在临界段也会被触发,而不是等到退出临界段的时候被触发,这些中断服务函数也不能调用RTOS所提供的API函数接口,而中断优先级在 5 到 15 的这些中断是可以被屏蔽的,也能安全调用 FreeRTOS 提供的 API 函数接口。
ARM Cortex-M NVIC 支持中断嵌套功能:当一个中断触发并且系统进行响应时,处理器硬件会将当前运行的部分上下文寄存器自动压入中断栈中,这部分的寄存器包括 PSR,R0,R1,R2,R3 以及 R12 寄存器。(硬件行为)
FreeRTOS 在 Cortex-M 系列处理器上也遵循与裸机中断一致的方法,当用户需要使用自定义的中断服务例程时,只需要定义相同名称的函数覆盖弱化符号即可。所以RTOS的中断与裸机的中断并没有什么区别。编写函数时,按照裸机编写的流程进行编写即可。
比如:
void xxx_IRQHandler(void)
{
BaseType_t pxHigherPriorityTaskWoken;
uint32_t ulReturn;
/* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();
//确保是否产生了 EXTI Line 中断
if (EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET) {
/* 将数据写入(发送)到队列中,等待时间为 0 */
xQueueSendFromISR(Test_Queue, /* 消息队列的句柄 */
&send_data2,/* 发送的消息内容 */
&pxHigherPriorityTaskWoken);
//如果需要的话进行一次任务切换
portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
//清除中断标志位
EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);
}
/* 退出临界段 */
taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}
CPU 使用率其实就是系统运行的程序占用的 CPU 资源,表示机器在某段时间程序运行的情况,如果这段时间中,程序一直在占用 CPU 的使用权,那么这个时候CPU的利用率为100%。CPU 的利用率越高,说明机器在这个时间上运行了很多程序,反之较少。
FreeRTOS 是多任务操作系统,对 CPU 都是分时使用的:比如 A 任务占用 10ms,然后 B 任务占用 30ms,然后空闲 60ms,再又是 A任务占 10ms,B 任务占 30ms,空闲 60ms;如果在一段时间内都是如此,那么这段时间内的利用率为 40%,因为整个系统中只有 40%的时间是 CPU 处理数据的时间。
一个系统设计的好坏,可以使用 CPU 使用率来衡量,一个好的系统必然是能完美响应急需的处理,并且系统的资源不会过于浪费(性价比高)。也就是说这个系统空闲时间少,但是有需要解决有时候紧急响应的问题。作为产品的设计,既不能让资源过于浪费,也不能让资源过于紧迫,这种设计才是完美的,在需要的时候能及时处理完突发事件,而且资源也不会过剩,性价比更高。
假设一个系统的CPU 利用率经常在 90%~100%徘徊,那么系统就很少有空闲的时候,这时候突然有一些事情急需 CPU 的处理,但是此时 CPU 都很可能被其他任务在占用了,那么这个紧急事件就有可能无法被相应,即使能被相应,那么占用 CPU 的任务又处于等待状态,这种系统就是不够完美的,因为资源处理得太过于紧迫;反过来,假如 CPU 的利用率在 1%以下,那么我们就可以认为这种产品的资源过于浪费,空闲状态保持时间久。
FreeRTOS提供测量各个任务占用CPU 时间的函数接口,我们可以知道系统中的每个任务占用 CPU 的时间,从而得知系统设计的是否合理。FreeRTOS 是使用一个外部的变量进行统计时间的,并且消耗一个高精度的定时器,其用于定时的精度是系统时钟节拍的 10-20 倍,而且 FreeRTOS 进行 CPU 利用率统计的时候,也有一定缺陷,因为它没有对进行 CPU 利用率统计时间的变量做溢出保护,我们使用的是 32 位变量来系统运行的时间计数值,而按 20000HZ 的中断频率计算,每进入一中断就是 50us,变量加一,最大支持计数时间:2^32 * 50us / 3600s =59.6 分钟,运行时间超过了 59.6 分钟后统计的结果将不准确,除此之外整个系统一直响应定时器 50us 一次的中断会比较影响系统的性能。
用户想要使用使用 CPU 利 用 率统 计 的 话 , 需 要 自 定 义 配 置 一 下 ,首 先 在FreeRTOSConfig.h 配置与系统运行时间和任务状态收集有关的配置选项,并且实现portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
与portGET_RUN_TIME_COUNTER_VALUE()
这两个宏定义。
//启用运行时间统计功能
#define configGENERATE_RUN_TIME_STATS 1
//启用可视化跟踪调试
#define configUSE_TRACE_FACILITY 1
// 与宏 configUSE_TRACE_FACILITY 同时为 1 时会编译下面 3 个函数
// prvWriteNameToBuffer()
// vTaskList(),
// vTaskGetRunTimeStats()
extern volatile uint32_t CPU_RunTime; // 实现定时器,时间到了就++,用于统计运行时间
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (CPU_RunTime = 0ul)
#define portGET_RUN_TIME_COUNTER_VALUE() CPU_RunTime
之后编写中断服务函数:
volatile uint32_t CPU_RunTime = 0UL;
void BASIC_TIM_IRQHandler (void)
{
if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) {
CPU_RunTime++;
TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);
}
}
在任务中调用 vTaskGetRunTimeStats()
和 vTaskList()
函数获得任务的相关信息与 CPU 使用率的相关信息,然后打印出来即可。
static void CPU_Task(void* parameter)
{
uint8_t CPU_RunInfo[400]; //保存任务运行时间信息
while (1) {
memset(CPU_RunInfo,0,400); //信息缓冲区清零
vTaskList((char *)&CPU_RunInfo); //获取任务运行时间信息
printf("---------------------------------------------\r\n");
printf("任务名 任务状态 优先级 剩余栈 任务序号\r\n");
printf("%s", CPU_RunInfo);
printf("---------------------------------------------\r\n");
memset(CPU_RunInfo,0,400); //信息缓冲区清零
vTaskGetRunTimeStats((char *)&CPU_RunInfo);
printf("任务名 运行计数 使用率\r\n");
printf("%s", CPU_RunInfo);
printf("---------------------------------------------\r\n\n");
vTaskDelay(1000); /* 延时 500 个 tick */
}
}