☰
Zynq-7000裸机迁移FreeRTOS实战:任务调度与中断改造
2026/10/7 14:50:42 网站建设 项目流程

第15期做完的时候,我的工程里跑的还是裸机主循环——串口能打印、PL侧的FIFO能轮询读写、中断能进、各个模块单测都过。看起来一切正常,但我心里清楚,这种"一切尽在掌控"其实是一种假象:所有外设都在同一个while(1)里排队,优先级、实时性、模块隔离全靠自觉,代码稍微再长一点就要乱。所以第16期我决定干一件改变工程结构的事:加入调度器。

这一期是整个bring-up过程里比较有分水岭意义的一步。Zynq-7000这颗芯片的真正价值在于PS与PL协同,而PS这一侧一旦从裸机模式切换到RTOS任务模式,后面所有外设驱动、算法逻辑、PL侧数据流的接入方式都会跟着变。本文就把我在这个阶段做的事情完整拆开来讲:为什么是这个时机选调度器、怎么选型、移植FreeRTOS到Cortex-A9有哪些关键步骤、中断要怎么改、实测又踩了哪些坑。适合正在做Zynq-7000平台bring-up、想把工程从玩具级推向工程化的朋友。

1. 先把第15期的"底子"摊开:这个平台此刻还缺什么

1.1 十五期结束时系统已经具备的能力

简单回顾一下我手上这套平台的现状。经过前面十几期的折腾,PS侧的启动链路是通的:FSBL能正常加载、U-Boot能起来、内核最后也顺利跑到了命令行(这是Linux侧,裸机侧我们另有一条从BSP生成的ELF直接上板的路径)。PL侧的bitstream加载没问题,PS透过AXI总线对PL侧BRAM/寄存器的读写验证也通过了,UART的收发、GPIO的点灯、裸机中断的进入退出逻辑都单独验证过。

为了不让后面的描述悬空,我把第15期结束时系统里确认过的东西列一个表。

功能模块状态验证方式
PS启动链路正常FSBL + U-Boot + 内核引导
PL bitstream加载正常通过SD卡/FSBL加载
AXI总线读写正常AXI GP口读写PL侧BRAM寄存器
UART串口通信正常裸机printf收发测试
GPIO控制正常按键输入、LED输出测试
裸机中断正常定时器中断、PL中断均可触发

当时所有验证都是在一个main函数里通过主循环完成的。UART要轮询发送、GPIO要轮询读取、PL侧状态要轮询刷新,唯一的异步机制就是中断服务函数。

1.2 裸机主循环模式的"天花板"在哪里

很多人做Zynq开发会喜欢裸机主循环,因为简单直观,逻辑是一行行顺序下来的。但等你手上的模块超过两三个,这个模式就开始变味了。打个比方,主循环就像一个只有一个收银台的小超市,所有顾客(事件)都必须在柜台前排队,谁先到谁先结账,哪怕是VIP客户(高优先级事件)也得等着。

具体到我的平台,问题有几个明显暴露点。

第一是响应实时性不可控。PL侧如果有一个高频数据流,比如每毫秒产生一个中断,而主循环里某个外设查询函数执行时间稍长,下一次中断的数据就可能被覆盖。裸机下想保证实时性,唯一靠谱的办法就是把关键代码全塞进中断里,但中断里塞太多逻辑,系统又会被拖死。

第二是模块间耦合严重。每个新功能都要改主循环的调用顺序,要么加标志位,要么加状态机。多个功能模块共享同一个循环变量、共享同一个全局状态,排查问题的时候谁都说不清是哪个模块改坏了什么。

第三是没有统一的休眠机制。CPU永远满转,功耗和发热在嵌入式设备上是硬伤。想让CPU在空闲时停下来等事件,裸机下要自己实现低功耗逻辑,又回到容易出错的老路。

1.3 第16步为什么是调度器:bring-up顺序里的合理化解释

我理解的工程师化bring-up,不是简单地把外设一个个点亮,而是让这些外设最终能在一个"有条理"的系统里协同工作。第1期到第15期解决的核心问题是:硬件能不能用、驱动怎么调通。到了第16期,要解决的是:硬件都通了,代码结构怎么组织,才能支撑后面继续叠加功能。

