1、uC/OS-II 是源码开放、可固化、可移植、可裁剪、可剥夺的 实时多任务 OS内核,适用于任务多、对实时性要求较高的场合。uC/OS-II 适合小型系统,具有执行效率高、占用空间小、实时性优良和可扩展性等特点,最小内核可编译至 2K。uC/OS-II 内核提供任务调度与管理、时间管理、任务间同步与通信、内存管理和中断服务等功能。所谓 RTOS 移植 ,就是使一个实时内核能在某个微处理器或微控制器上运行。大部分的 uC/OS-II 代码试用 C 写的,但仍需要用 C 和 ASM 写一些与处理器相关的代码,这是因为 uC/OS-II 在读写处理器寄存器时只能通过 ASM 实现。要是 uC/OS-
2、II 正常运行,处理器必须满足一定的条件: 处理器的 C 编译器能产生可重入代码; 用 C 语言就可以打开和关闭中断; 处理器支持中断,并能产生定时中断; 处理器支持能够容纳一定量数据的硬件堆栈; 处理器有将 SP 和其他 CPU reg 读出和存储到堆栈或内存中的指令;uC/OS-II 移植工作 主要包括以下三个方面的内容:(1) 修改与处理器核编译器相关的代码:主要在 includes.h 中,修改数据类型定义说明,OS_ENTER_CRITICAL()、OS_EXIT_CRITICAL()和堆栈增长方向定义 OS_STK_GROWTH。(2) 用 C 语言编写 10 个移植相关的函数:主
3、要在 OS_CPU_C.C 中,包括堆栈初始化 OSTaskStkInit()和各种回调函数。(3) 编写 4 个汇编语言函数:主要在 OS_CPU_A.ASM 中,包括: _OSTickISR /时钟中断处理函数 _OSIntCtxSW /从 ISR 中调用的任务切换函数 _OSCtxSW /从任务中调用的任务切换函数 _OSStartHighRdy /启动最高优先级的任务uC/OS-II 移植的 关键问题:(1) 临界区访问:uC/OS-II 需要先禁止中断再访问代码临界段,并且在访问完毕后重新允许中断,这就使得 uC/OS-II 能够保护临界段代码免受多任务或 ISR 的破坏。 uC/O
4、S-II 定义了 OS_ENTER_CRITICAL()(禁止中断)和 OS_EXIT_CRITICAL()(允许中断) ,有 3 种实现方法。(2) 系统引导:uC/OS-II 引导分为两部分:一是处理器相关的引导,包括初始化处理器状态、数据段、SP 等,一是 OS 相关的引导,包括系统各个数据结构的初始化、初始任务的创建等,最终执行到_OSStartHighRdy开启多任务执行。与 uC/OS-II 引导相关的几个移植函数是OSStartHighRdy、OSTaskStkInit 和 OSTickISR,其中 OSTaskStkInit 负责初始化任务堆栈,初始化的堆栈数据会在任务第一次被
5、调用执行时装入相应的寄存器。在 OSStartHighRdy 前执行的所有代码都是为多任务启动做准备,由OSStartHighRdy 开始才真正的启动多任务环境,OSStartHighRdy 执行后不会返回,而是直接切换到最高优先级任务运行。(3) 堆栈切换和管理:堆栈切换伴随任务切换而发生,uC/OS-II 中主要有三个地方发生堆栈切换: 在 OSStartHighRdy 启动多任务时,将任务堆栈初始内容弹出堆栈; 在 OSIntCtxSW 中切换,在中断返回时如果最高优先级的任务发生了改变,就需要调用这个函数切换任务; 在 OSCtxSW 中切换,这是主动调用 OSSched 等函数触发的
6、任务切换,在任务中以软中断方式触发该调用;影响堆栈切换的另一个因素是中断嵌套,uC/OS-II 中的中断嵌套通过全局变量 OSIntNesting 控制。当进入中断时,只有最外层的中断(也就是第一次进入的中断)需要将当前的 SP 保存在 TCB 中;相应的,只有最外层的中断返回时才需要进行可能的堆栈切换。uC/OS-II 多 NiosII 软核扩展-uC/OS-MC在多 NiosII 软核上扩展的 uC/OS-II 称为 uC/OS-MC,uC/OS-MC 期望能够实现其对于多核处理器的支持,实现传统的 CMP 架构,即运行一个 RTOS 动态的调度任务到各个 CPU 核上运行,保证任务的优先
7、级和 CPU 核的负载平衡。对 uC/OS-MC,需要保证有最高优先级的 N 个任务始终运行,N 代表系统中 NiosII 核的数量,这是对任务优先级的保证;还要 保证处理器的负载均衡,任意一个处理器核如果运行空转任务,说明系统中不存在就绪运行的任务。uC/OS-MC 移植设计难点一是多核处理器的引导,怎样将多核处理器启动,完成各个处理器核的初始化,同时还要保证各个处理器核间的同步,以使其能够在几乎同一时间进入任务调度;二是多核处理器的互斥和同步,仅仅禁止某个核的中断处理不能保证临界区的互斥访问,必须采用新的方式实现多核处理器对于临界区的互斥核同步访问;三是任务调度,必须在多核处理器上设计出一
8、个性能良好的调度算法,一方面保证最高优先级任务的运行,另一方面要是各个处理器核上的任务切换次数尽可能的少;四是时钟中断处理,多核处理器上的每个处理器核都需要一个独立的时钟中断来提供“心跳” 。(1) 多核处理器的引导在多核处理器的引导过程中,处理器内核间是不平等的,有主次之分。系统启动后,PM(Primary CPU)首先初始化自身,同时完成一些全局化的初始化过程,如数据段清零、MMU 初始化等,然后跳转到 C 代码的函数中执行,此时才由 PM 逐个启动 PE(Processor Element) ,各个 PE 在初始化自身后需要和 PM 同步,等待 PM 发出启动信号量后统一进入任务调度。对
9、于 PM 和各个 PE,自身的初始化是每个处理器核都必须执行的,而全局和一些系统的初始化工作则只由 PM 在启动时执行一次。(2) 多核上的任务调度每个任务都能在任意的 CPU 上运行,这就需要在任务堆栈中保存足够的信息以使它能够在任意一个 CPU 上恢复运行。空转任务(IDLE TASK)作为一个特殊的任务不能够被调度到别的处理器上运行,实际上每个处理器上都运行了一个相同的空转任务。RTOS 在多核处理器上的任务调度实际上是一个处理器核和任务双向选择的过程:一方面要保证最高优先级任务的运行,另一方面要保证全局的任务切换次数最少,即原来运行在该处理器核上的任务尽可能不发生切换,这是任务调度算法
10、需要考虑的两个关键问题。uC/OS-MC 按优先级排序,最高优先级的 N 个任务都有可能同时运行。 任务切换时机在 uC/OS-MC 中,与任务调度相关的 4 个变量OSPrioCur、OSPrioHighRdy、OSTCBCur、OSTCBHighRdy 都扩展为长度为 N 的数组。注意:“最高优先级任务”的含义发生了变化 ,在单一处理器核上运行的任务也许不是全局的最高优先级,但在所有处理器核上运行的 N 个任务一定是所有任务中具有最高优先级的 N 个任务。每个处理器核在两个地方进行任务切换:一是时钟中断返回,通过调用OSIntExit 检查是否需要进行任务切换;一是处理器间中断,这是任务主
11、动触发的任务调度。在多核处理器环境下,某一个任务上运行的任务调度的效果可能是全局性的,也就是说 PE1 上运行的任务调度可能会影响到PE0、 PE2、 PE3 上运行的任务,这就需要通过处理器间中断来处理。 调度算法多核处理器上的任务调度算法有两个目标:一是保证最高优先级的 N个任务运行,二是保证全局的任务切换次数最少,即使原来运行在该处理器上的任务尽可能的不发生切换。uC/OS-MC 任务调度分两个步骤 :首先从当前就绪的任务队列中选择出优先级排在前 N 位的任务,然后把这 N 个任务调度到 N 个处理器核上。任务调度流程图如下图所示:(3) 多核处理器间的通信uC/OS-MC 主要使用三种
12、核间通信机制 : 多核处理器间的中断; 多核处理器间的同步; 多核处理器间的互斥,主要通过硬件自旋锁实现;在多核处理器中,禁止本处理器核的中断只能保证运行在该处理器核上的任务间的互斥,而运行在别的处理器核上的任务可能同时进入临界区访问。这样,不仅需要禁止本处理器核上的中断,还需要处理器核间的互斥机制来保证对于临界区的访问。uC/OS-MC 中,封装了对于多核处理器间的 自旋锁 操作:wait_cmp_lock()和 release_cmp_lock()。在访问多核处理器间需要互斥访问的数据之前,需要先调用 wait_cmp_lock()来获得处理器核间的互斥锁,访问完毕后再通过 release
13、_cmp_lock()释放互斥锁。注意:需要同时调用 OS_ENTER_CRITICAL()和 wait_cmp_lock()时,要先调用 OS_ENTER_CRITICAL()禁止中断,再调用 wait_cmp_lock()获得处理器间的互斥锁。(4) 时钟中断处理对 uC/OS-MC,外部中断的分配和处理大部分由硬件支持,而作为RTOS“心跳 ”的时钟中断 则由每个处理器核内部提供。系统初始化时,每个处理器核都需要初始化其内部的定时器,配置成频率相同的时钟中断;另外,不同的处理器核必须使用不同的时钟中断源。uC/OS-II 中,时钟中断作为外部中断由外部时钟提供;而 uC/OS-MC中,每个处理器核上都需要一个独立的时钟中断来驱动该处理器核上的任务切换,改由每个处理器核上的内部时钟提供时钟中断。在 SOPC Builder 中,可为每个 NiosII 软核配置一个 Internal Timer。