【嵌入式八股14】RTOS
一、RT-Thread 内存管理算法
RT-Thread 通过开辟静态数组的方式来管理内存,以下是具体的定义:
#define RT_HEAP_SIZE 6*1024
/* 从内部 SRAM 申请一块静态内存来作为内存堆使用 */
static uint32_t rt_heap[RT_HEAP_SIZE]; // heap default size: 24K(1024 * 4 * 6)
RT-Thread 提供了多种内存管理算法,适用于不同的内存场景,具体如下:
| mem 小内存 | mem.c | 适用于 2MB 以内的小内存设备 | 如同一个瓜,根据需求吃多少切多少,灵活分配小内存 |
| slab 大内存 | slab.c | 适用于大内存设备,采用内存池管理方式 | 类似一个已经切好固定大小块的瓜,需要时直接拿对应的块,提高分配效率 |
| memheap 多内存 | memheap.c | 可对多个内存设备进行合并管理 | 好比多个瓜,吃完一个接着拿下一个,实现不同内存区域的统一调配 |
- mem 小内存管理算法(类似 heap_4):
- 数据结构:采用链表组织内存块,每个链表表项包含 {magic(用于判断是否被非法改写), used(标识是否被使用), next(指针域,指向下一个表项), prev(指针域,指向前一个表项)}。
- 内存分配操作:以分配 64 Byte 内存为例,从链表表头开始遍历,寻找可用的内存空间进行分配(需要注意表头本身占用 3*4 Byte 的空间)。
- 内存释放操作:释放内存时,更改 used 表项的状态,并检查前后相邻的内存块是否为空闲状态。若有相邻空闲块,则将它们合并为一个大的内存块,以提高内存利用率。
- slab 大内存管理算法(内存池):为避免频繁的内存分配和释放操作带来的性能开销,该算法提前将内存划分成固定大小的块,形成内存池。当需要使用内存时,直接从内存池中获取相应的内存块;使用完毕后,将内存块归还到内存池中,从而实现高效的内存管理。
- memheap 内存管理算法(类似 heap_5):此算法能够将多个不连续的内存地址进行合并拼接,实现对不同内存区域的统一管理和使用。这对于一些具有分散内存资源的嵌入式系统来说,能够有效地提高内存的使用效率和灵活性。
二、RT-Thread 链表
- 链表类型对比:
- 普通双向循环链表:通常针对每一个数据结构固定的节点进行操作,节点的数据类型和结构在链表创建时就已经确定,灵活性相对较低。
- RTT 中双向循环链表:数据结构不固定,其指针域指向下一个指针域,这使得插入的元素可以为不同类型,大大提高了链表的通用性和灵活性。
- 链表操作函数:
- 指定节点前插入:
rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{
l->prev->next = n;
n->prev = l->prev;
l->prev = n;
n->next = l;
}
- **指定节点后插入**:
rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{
l->next->prev = n;
n->next = l->next;
l->next = n;
n->prev = l;
}
- **删除节点**:
rt_inline void rt_list_remove(rt_list_t *n)
{
n->next->prev = n->prev;
n->prev->next = n->next;
n->next = n->prev = n;
}
- 节点元素的访问:由于在 RTT 链表的节点中,指针域的存放位置不确定,因此需要通过一种宏定义来从指针域寻找对应的结构体元素。具体来说,就是通过 rt_list_t 成员的地址来访问节点中的其他元素。虽然不同类型节点中 rt_list_t 成员的位置各不相同,但在确定类型的节点中,rt_list_t 成员的偏移是固定的。在获取 rt_list_t 成员地址的情况下,可以通过计算 (
rt_list_t成员地址) - (rt_list_t成员偏移)得到节点的起始地址。RT-Thread 中提供了相应的算法和宏定义rt_container_of来实现这一功能:
/**
* Double List structure
*/
struct rt_list_node
{
struct rt_list_node *next; /**< point to next node. */
struct rt_list_node *prev; /**< point to prev node. */
};
typedef struct rt_list_node rt_list_t; /**< Type for lists. */
struct rt_thread
{
char name[RT_NAME_MAX]; /**< the name of thread */
rt_list_t list; /**< the object list */
rt_list_t tlist; /**< the thread list */
rt_uint8_t current_priority; /**< current priority */
rt_uint8_t init_priority; /**< initialized priority */
};
typedef struct rt_thread *rt_thread_t;
#define rt_container_of(ptr, type, member) \
((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))
//ptr: 成员首地址(指针域地址,例如 rt_thread_priority_table[highest_ready_priority].next)
//type: 结构体类型(例如 struct rt_thread)
//member: 结构体成员名称(例如 tlist)
三、RT-Thread 抢占式调度实现
假设存在两个线程,低优先级的 t2 任务在 while(1) 循环中执行耗时任务,高优先级的 t1 任务则进行抢占式打印操作,随后进入阻塞状态。调度器的具体执行顺序如下:
- 高优先级任务
t1首先执行,当执行到rt_thread_mdelay()函数时,该函数会调用rt_thread_sleep()中的rt_schedule()函数,将t1任务挂起。 - 调度器介入,通过特定的算法寻找到当前最高优先级的任务(此时为
t2任务),并让其运行。 - 在低优先级任务
t2的时间片未到期的情况下,由于高优先级任务t1的rt_thread_mdelay()超时,其定时计数器会发生相应的变化。 - 当下一个节拍周期到达时,会定时执行
rt_tick_increase()函数,该函数会调用rt_timer_check()中的timeout_func()函数。 - 通过函数指针跳转到
rt_thread_timeout()函数,在该函数中执行rt_schedule()函数,触发任务调度。 - 进入 PendSV 中断处理函数,在该函数中进行线程的上下文切换,将 CPU 的控制权交给下一个任务。
四、FreeRTOS 内存管理
- 内存位置:FreeRTOS 的内存位于
.bss段,而不是传统意义上的 heap(即启动文件中所定义的堆空间大小)。当使用pvPortMalloc函数申请内存时,实际上是从这个系统堆(位于.bss段)中进行申请的。
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 100 * 1024 ) // 申请 100KB 内存用于 RTOS 系统堆内存
- 内存管理策略示例:以
heap_4.c内存管理策略为例,在map文件中可以看到 FreeRTOS 使用一个静态数组作为 HEAP,该数组定义在heap_4.c文件中。由于这个 HEAP 来自于静态数组,所以它存在于数据段(具体为.bss段),而不是通常所认为的系统堆。以下是map文件中的相关信息示例:
.bss zero 0x2021'7d1c 0x1'9000 heap_4.o [35] // 实际位于.bss 段
Entry Address Size Type Object
----- ------- ---- ---- ------
ucHeap 0x2021'7d1c 0x1'9000 Data Lc heap_4.o [35] // 起始地址与大小
五、FreeRTOS 任务调度
- 调度依据:系统通过时钟来判断当前最高优先级的任务,并进行相应的调度,确保高优先级的任务能够优先获得 CPU 资源,从而保证系统的实时性和响应速度。
- 任务让出 CPU 使用权:当前任务可以主动执行
taskYIELD()或portYIELD_FROM_ISR()函数,让出 CPU 的使用权,以便调度器能够切换到其他任务进行执行。
六、FreeRTOS 创建任务
在 FreeRTOS 中创建任务时,会在堆中(实际上是位于 .bss 段的系统堆)通过 pvPortMalloc 函数分配内存给任务控制块(TCB),用于存储任务的相关信息,如任务的状态、优先级、上下文等。
七、任务堆栈
在创建任务时,用户可以选择动态创建或静态创建任务堆栈。静态的任务栈在任务结束后无法被回收,会一直占用内存空间;而动态的任务栈则可以在任务结束后,将分配的内存空间归还给系统,提高内存的使用效率。
八、RTOS 堆栈溢出的检测
- 方案 1:检查栈指针是否越界:
- 检测方法:在调度时,利用任务保存的栈顶和栈大小信息,检查栈指针是否越界。即每次任务切换时,对比栈指针的位置与栈顶和栈底的位置关系,判断是否发生了堆栈溢出。
- 优点:能够快速检测到堆栈溢出的情况,只要栈指针超出了预设的范围,就能立即发现问题。
- 缺点:对于任务运行时发生堆栈溢出,但在切换任务前又恢复正常的情况,无法进行有效的检测。因为在任务切换时,栈指针可能已经回到了正常范围内,导致检测失败。
- 方案 2:检查栈末尾字节是否改变:
- 检测方法:在创建任务时,将栈末尾的 16 个字节初始化为特定的字符。每次任务切换时,判断这些特定字符是否被改写。如果被改写,则说明发生了堆栈溢出。
- 优点:可检出几乎所有的堆栈溢出情况,因为只要堆栈发生溢出,就很可能会覆盖栈末尾的这些特定字符。
- 缺点:检测速度相对较慢,因为每次任务切换时都需要对栈末尾的 16 个字节进行比较操作,增加了系统的开销。
九、RT-Thread PendSV 系统调用--上下文切换
- 省流版总结:OS 调度依赖于 systick,其优先级最低。当有 ISR(中断服务程序)时,ISR 会抢占 OS 调度先执行;在无 ISR 时,OS 调度实际由 PendSV 执行;若在调度过程中 ISR 到来,则 ISR 会插队执行,执行完毕后再继续进行调度。
- 不同优先级设置下的情况分析:
- 方法 1 - 无 PendSV - SysTick 最高优先级(Fault 异常):假如在产生异常时,CPU 正在响应另一个中断 ISR,且 SysTick 的优先级大于 ISR,此时 SysTick 会抢占 ISR 获取 CPU 使用权。然而,在 SysTick 中不能进行上下文切换,因为这会导致中断 ISR 被延迟,在对实时性要求较高的系统中是不可接受的。此外,由于 IRQ(中断请求)未得到及时响应,而执行了线程,会触发 Fault 异常,影响系统的稳定性和实时性能。
- 方法 2 - 无 PendSV - SysTick 最低优先级(无法满足实时性):将 SysTick 的优先级设置为最低,然后在 SysTick 中进行上下文切换。一般情况下,OS 在调度任务时会关闭中断,进入临界区。但由于 OS 任务调度需要耗费一定时间,这就可能出现一种情况:在任务调度期间,如果新的外部 IRQ 发生,CPU 将无法快速响应处理,导致系统的实时性受到影响。
- PendSV - SysTick 最低优先级(实际方案):将 SysTick 的优先级调低,避免了触发 Fault 的问题,但会影响外部中断 IRQ 的处理速度。为了进一步优化,引入了 PenSV。由于 PendSV 具有【缓期执行】的特点,可将 OS 调度过程拆分为 2 段:
- 滴答定时器中断,主要进行业务调度前的判断工作,不进行任务切换。
- 触发 PendSV,PendSV 并不会立即执行,因为其优先级最低。如果此时正好有 IRQ 请求,那么先响应 IRQ,等到所有优先级高于 PendSV 的 IRQ 都执行完毕后,再执行 PendSV 进行任务调度(PendSV 可被打断)。
- 实际方案的缺陷:
- SysTick 的优先级最低,如果外部 IRQ 比较频繁,可能会导致 SysTick 经常被挂起,进而滞后,使得 Systick 的节拍延长,导致系统时钟不准确。
- 由于上述原因,任务的执行调度速度可能会受到影响,无法及时响应任务的切换请求。
- 实际方案的具体实现流程:
- 任务 A 请求 SVC(supervisor call,系统调用)进行任务切换。
- 内核收到请求后,挂起 PendSV 异常。
- CPU 退出 SVC 后进入 PendSV,执行上下文切换操作。
- PendSV 执行完毕后返回任务 B。
- 中断发生,执行 ISR(子中断服务程序)。
- ISR 执行过程中,心跳到达,SysTick 异常发生,抢占了 ISR。
- PendSV 准备进行上下文切换。
- SysTick 退出后,继续执行 ISR。
- ISR 执行完毕,进入 PendSV 进行上下文切换。
- 切换至任务 A。
十、SVC 中断
- SVC 和 PendSV 的作用:SVC(系统服务调用)和 PendSV(可悬挂系统调用)在操作系统之上的软件开发中有着重要的应用。SVC 主要用于产生系统函数的调用请求。例如,在操作系统中,为了保证系统的安全性和稳定性,不让用户程序直接访问硬件,而是通过提供一些系统服务函数来实现对硬件的间接访问。用户程序使用 SVC 发出对系统服务函数的呼叫请求,当用户程序想要控制特定的硬件时,就会产生一个 SVC 异常,然后操作系统提供的 SVC 异常服务例程得到执行,它再调用相关的操作系统函数,完成用户程序请求的服务。
- SVC 异常的触发和处理:系统调用会处理异常,实现用户与内核之间的交互。当用户想要执行一些与内核相关的功能时,必须通过 SVC 异常,使内核进入异常模式,从而调用执行内核的源码。一旦触发 SVC 异常,会立即执行 SVC 异常代码。
- SVC 在 FreeRTOS 中的应用:在 FreeRTOS 中,任务调度器触发了
SVC中断来启动第一个任务,之后的工作主要依靠PendSV和SysTick中断触发来实现。SVC是系统服务调用,由SVC指令触发调用,在 FreeRTOS 中用于在任务调度中开启第一个任务,触发指令为svc 0。SVC中断本质上是软中断,为用户提供了一个访问硬件的接口;而PendSV中断相对SVC来说,可以被延迟执行,主要用于任务切换,确保系统在合适的时机进行任务的切换,提高系统的性能和稳定性。
十一、FreeRTOS 中 _FROM_ISR
- 作用:在 FreeRTOS 中,带有
_FROM_ISR后缀的 API 是专门用于在中断中调用的。这些 API 会禁用调度器,以避免在中断处理期间发生任务调度,从而保证对临界区资源的快速访问,防止出现资源竞争等问题。同时,它们不包含延时等阻塞操作,确保中断处理能够尽快完成,符合中断处理快进快出的原则,保证系统的实时性和稳定性。 - 与 RT-Thread 的对比:RT-Thread 中没有完全类似的 API 设计。在 RT-Thread 中,对于中断处理相关的操作,没有专门以这种形式来区分的 API,仅有延时参数选项等其他方式来控制中断处理过程中的一些行为。
十二、RT-Thread 同步互斥与通信
RT-Thread 提供了多种内核对象用于实现任务之间的同步、互斥与通信,它们各自具有不同的特点和适用场景,具体如下:
| Semaphore(信号量) | 所有任务 | 所有任务 | 数量范围为 0 到 n | 主要用于维护一定数量的资源,通过信号量的计数来控制对资源的访问,当信号量的值大于 0 时,任务可以获取信号量并访问资源,否则任务将等待信号量的释放 |
| Mutex(互斥锁) | 任务 A 上锁 | 只能由任务 A 开锁 | 取值为 bit 0、1 | 用于保护单一的互斥资源,确保同一时间只有一个任务能够访问该资源,避免资源竞争和数据不一致问题 |
| Event(事件) | 所有任务 | 所有任务 | 多个 bit | 用于在任务之间传递事件,通过设置和清除事件标志位来实现任务的同步,当某个任务等待的事件发生时,该任务将被唤醒继续执行 |
| Mail box(邮箱) | 所有任务 | 所有任务 | 固定大小为 4 Byte,通常传递指针 | 用于在任务之间传递指针,通过邮箱可以快速地将一个指向数据的指针传递给其他任务,而不需要进行数据的复制,提高了通信效率 |
| Message queue(消息队列) | 所有任务 | 所有任务 | 若干数据(通常以结构体形式) | 可以承载一定数量的数据,用于在任务之间传递复杂的数据结构,任务可以向消息队列中发送消息,也可以从消息队列中接收消息,实现数据的共享和传递 |
| Signal(信号) | 无特定 | 无特定 | 无特定 | 类似于软中断,主要用于唤醒等待的任务,当某个信号发生时,相应的任务将被唤醒并执行相关操作 |
十三、RT-Thread 消息队列、邮箱、信号量区别
- 全局变量通信的局限性:使用全局变量进行通信虽然可以承载通信的内容,但存在明显的缺陷。因为全局变量无法主动告知接收方数据的到达,接收方需要不断地轮询全局变量来检查数据是否更新,这会占用大量的 CPU 资源,降低系统的效率。
- 信号量的特点:信号量主要的作用是告知接收方信息已经到达,但它并不携带具体的数据内容。例如,当一个任务完成了某个操作后,可以释放一个信号量来通知其他任务,但其他任务并不知道具体发生了什么,还需要通过其他方式获取详细信息。
- 消息队列的优势:消息队列不仅能够承载信息内容,还能有效地告知接收方信息的到达。任务可以将数据以结构体等形式发送到消息队列中,接收方任务可以从消息队列中接收消息,从而获取具体的数据内容,实现了数据的可靠传递和共享。
- 邮箱的特点:邮箱主要用于 4 Byte 的通信,它通过传递指针而不是使用
memcpy()函数来复制数据,因此开销较小。这种方式适用于需要快速传递少量数据(如指针)的场景,能够提高通信的效率。
十四、RTOS 优先级的分配原则
在 RTOS 中,任务优先级的分配需要综合考虑多个因素,以确保系统的高效运行和实时性要求,具体原则如下:
- 任务对响应的敏感性:对于那些对响应时间要求极高的任务,如串口接收中断等任务,由于它们需要及时处理外部输入的数据,否则可能会导致数据丢失或系统错误,因此应将其优先级设置为最高。
- 执行时长:考虑到 RTOS 通常采用抢占式调度策略,如果高优先级任务执行时间过长,可能会导致低优先级任务长时间得不到调度,从而出现饥饿现象。因此,对于像电机 PID 计算以及控制这类需要固定控制周期的任务,虽然它们对实时性也有较高要求,但为了避免影响其他任务的执行,其优先级应设置得较高,但不是最高。
- 任务的重要性和实时性需求:看门狗任务用于监控系统的运行状态,确保系统的稳定性;按键处理任务用于响应用户的输入操作。这两类任务的实时性要求相对中等,因此它们的优先级可设置为中等。
- APP 层任务的优先级:最低优先级通常分配给 APP 层的心跳和信息显示任务等。这些任务对实时性要求较低,即使稍微延迟执行也不会对系统的核心功能产生严重影响。
十五、FreeRTOS 优先级
在 FreeRTOS 中,优先级的设定规则与一些其他 RTOS 不同,其高优先级对应的数字较大。这意味着在任务调度时,数字较大的优先级任务会优先于数字较小的优先级任务得到执行。
十六、优先级反转
- 定义:当使用信号量进行任务同步和互斥时,可能会出现优先级反转的情况。具体表现为高优先级任务被低优先级任务阻塞,导致高优先级任务迟迟得不到调度。然而,其他中等优先级的任务却能够抢到 CPU 资源。从现象上看,就好像是中优先级的任务比高优先级任务具有更高的优先权。
- 示例:假设有三个任务,高优先级任务 H、中等优先级任务 M 和低优先级任务 L。低优先级任务 L 获得了某个信号量,正在访问共享资源。此时高优先级任务 H 也需要访问该共享资源,由于信号量被占用,高优先级任务 H 被阻塞。而在这个过程中,中等优先级任务 M 可以正常运行,抢占了 CPU 资源,使得高优先级任务 H 无法及时执行,从而出现了优先级反转的现象。
十七、RT-Thread 内核移植
- CPU 架构移植:为了使 RT-Thread 能够在不同的 CPU 架构,如 RISC-V、Cortex-M 等上运行,需要进行 CPU 架构移植。这涉及到对上下文切换、时钟配置以及中断操作等方面的适配。例如,不同架构的 CPU 在上下文切换时保存和恢复的寄存器可能不同,需要根据具体架构进行相应的调整;时钟配置也需要根据不同架构的时钟源和时钟控制寄存器进行设置,以确保系统时钟的准确性;中断操作则需要适配不同架构的中断控制器和中断处理机制。
- BSP 移植:对于相同架构的 CPU,但不同的外设配置,需要进行 BSP(Board Support Package,板级支持包)移植。BSP 移植主要包括对不同外设的驱动适配,确保 RT-Thread 能够正确地访问和控制这些外设。同时,还涉及到动态内存管理的适配,根据硬件平台的内存布局和需求,合理地管理内存资源,提高内存的使用效率。
十八、RT-Thread POSIX 标准
POSIX(Portable operating system interface,可移植操作系统接口)标准在 RT-Thread 中的应用,旨在保证应用程序在不同操作系统下的可移植性。通过遵循 POSIX 标准,开发者可以编写具有较高可移植性的代码,使得应用程序能够在支持 POSIX 标准的不同操作系统之间轻松迁移,减少了因操作系统差异带来的开发和维护成本,提高了软件开发的效率和可维护性。
十九、RT-Thread 单元测试
- 定义:RT-Thread 中的单元测试是指对软件中的最小可测试单元,如函数、方法、类、功能模块等进行检查和验证的过程。通过单元测试,可以确保每个单元的功能正确性,及时发现和修复潜在的错误,提高软件的质量和可靠性。
- utest 框架(unit test):RT-Thread 提供了 utest 框架来支持单元测试。该框架提供了一系列的工具和接口,方便开发者编写、运行和管理单元测试用例。开发者可以使用 utest 框架定义测试用例、设置测试环境、执行测试并查看测试结果,从而有效地进行单元测试工作。
二十、RT-Thread 崩溃调试
在 RT-Thread 中,CmBacktrace 函数在系统崩溃调试中发挥着重要作用。当系统发生崩溃时,该函数能够保存线程栈和寄存器的值。通过这些保存的信息,开发人员可以逆向分析函数的调用关系,追溯系统崩溃的原因。例如,可以查看在崩溃前执行了哪些函数,函数之间的参数传递情况等,从而快速定位问题所在,进行修复。
二十一、RTOS 中多线程看门狗
- 方案 1:在 RTOS 中,可以将喂狗操作放在最低优先级线程中。这样,如果高优先级线程长时间抢占 CPU 资源,导致最低优先级线程无法及时执行喂狗操作,看门狗就会超时。这种方式可以检测到高优先级任务长时间占用 CPU 而导致系统无法正常运行的情况。
- 方案 2:另一种方案是监控各线程的调度情况,在每个线程中放置定时任务进行喂狗操作。如果某个线程的定时任务超时未执行喂狗操作,就说明该线程可能出现了阻塞或其他异常情况。通过这种方式,可以更精确地检测到单个线程的运行状态,及时发现和处理线程级别的问题。
- 初始化时的操作:
- 系统复位时,从 0x00000000 处读出 MSP(主堆栈指针)的初始值,为系统的堆栈操作提供基础。
- 在 OS 初始化时,对 PSP(进程堆栈指针)进行初始化,确保每个任务都有独立的堆栈空间,用于保存任务的上下文信息。
- 任务调度时的操作:
- 当进行任务切换时,首先用任务 A 的 SP(堆栈指针)执行入栈操作,将任务 A 的当前上下文信息保存到堆栈中,并保存任务 A 的 SP 值。
- 然后设置 PSP 指向任务 B 的栈空间,用任务 B 的 SP 执行出栈操作,将任务 B 的上下文信息从堆栈中恢复出来,随后开始执行任务 B,实现任务的切换和上下文的恢复。
二十二、用户级和特权级
在 Cortex-M 架构中,分为两个运行级别:
- 处理模式:当系统发生异常与中断时,CPU 进入处理模式,此时工作在特权级。在特权级下,CPU 可以访问所有的系统资源,包括一些受保护的寄存器和内存区域,以便进行异常和中断的处理。
- 线程模式:在其他情况下,CPU 处于线程模式。线程模式可以工作在用户级和特权级,通过配置可以选择线程模式下的运行级别。在用户级下,CPU 的操作受到一定的限制,不能访问一些敏感的系统资源,从而提高系统的安全性;而在特权级下,线程模式可以访问更多的系统资源,适用于一些需要更高权限的操作。
二十三、NVIC(嵌套向量中断控制器)
NVIC 是 Cortex-M 架构中的一个重要组件,它支持中断嵌套功能。当一个中断触发并且系统进行响应时,处理器硬件会自动将当前运行位置的上下文寄存器压入中断栈中。这些上下文寄存器包括 PSR(程序状态寄存器)、PC(程序计数器)、LR(链接寄存器)、R12、R3 - R0 寄存器等。通过保存这些寄存器的值,当处理完中断后,系统可以准确地恢复到中断前的状态,继续执行被中断的程序,保证了系统的连续性和稳定性。
二十四、M3 M4 对比
- FPU 浮点功能:相较于 Cortex-M3,Cortex-M4 新增了 FPU(浮点运算单元)。在 Cortex-M3 中,计算浮点数据通常需要使用软件方式,这会消耗较多的 CPU 时间和资源,计算速度相对较慢。
- 计算速度优势:而 Cortex-M4 的硬件浮点计算功能使得浮点运算可以直接由 FPU 进行处理,大大提高了浮点计算的速度。这对于一些对浮点计算性能要求较高的应用,如数字信号处理、科学计算等领域,具有明显的优势,能够更高效地处理相关任务。
| 核心版本 | v7ME | v7M | v6M |
| 指令系统 | Thumb® / Thumb - 2 | Thumb® / Thumb - 2 | Thumb® / Thumb - 2 subset |
| 指令增强 | 单周期的16、32位MAC 单周期的双16位MAC 8、16位SIMD计算 硬件除法(2~12周期) |
硬件除法(2~12周期) 单周期 (32x32)乘法 支持饱和算术运算 |
可选的硬件单周期 (32x32)乘法 |
| 流水线 | 三级 + 分支推测 | 三级 | 三级 |
| 执行效率 | 2.19 CoreMark/MHz,1.25 DMIPS/MHz | 未提及 | 1.62 CoreMark/MHz,0.9 DMIPS/MHz |
| 存储器保护 | 可选。8区域管理,可划分子区域和后台区 | 没有 | 没有 |
| 中断 | 非屏蔽中断(NMI) + 1~240个物理中断源 | 非屏蔽中断(NMI) + 1~240个物理中断源 | 非屏蔽中断(NMI) + 1~32个物理中断源 |
| 中断优先级 | 8~256个优先级 | 未提及 | 4级优先 |
| 唤醒中断控制器 | 多达240个唤醒中断 | 未提及 | 可选 |
| 睡眠模式 | 集成WFI和WFE指令。退出时睡眠功能,睡眠和深度睡眠信号。 使用ARM的电源管理部件,可选择保持模式。 |
未提及 | 未提及 |
| 位操作 | 集成位指令和位带域 | 未提及 | 未提及 |
| 调试 | 可选的JTAG和SWD调试接口 支持最多8个断点和4个察看点 |
未提及 | 可选的JTAG和SW调试接口 支持最多4个断点和2个察看点 |
| 跟踪(可选) | 指令跟踪(ETM)、数据跟踪(DWT)和仪器跟踪(ITM)模块 | 未提及 | 未提及 |
一些八股模拟拷打Point,万一有点用呢


查看14道真题和解析