调度器就是这个阶段最应景的工具。它正好解掉了裸机主循环的三个死结:用优先级抢占保证高价值事件能被及时处理;用任务边界把模块彻底隔离;用阻塞机制让CPU在无事件时真正空闲下来。后面每一期再加新外设,比如网口、SD卡、图像采集,就不再是往主循环里插一段代码,而是新建一个任务、配好优先级和信号量,本质改变了工程的组织方式。

2. 调度器选型:为什么是FreeRTOS,而不是Linux或继续while(1)

2.1 一个不科学的直觉:Zynq不是能跑Linux吗,为什么不用?

聊到调度器,很多人第一个念头是:Zynq-7000这边不是有完整的处理器系统吗,直接把Linux跑起来不就完事了?Linux内核本身就是个超强的调度器,什么任务调度、内存管理、网络协议栈全都有了。我确实在平台上验证过Linux,但把系统换成Linux跑业务,等于把bring-up的难度整体抬了一个数量级。

Linux的启动链路更长,U-Boot配置、设备树、内核配置、根文件系统,每个环节都是新的不确定性来源。而且PL侧的IP核要接入Linux,你得按照Linux的设备驱动模型写驱动,要么用UIO,要么走DMA框架,这和在裸机上直接操作寄存器完全是两回事。对正在做PS/PL协同验证的团队来说,Linux的驱动框架会把PL侧的调试问题掩盖在一堆"不明原因"的内核报错里,排查起来非常痛苦。Linux适合做产品化的最终形态,不适合放在bring-up阶段当调试杠杆。

如果嫌Linux太重,又不想回到裸机轮询的死胡同,FreeRTOS或者类似的轻量RTOS就是刚好卡在中间的那个选择。

2.2 FreeRTOS在Cortex-A9上的表现与生态

我选FreeRTOS,说到底四个字:轻、稳、熟、多。

"轻"不只是内核代码量少,关键是心智负担小。整个内核就是task、queue、semaphore、timer这几样东西,搞过单片机的人大概一两天就能上手。对Cortex-A9这种带MMU、带GIC的复杂处理器来说,FreeRTOS的Cortex-A9移植层是官方维护的,社区里用Zynq跑FreeRTOS的案例非常多,遇到问题能搜到的参考资料比任何其他RTOS都多。

"稳"体现在板级验证上。Zynq官方SDK本身就提供FreeRTOS的BSP模板,说明Xilinx自己就用它做过Zynq平台验证。GIC中断、物理定时器、上下文切换这些A9特有的东西,官方移植代码已经处理好了,不再需要我从零写核心汇编。

"熟"是对团队说的,协作的同事大都学过FreeRTOS,后续维护成本低。"多"则是说生态,任务通知、流缓冲、软件定时器,需要的组件基本都有。

其他轻量RTOS我也评估过。RT-Thread国产化程度高、组件丰富,但Zynq平台的支持相对没有FreeRTOS那么"原装";uC/OS经过商业授权风波后,许可条款让一部分商业项目有顾虑。选型这种事没有绝对好坏,只有跟自己的平台和团队匹配不匹配,对我来说FreeRTOS是这个阶段最省事的选项。

2.3 单核任务调度与双核负载均衡:本期先不碰SMP

这里要给一个特别重要的提醒:Zynq-7000是双核Cortex-A9,但FreeRTOS加入系统绝不等于从此就双核并行跑任务了。这期我选择的是单核模式,把所有任务调度都在CPU0上跑,CPU1先挂起。原因很实在:双核SMP模式涉及CPU间中断、任务迁移、锁的竞争粒度、缓存一致性,任何一个问题在bring-up阶段都是巨大的调试黑洞。先把单核调度跑稳,确保任务切换、中断嵌套、信号量同步全部没问题,后面再开SMP就是水到渠成的事。

至于"负载调度器"这种话题——双核之间怎么把任务均匀分配、怎么让高负载核的实时任务迁移到空闲核,那是在SMP打开之后才真正变成焦点的。单核模式下所有任务都挤在一个核上,讨论负载均衡是空中楼阁。所以这期先把"调度"本身做好,负载均衡留给后面专门的一期来讲。

3. 工程移植记录:从裸机SDK工程到FreeRTOS任务工程

3.1 代码组织:把FreeRTOS源文件和BSP分隔开

移植的第一步不是写代码,而是先把工程目录结构理清楚。我以前见过不少人的做法是把FreeRTOS源码直接拷进BSP目录里平铺,看起来省事,实际上把第三方代码和项目代码混在一起,以后升级内核版本、同步修改都痛苦。我最终采用的结构是这样的:

app/ main.c bsp/ platform.c platform.h uart.c pl_driver.c pl_driver.h task/ app_task.c app_task.h freertos/ Source/ include/ portable/ GCC/ARM_CA9/ list.c queue.c tasks.c timers.c event_groups.c

FreeRTOS源码保持原样,一份干净、只读的第三方代码放在freertos目录下,编译系统把它作为静态库或者直接编进来都行。自己的初始化代码和任务逻辑放app目录,靠include路径来引用FreeRTOS的头文件。这样做最大的好处是:FreeRTOS源码可以随时换版本,我的应用层代码完全不依赖它是怎么组织的。

3.2 FreeRTOSConfig.h里最关键的四组配置

FreeRTOS没有图形化配置界面,所有裁剪和参数都在FreeRTOSConfig.h里完成。这个文件我照搬了官方Cortex-A9 port的模板,然后根据自己的工程调整了关键几项。这里必须说明,不同版本的port对配置项的依赖不完全一样,以下内容是基于我当前工程的实际配置,用的是官方ARM_CA9移植目录下那套实现。

#define configUSE_PREEMPTION 1 #define configMAX_PRIORITIES 8 #define configTICK_RATE_HZ ( 1000 ) #define configTOTAL_HEAP_SIZE ( 128 * 1024 ) #define configSUPPORT_DYNAMIC_ALLOCATION 1 #define configUSE_MUTEXES 1 #define configUSE_COUNTING_SEMAPHORES 1 #define configUSE_TIMERS 0 #define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 #define configMINIMAL_STACK_SIZE ( 128 ) #define configUSE_NEWLIB_REENTRANT 1

几个要展开讲的重点:

configTICK_RATE_HZ是调度器的"心跳"频率。我设定为1000Hz,也就是每个tick间隔1ms。这个值决定了系统时间的最小粒度,也决定了定时任务(延时、超时)的精度。对PL侧高频中断的场景,1000Hz足够支撑绝大多数控制周期需求。如果后续要做音频、高精度PWM这类周期更短的任务,可以提到2000甚至4000Hz,代价是CPU每秒钟要处理更多次tick中断,上下文切换开销也随之上升。

configTOTAL_HEAP_SIZE决定任务栈的总容量。这是新手最容易忽略的一个宏。FreeRTOS里面xTaskCreate动态创建任务时,任务的栈是从堆里分配的,而这个堆的大小就是由configTOTAL_HEAP_SIZE定义的。我给了128KB。从后面实际运行情况看,4到5个任务、每个任务栈4KB到8KB,堆空间还有富余,但如果后面要加TCP/IP协议栈、文件系统组件,这个值得再往上加。

configUSE_PORT_OPTIMISED_TASK_SELECTION必须关心。这个是Cortex-A9 port特有的优化项,打开后任务选择会使用硬件指令来加速查找最高优先级任务,比纯软件遍历要快。对实时性敏感的场景建议打开,但前提是优先级数量不能超过32个(我的configMAX_PRIORITIES是8,满足要求)。

configUSE_NEWLIB_REENTRANT要注意。如果你在Zynq上用了newlib的printf、malloc这类C库函数,这个配置必须为1。它的作用是让每个任务有独立的errno和stdio状态,避免多个任务同时打印时库内部状态被互相覆盖。这个配置直接和后面要讲的串口打印踩坑有关。

3.3 链接脚本与栈的搬运:内存分配不能想当然

裸机工程里,Cortex-A9的栈顶、堆位置通常是在链接脚本里写死的。加入FreeRTOS后,任务栈都要从堆里动态分配,所以"堆"的设置变得格外重要。我遇到过的情况是:链接脚本里堆的起始地址和大小设置不合理,任务一创建就报内存申请失败,但看代码又明明调用了xTaskCreate,问题极其隐蔽。

另外一个印象深刻的点是FPU。Cortex-A9带硬件浮点单元,Zynq的CPU默认上电后FPU是关闭的,需要在启动代码里把CP10、CP11的访问权限打开,并且把FPEXC的EN位置1。如果忽略这一步,任务里只要有一句浮点运算,CPU立刻进入Undefined Instruction异常,表现就是死机或者反复复位。我的解决办法是在启动汇编里加入FPU使能代码,同时在链接脚本里把栈(裸机模式的启动栈)放到OCM里,因为早期代码在DDR初始化完成前就要使用栈。

