uC/OS-II

1. 优先级反转(Priority Inversion)

优先级反转,是指某同步资源被较低优先级的进程/线程所拥有,较高优先级的进程/线程竞争该同步资源未获得该资源,而使得较高优先级进程/线程反而推迟被调度执行的现象。

假定一个进程中有三个线程Thread1、Thread2和Thread3,它们的优先级顺序是Priority(Thread1) > Priority(Thread2) > Priority(Thread3)。考虑图一的执行情况。


1,T0时刻,只有低优先级任务Thread3处于可运行状态,运行过程中,Thread3拥有了一个互斥资源SYNCH1;
2,T1时刻,中优先级任务Thread2就绪进入可运行状态,由于优先级高于正在运行的Thread3,Thread3被抢占(未释放互斥资源SYNCH1),Thread2被调度执行;
3,T2时刻,高优先级任务Thread1就绪抢占Thread2;
4,T3时刻,Thread1请求互斥资源SYNCH1,但信号量已被占有,所以Thread1阻塞等待该资源,而此时处于可运行状态的线程Thread2和Thread3中,Thread2的优先级高于Thread3的优先级,Thread2被调度执行。

上述现象中,优先级最高的Thread1既要等优先级低的Thread2运行完,还要等优先级更低的Thread3运行完之后才能被调度,如果Thread2和Thread3执行的很费时的操作,显然Thread1的被调度时机就不能保证,整个实时调度的性能就很差了。

2. 优先级继承(Priority Inheritance)

为了解决上述由于优先级反转引起的问题,uC/OS引入了优先级继承的解决方法。优先级继承也就是,高优先级进程TH在等待低优先级的线程TL继承占用的竞争资源时,为了使TH能够尽快获得调度运行,由操作系统把TL的优先级提高到一个很高的优先级,从而让TL优先参与调度,尽快让TL执行并释放调TH欲获得的竞争资源,然后TL的优先级调整到继承前的水平,此时TH可获得竞争资源而继续执行。

有了优先级继承之后的上述现象的执行情况如图二所示。


与图一比较,图二中,到了T3时刻,Thread1需要Thread3占用的互斥资源SYNCH1,操作系统检测到这种情况后,就把Thread3的优先级提高到一个很高的的优先级。此时处于可运行状态的线程Thread2和Thread3中,Thread3的优先级大于Thread2的优先级,Thread3被调度执行。

Thread3执行到T4时刻,释放了互斥资源SYNCH1,操作系统恢复了Thread3的优先级,Thread1获得了互斥资源SYNCH1,重新进入可执行队列。处于可运行状态的线程Thread1和Thread2中,Thread1的优先级大于Thread2的优先级,所以Thread1被调度执行。

上述机制,使优先级最高的Thread1获得执行的时机提前。

3. uC/OS-II中互斥信号量对优先级继承机制的实现

互斥信号量是一种特殊信号量,取值只能是0或1,实现对共享资源的独占式处理。为了保证系统的实时性,拒绝优先级反转,对互斥信号量的管理采用了优先级继承机制。解决优先级反转是互斥信号量管理区别于普通信号量管理的最本质的区别。

uC/OS-II中的互斥型信号量由三个部分组成:

◎ 一个标志,指示mutex是否可以使用(0或1);

◎ 一个优先级,准备一旦高优先级的任务需要这个mutex,赋予给占有mutex的任务;

◎ 一个等待该mutex的任务列表.

假设现在有三个任务分别是Task1,Task2,Task3,优先级从大到小。程序在运行过程中,Task1和Task2处于挂起(pend)的状态,等待某个事件的发生。这样优先级最低的Task3首先申请到了mutex并开始同共享资源打交道,过了一会儿,Task1等待的事件出现了,那么此时Task1就需要使用共享资源了,于是申请mutex(OSMutexPend)。在这种情况下,OSMutexPend注意到高优先级的任务要使用这个共享资源,于是将Task3的优先级升到比Task1更高的级别,并强制任务调度回到Task3。而Task1只好继续挂起,等待共享资源被释放。同时Task3继续运行,直到Task3调用OSMutexPost(),释放Mutex.在调用OSMutexPost()时,OSMutexPost()会将Task3的优先级恢复到原来的水平,同时还会注意到有个高优先级的任务Task1需要这个Mutex,于是将这个Mutex交给Task1,并做任务切换。