// 启动代码中的FPU使能片段示意 // 将协处理器访问权限打开,并允许用户态访问 __asm volatile( "mrc p15, 0, r0, c1, c0, 2\n" "orr r0, r0, #(0xf << 20)\n" "mcr p15, 0, r0, c1, c0, 2\n" "isb\n" "mov r0, #0x40000000\n" "fmxr fpexc, r0\n" );

实际验证中发现,FPU使能这类底层代码写错位置或者漏掉,并不会立刻在启动阶段报错,而是要等到第一个涉及浮点的任务运行才崩,排查起来特别容易把怀疑的焦点引向任务优先级或调度逻辑,走了不少弯路。

4. 裸机中断与FreeRTOS中断的"翻译":最容易翻车的环节

4.1 从XScuGic的Handler到FreeRTOS的ISR

加入调度器之后,整个中断体系的处理姿势跟裸机时代完全不同。裸机模式下我简单地在XScuGic_Config里注册好中断回调函数,中断一来,CPU就跳进回调,跑完再回去。但FreeRTOS的Cortex-A9移植层接管了GIC的一部分核心逻辑,中断进入时会先走port层的中断处理代码,然后才到我的回调。这个变化带来的直接要求是:我的ISR里不能随便调用任务级的API,必须使用后缀带FromISR的版本。

裸机时代的典型写法是:

// 裸机风格:中断服务函数里直接处理业务 void PL_IRQHandler(void *CallbackRef) { u32 data = Xil_In32(PL_FIFO_BASE); process_data(data); XScuGic_DeviceDriverUpdate(&Gic); }

切换FreeRTOS之后,我最简单的PL通知中断变成了这样:

// FreeRTOS风格:中断里只做事件通知,具体处理挪到任务 void PL_IRQHandler(void *CallbackRef) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; vTaskNotifyGiveFromISR(pl_rx_task_handle, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }

两段代码最大的区别是:处理数据的动作从ISR里消失了,变成了一个信号通知。中断服务函数要做的事情只有三件——快速读取必要的硬件状态、通过FromISR接口通知对应的任务、必要时让出CPU。数据搬运、运算、格式化输出这些耗时操作全部挪到任务上下文里做。这样做的好处是中断占用的时间极短,系统不会因为高频中断而假死。

4.2 中断优先级与调度器tick的关系

FreeRTOS的调度发生在tick中断和系统调用里,所以硬件中断的优先级设置对调度行为有直接影响。这里有个经验法则:不能让所有硬件中断的优先级都高于tick中断,否则tick永远被硬件中断打断,调度器无法按预期周期运行;也不能让所有硬件中断都低于tick,否则关键外设的实时响应会被tick抢占。

我在Zynq上的实际配置是:PL侧高速数据中断优先级最高,因为数据流中断丢失代价最大;UART中断和软件定时器中断设为中等;tick中断设为中等偏下。这样既保证了高速外设能抢占调度器获得快速响应,又不至于让tick饿死。

GIC中断优先级的数值越小优先级越高,Cortex-A9对每核有多个优先级级别。具体到每个设备的中断号,直接照着Zynq-7000 TRM里的中断表查就行。PL侧的中断输入我用了SPI 61这一组,UART1的中断号在SPI组里是60,这些数字在配置的时候一定要和原理图、地址分配表对清楚,编错了最直接的现象就是一个设备的中断跑到另一个设备的中断服务函数里。

另外要注意,FreeRTOS在Cortex-A9上对中断的处理,依赖GIC的优先级比较和抢占。你需要在启动GIC时就把优先级抢占功能打开,否则多个中断同时到达时行为就不好控制,排查起来会非常困惑。

4.3 任务同步的原语选择:信号量、事件组还是任务通知

任务之间怎么打交道,是第16期我花时间最多的地方。FreeRTOS提供了一大堆同步原语,最常用的是二值信号量、互斥量、队列、事件组、任务通知。裸机时代我习惯用全局变量加标志位,在任务环境下这种习惯要改掉——因为改共享标志位的时候不是原子的,两个任务可能同时读写同一个变量,产生竞态。

我总结了一套自己的选型经验:

同步场景推荐原语理由
中断通知任务"有数据了"任务通知速度快,CPU开销最小
多任务抢占共享慢速外设互斥量有优先级继承,防优先级反转
生产者消费者传数据队列自带缓冲,天然实现解耦
等待一组事件中的任意一个事件组位掩码操作,逻辑清晰

任务通知xTaskNotifyGive是这里面开销最低的,一次典型的通知大约只花几百纳秒到几微秒(取决于是否有任务需要唤醒),非常适合ISR到任务这种高频场景。队列看起来方便,但数据拷贝有开销,如果传的是大块数据,我更倾向于把数据放在全局缓冲里,队列只传指针或者帧序号。

在PL侧FIFO接收任务里,我用的是任务通知+指针的方式:ISR里xTaskNotifyGive通知,任务收到通知后从全局双缓冲里取数据,然后立刻回下一次接收。实测在1MHz左右的PL数据流频率下CPU占用率可以接受,数据不丢。

5. 任务化改造后的实测:三个必踩的坑与排查链路

5.1 坑一:printf在多个任务里打架,系统串口卡死

现象是最典型不过了:vTaskStartScheduler之后,第一个任务打印正常,第二个任务一开打印,串口输出就开始乱,紧接着系统卡住不动了。我用调试器挂上去,发现任务卡在UART的发送函数里一直等FIFO空。

一开始我怀疑是UART驱动本身的问题,裸机下它明明没问题。后来查FreeRTOS的一组文档才意识到,多个任务同时调用printf,而printf内部通过newlib的write接口写UART寄存器,这里有两个并发隐患:一是newlib的stdio内部有缓冲区,多任务同时访问缓冲区会互相踩;二是UART驱动发送过程中包含了"写寄存器、等待状态位"这样的非原子操作,两个任务交替执行,状态机就乱了。

解决办法分两步。第一步开启configUSE_NEWLIB_REENTRANT,保证每个任务有独立的stdio锁;第二步给UART发送函数外面的printf包一把互斥量,让整段"格式化+发送"的过程变成临界区。

void app_printf(const char *fmt, ...) { va_list args; xSemaphoreTake(uart_mutex, portMAX_DELAY); va_start(args, fmt); vprintf(fmt, args); va_end(args); xSemaphoreGive(uart_mutex); }

需要特别注意的是,这个互斥量不能直接在中断里配合xSemaphoreTake使用。中断里的调试打印要走专门的ISR版本,或者在调试中断时干脆禁掉任务级的打印。我把这个坑写出来是因为它极具代表性:很多"加入RTOS就死机"的问题,根子并不在RTOS的调度本身,而在你把裸机时代不设防的函数直接带进了RTOS环境。

5.2 坑二:PL高速中断把系统拖到假死

PL侧的设计是每毫秒产生一次中断,用于同步数据采集。改造前我在ISR里做的事情比较多:读FIFO、解析包头、更新标志位、清零中断。在裸机时代这套逻辑能跑,但切换FreeRTOS之后,系统表现为"看起来活着,但任务调度极其迟钝",LED闪烁周期变得不规则,串口打印速度严重下降。

我第一反应是任务优先级没配好,各种调优优先级之后问题依旧。后来用示波器量了PL侧中断引脚和CPU的响应时间,发现ISR里耗时接近800微秒,而tick周期才1毫秒——等于CPU有将近八成的时间都泡在ISR里,调度器根本没机会运行。

根因清楚了:ISR里做了大量非必须的软件处理。我按照前面说的原则把ISR瘦身,只保留读硬件状态和发任务通知两个动作,数据解析全部移到高优先级任务里。改造之后同样1ms一次的中断,ISR耗时压在10微秒以内,系统调度恢复正常。这算是RTOS和裸机最大的习惯差异:裸机下ISR里多干点事只要不出错就行,RTOS下ISR的"短"是系统一切正常的前提。

5.3 坑三:任务栈溢出引发的"随机"死机

第三个坑是长期运行才暴露的。某次跑压力测试,系统跑了一个多小时之后突然死掉,复位重来又可能两三个小时才出问题。这种随机死机特别折磨人,因为你没办法在几分钟内复现它。