uC/OS-II单独为互斥信号量管理编写了C语言文件os_mutex.c,互斥信号管理的核心函数如下:

1,建立互斥型信号量:

OS_EVENT *OSMutexCreate(INT8U prio,INT8U *err)

返回值赋给信号量

参数prio:较高的空闲优先级,用于任务提权

参数err:存放出错信息

2,删除互斥型信号量:

OS_EVENT *OSMutexDel(OS_EVENT *pevent,INT8U opt,INT8U *err)

返回值为NULL,赋给信号量

参数pevent:要删除的信号量

参数opt:删除的选项

参数err:存放出错信息

3,挂起等待互斥型信号量:

void OSMutexPend(OS_EVENT *pevent,INT16U timeout,INT8U *err)

参数pevent:等待的信号量

参数timeout:等待超时的节拍数,0为永久等待

参数err:存放出错信息

4,释放互斥型信号量:

INT8U OSMutexPost(OS_EVENT *pevent)

返回释放状况

参数:要释放的信号量

5,无阻塞请求互斥型信号量:

INT8U OSMutexAccept(OS_EVENT *pevent,INT8U *err)

调用此函数会立即返回,不会等待,返回值0表示信号量不可用,返回1表示得到互斥型信号量

参数pevent:要请求的互斥型信号量

参数err:存放出错信息

6,查询互斥型信号量当前状态:

INT8U OSMutexQuery(OS_EVENT *pevent,OS_MUTEX_DATA *pdata)

返回查询出错代码

参数pevent:要查询的信号量

参数pdata:存放查询到的状态信息

本文转自:博客园 - yucen
https://www.cnblogs.com/yucen/p/9343581.html

围观 95

uC/OS- II是最早进入国内的一款开源RTOS,因为代码开源,又有配套的书籍,加上不大的代码量,在嵌入式群体中最为流行。在写“实用单片机系统”第一版之后,就接触了uC/OS-II,虽然大致的明白其工作原理,但一直似懂非懂,尤其有太多的宏定义,严重的干扰了源码的阅读,加上RTOS带来太多的概念,而这些概念都没有实际用过,不知道如何应用,并且听说有很多陷阱,所以心里有些空,把握不住风险,一直都回避RTOS。

高频机开发的后期,菜单界面编程的复杂性严重的干扰了业务逻辑,逼迫我设计msOS的时候,考虑把业务逻辑与菜单界面分离开,这必须要引入RTOS,而uC/OS-II因为广为人知又相对简单一些,所以选择了uC/OS-II。

这一次正式选用uC/OS-II,必须要深入理解透彻每一个细节,否则因为自己对uC/OS-II的理解不到位,尤其是任务之间的通讯等细节问题引起的缺陷可能让自己的项目失败,这是不可接受的,所以参考书籍仔细的阅读源码,然而一接触这个源码,就让我犯晕,uC/OS-II为了实现可配置、可裁减,运用了大量的宏定义,考虑到各种情况,这严重的干扰了我的阅读,同时也有很多网友向我反应类似的问题,因为要了解uC/OS-II的核心原理,却经常被很多没用的源码干扰,他们迫切需要一份简单、清晰的源码,于是我决定先弄出一份可以清晰阅读的源码来。

第一步,去掉了绝大部分跟内核无关的事件管理功能,比如信号量、互斥型信号量、事件标志组、消息邮箱、内存管理这几个功能,只保留了msOS今后需要用到的时间管理、消息队列功能,这样一来,几乎就剩下内核部分源码,阅读大大简化了,系统基本上没有什么宏定义了,下图为uC/OS-II的头文件,非常简单,只需要定义三个宏定义:任务数、事件数和消息队列数,对于msOS来说,默认就是2、1、1,模式化了,不需要改变。

这样精简uC/OS-II,才会事半功倍!

第二步,进一步去掉用不上的功能函数,比如时间管理中只保留OSTimeDly函数,消息队列中只保留创建队列、发送消息、等待消息三个必须要用的函数。任务管理中只保留了普通的创建任务函数,其它的删除任务,挂起任务等都删除了,因为msOS中不可能用到删除任务,挂起任务这些函数,放着除了干扰我之外,没有别的作用。这样一来,基本上就没有多少宏定义了,代码较容易看懂了,下图为前两步精简后的uC/OS-II接口函数。

这样精简uC/OS-II,才会事半功倍!

第三步,因为能够看懂代码,就越觉得msOS不需要uC/OS-II这么多复杂的功能,比如msOS一般来说只需要两个任务即可,uC/OS-II却支持 64个任务,因为支持64个任务,需要一个8*8bit的就绪表,为了实现快速查找最高优先级任务,需要一个算法和一个256字节的查找表,虽然这些不是很复杂,但却把很多人搞的稀里糊涂的,比较绕,严重的干扰了内核的阅读理解,所以要降低任务,只需要支持8个即可,对于msOS来说,8个都已经太多了,完全满足了,而实际的项目,一般都是几个任务即可,不建议大家开太多的任务,这样严重影响效率,并且各个任务之间通讯,访问资源等都容易引起很多冲突,理解不准确会导致一系列问题。

下图为msOS双任务查找表,数组中只有4个数据。实际上因为msOS只有两个任务,MenuTask是永恒最低任务存在,只要LogicTask激活,就马上执行LogicTask,处理完后再退到MenuTask,所以只需要识别LogicTask即可,根本不需要算法中的查找表,只是为了保留与 uC/OS-II统一,预留扩展8个任务,所以还保留了任务查找表风格。

这样精简uC/OS-II,才会事半功倍!

第四步,因为只有8个任务,而uC/OS-II默认有两个内部任务:统计任务与空闲任务,所以需要去掉这两个任务,msOS中必须要有业务逻辑与菜单界面两个任务,优先级最低的任务是菜单界面,这样还有6个任务可以供额外使用,6个已经足够了,非特别情况下,不建议用。

第五步,uC/OS-II的任务块和事件块是采用链表结构的,可以动态增删,但这一点对于绝大部分项目来说,没有意义,尤其是对msOS来说,根本就不需要动态的,于是把链表结构改成数组结构,这样非常容易看懂,也节省资源。

第六步,按C#语言风格标准化,跟msOS统一编程风格。

通过以上六步操作操作之后,uC/OS-II非常简单明了,只有os.c、os.h和os_a.asm三个文件,os.c中只有寥寥15个函数,os_a.asm中只有4个汇编函数。考虑到扩展性,还是保留了uC/OS-II的一些影子,其实若再精简下去,可能就只剩下一个内核切换,msOS 只需要两个任务即可,完全可以精简到跟uC/OS-II无关了。

转自: 嵌入式资讯精选

围观 506

1.uC/OS-II文件结构

与处理器无关的代码:OS_CORE.C, OS_FLAG.C, OS_MBOX.C, OS_MEM.C, OS_MUTEX.C, OS_Q.C, OS_SEM.C, OS_TASK.C, OS_TIME.C, UCOS_II.C, UCOS_II.H。

配置文件(与应用程序有关):OS_CFG.H, INCLUDES.H

与处理器有关的代码(移植):OS_CPU.H, OS_CPU_A.ASM, OS_CPU_C.C

2.uC/OS-II组成部分

uC/OS-II大致可以分成系统核心(包含任务调度)、任务管理、时间管理、多任务同步与通信、内存管理、CPU移植等部分。

(1) 核心部分(OSCore.c) :uC/OS-II处理核心,包括初始化、启动、中断管理、时钟中断、任务调度及事件处理等用于系统基本维持的函数。

(2) 任务管理(OSTask.c) :包含与任务操作密切相关的函数,包括任务建立、删除、挂起及恢复等,uC/OS II以任务为基本单位进行调度。