排查的时候我加了两层保险。第一层是打开configCHECK_FOR_STACK_OVERFLOW,让FreeRTOS在上下文切换时检查栈边界,如果有溢出会调用vApplicationStackOverflowHook,我在这个钩子里打印出错的任务名。第二层是在每个任务里定期调用uxTaskGetStackHighWaterMark,把剩余栈空间打印出来观察。这样跑了一个晚上,抓到是某个任务栈不足:局部数组加函数嵌套调用,把2KB的栈吃穿,溢出的数据正好踩坏了相邻任务的TCB,导致任务链表被破坏,随机死机。

处理办法是给这个任务单独加栈到4KB,并且给所有任务定了个规矩:任务内大块数据用静态全局或者堆上分配,绝不在函数里声明大数组。栈溢出这个问题,很多人的第一反应是"RTOS不靠谱",其实是因为任务栈和裸机栈的设计思路完全不同——裸机只有一个大栈,RTOS里每个任务都是独立的受保护栈,谁在栈上过于"豪放",谁就是悬在系统头上的定时炸弹。

6. 第16期的可交付成果与下一期方向

6.1 当前任务结构列表:任务怎么划分、优先级怎么排

第16期结束后,我手上的平台已经从"裸机单循环"变成了"RTOS任务化"结构。当前跑着的任务不多,但每一个都代表一类典型场景:

任务名优先级周期/触发方式栈大小主要职责
pl_rx_task5事件通知触发4KB接收PL侧数据,解析帧
ctrl_task410ms周期4KB控制算法,写PL控制寄存器
uart_tx_task3队列触发2KB从队列取数据发送
stat_task21s周期2KB打印各任务运行状态、栈水位
idle_task0空闲1KBCPU空闲处理

这个优先级排列有几个讲究。PL数据接收最急,放最高优先级;控制算法周期性很强,但周期到了本身就有硬死线,放第二;串口发送可以缓冲,放第三。关键的原则是:实时性强、丢数据代价大的任务优先级高,能缓则缓的任务优先级低。优先级配错,轻则CPU空转,重则实时任务被非实时任务饿死。

6.2 怎么验证调度器真的在工作:观测手段

任务化改造完成后,怎么证明调度器真的达到了预期?不能拍脑袋说"能跑"。我用了三种观测手段交叉验证。

第一种是优先级抢占测试。建一个优先级很低、死循环里打印"LOW"的任务,再建一个高优先级周期任务,打印自己的名字。如果调度正常,低优先级任务只有高优先级任务休眠时才能打印,两者打印模式呈现明显的分时交替。

第二种是栈水位监测。在stat_task里周期读取各任务的uxTaskGetStackHighWaterMark,打印剩余栈。通过这个值可以定量判断每个任务的栈余量,哪些任务接近危险区一目了然。

第三种是系统心跳观测。用一个任务控制LED以精确的1Hz闪烁,正常运行时LED闪烁均匀。一旦CPU负载过高或者中断故障导致调度器饥饿,LED闪烁会明显变得不规则。这个办法简单粗暴,但确实是现场排查时最直观的传感器。

6.3 双核与更高阶调度:负载均衡怎么升级

第16期结束不是终点。当前FreeRTOS只在CPU0上运行,CPU1还是完全空闲的。后续如果要实现双核负载真正均衡,有两个方向可以走。一是切换到SMP模式,让FreeRTOS把任务自动分配到两个核上,系统自带负载均衡能力;二是保持AMP模式,CPU0跑业务逻辑,CPU1跑专门的硬实时任务,两个核之间通过共享内存和核间中断通信。

我的初步计划是先把SMP模式跑通,测一下任务迁移、锁竞争的表现。对Zynq这种双核A9来说,SMP的无线扩展性肯定不如高频单核方案,但两个核同时跑确实能缓解单核压力。负载调度器这个方向,等真正调通SMP之后再细说,到时候四个核的任务怎么放、中断怎么分离、内存带宽怎么分配,都是一整期的大话题。

回到这一期本身,我做完整套改造后,最大的体会就是:调度器不是一个"加进去就完事"的组件,它像一层操作系统,改变了所有代码的运行假设。裸机时代不被约束的全局变量、ISR里的大块运算、随意的栈分配,到了RTOS里都会变成腐化系统的隐患。Zynq-7000这种 PS + PL 的平台,PL侧还有着硬实时数据流,调度器更像是一个分配器:把CPU时间、外设访问权限、数据流处理时机,公平又高效地分散到每个需要它的模块。第16期把这层地基打稳了,后面再加任何东西都会顺畅得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询