(3) 时钟部分(OSTime.c) :uC/OS-II中最小时钟单位是timetick(时钟节拍),其中包含时间延迟、时钟设置及时钟恢复等与时钟相关的函数。

(4) 多任务同步与通信(OSMbox.c, OSQ.c, OSSem.c, OSMutex.c, OSFlag.c):包含事件管理函数,涉及Mbox、msgQ、Sem、Mutex、Flag等。

(5) 内存管理部分(OSMem.c):主要用于构建私有的内存分区管理机制,其中包含创建memPart、申请/释放memPart、获取分区信息等函数。

(6) CPU接口部分:uC/OS-II针对特定CPU的移植部分,由于牵涉到SP等系统指针,通常用汇编语言编写,包括任务切换、中断处理等内容。

3.uC/OS-II任务状态

在uC/OS-II中,一个任务就是一个线程,该任务可以认为CPU完全属于它自己。任务有自己的堆栈和CPU寄存器,并且被赋予一定的优先级。任务可能处于睡眠、就绪、运行、等待或中断服务状态之一。

(1) uC/OS-II调度算法
uC/OS-II采用基于优先级的调度算法,总是选择当前处于就绪状态的优先级最高的任务进行调度。uC/OS-II是可抢占性的强实时性OS,在完成中断后允许进行新的任务调度。

uC/OS-II有两种调度方式:任务级任务调度、中断级任务调度。

(2) 应用程序中函数的调用关系

#include “includes.h”
#define TASK_STK_SIZE 512 // 定义堆栈长度(1024字节)
OS_STK TaskStk[TASK_STK_SIZE]; // 定义一个数组来作为任务堆栈
void main()
{
……
OSInit(); // 初始化uC/OS-IIi
……
OSTaskCreate(MyTask1,……); // 创建用户任务1
OSTaskCreate(MyTask2,……); // 创建用户任务2
……
OSStart(); // 启动任务
……
}
void MyTask(void *pdata)
{
for (;;)
{
可以被中断的任务代码;
OS_ENTER_CRITICAL(); //进入临阶段(关中断)
不可以被中断的任务代码;
OS_EXIT_CRITICAL(); //退出临阶段 (开中断)
可以被中断的任务代码;
}
}

空任务块链表(所有任务控制块还没有分配给任务),是在应用程序调用函数OSInit()对系统进行初始化时建立的。

任务建立时,首先会调用OSTaskStkInit(),也会调用函数OS_TCBInit()初始化任务控制块OS_TCB,函数OSTaskCreate()或OSTaskCreateExt()调用任务控制块初始化函数OS_TCBInit()。

OS_TCBInit()然后调用OSTCBCreatHook()。

OSTCBCreatHook()是用户自定义的函数。

当用户程序调用函数OSTaskCreate()创建一个任务时,这个函数会调用系统函数OSTCBInit()来为任务控制块进行初始化。这个函数首先为被创建任务从空任务控制块链表获取一个任务控制块,然后利用任务的属性对任务控制块各个成员进行赋值,最后再把这个任务控制块链入到任务控制块链表的头部。

uC/OS-II有两种调度器:
任务级调度器(由OSSched()实现)、中断级调度器(由OSIntExt()实现)

(3) 任务级任务调度
指在非中断返回时进行任务调度,一般发生在当前任务因时间延迟或等待某事件而阻塞或被挂起,或有更高优先级的任务处于就绪状态。

任务的基本信息:

  -  CPU的PC寄存器:任务当前执行的位置;
  -  CPU的通用寄存器:任务当前执行涉及的临时数据;
  -  CPU的状态寄存器:存储当前CPU的状态。

任务级任务切换:从一个任务直接切换至另一个任务,不涉及CPU状态的切换,OS_TASK_SW()既保存当前任务上下文,又恢复新任务上下文。
过程:OS_Sched() -> OS_SchedNew() -> OS_TASK_SW()

来源: 嵌入式资讯精选

围观 453
订阅 RSS - uC/OS-II