μC/OS-II 实时操作系统
μC/OS是一个特殊风格的嵌入式操作系统,它有多个版本,可以适应从x86到8051的各种不同类型不同规模的嵌入式系统,原先代码开放,但某些改进版本,代码不开放。
1、μC/OS-II 的特点
可移植性:绝大部分μC/OS的源码是用移植性很强的ANSI C写的,和微处理器硬件相关的那部分是用汇编语言写的,汇编语言写的部分已经压到最低限度。
可固化:μC/OS是为嵌入式应用而设计的,用户可以通过固化手段将μC/OS嵌入到产品中成为产品的一部分。
可裁减:μC/OS系统由多个相对独立的、短小精炼的目标模块组成,用户可根据需要选择适当模块来裁剪和配置系统,这样,通过目标模块之间的按需组合,可以减少产品中的μC/OS所需的存储空间,这种裁减性是靠条件编译实现的。
占先式:μC/OS完全是占先式的实时内核,即μC/OS总是运行就绪条件下优先级最高的任务。
多任务:μC/OS可以管理64个任务,每个任务的优先级必须是不同的,其中系统占用8个,应用程序最多可以有56个任务。
可确定性:全部μC/OS的函数调用与服务的执行时间是可知的,即μC/OS系统服务的执行时间不依赖于应用程序任务的多少。
任务栈:μC/OS允许每个任务有不同的堆栈空间,以便压低应用程序对RAM的需求。
系统服务:μC/OS有多个相对独立的、短小精炼的目标模块组成,这些模块有:任务管理、时间管理、任务间的通信与同步、内存管理。其中:任务管理提供建立任务、删除任务、请求删除任务、任务的堆栈检查、改变任务的优先级、挂起任务、恢复任务和任务信息查询的系统调用;时间管理提供任务延时、取消任务延时和查询系统时间的系统调用;任务间通信与同步提供基于信号量、邮箱和消息队列机制的系统调用;内存管理提供内存分区的建立、分配、释放和查询的系统调用。
中断管理:中断可以使正在执行的任务暂时挂起,如果优先级更高的任务被该中断唤醒,则高优先级的任务在中断嵌套全部退出后立即执行,中断嵌套层数可达255层。
稳定性和可靠性:μC/OS自1992年以来已经有好几百个商业应用。
2、μC/OS-II 内核
实时操作系统对系统资源进行管理。主要包括任务调度、时间管理、内存管理、资源管理(信号灯、邮箱、消息队列)四大部分。μC/OS所有系统服务均由内核提供。内核将应用系统和底层硬件结合成一个完整的实时系统。
⑴ 任务调度
任务可以是一个无限的循环,也可以是在一次执行完毕后被删除掉。这里要注意的是,任务代码并不是被真正的删除了,而只是μC/OS-Ⅱ不再理会该任务代码,所以该任务代码不会再运行。
任务看起来与任何C函数一样,具有一个返回类型和一个参数,只是它从不返回。任务的返回类型必须被定义成void型。
任务的调度包括如何在用户的应用程序中建立任务、删除任务、改变任务的优先级、挂起和恢复任务,以及获得有关任务的信息。它主要由下列函数组成:
◇ 建立任务OSTaskCreate()
◇ 建立任务OSTaskCreateExt()
◇ 堆栈检验OSTaskStkChk()
◇ 删除任务OSTaskDel()
◇ 请求删除任务OSTaskDelReq()
◇ 改变任务的优先级OSTaskChangePrio()
◇ 挂起任务OSTaskSuspend()
◇ 恢复任务OSTaskResume()
◇ 获得有关任务的信息OSTaskQuery()
以上函数可以在OS_TASK文件中找到。
① 建立任务函数OSTaskCreate()或OSTaskCreateExt()
想让μC/OS-Ⅱ管理用户的任务,用户必须要先建立任务。用户可以通过传递任务地址和其它参数到以下两个函数之一来建立任务:OSTaskCreate() 或OSTaskCreateExt()。OSTaskCreate()与μC/OS是向下兼容的,OSTaskCreateExt()是OSTaskCreate()的扩展版本,提供了一些附加的功能。用两个函数中的任何一个都可以建立任务。
任务可以在多任务调度开始前建立,也可以在其它任务的执行过程中被建立。在开始多任务调度(即调用OSStart())前,用户必须建立至少一个任务。任务不能由中断服务程序(ISR)来建立。
OSTaskCreate() 格式为:
INT8U OSTaskCreate (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio)
OSTaskCreate()需要四个参数:
task 是任务代码的指针,
pdata 是当任务开始执行时传递给任务的参数的指针,
ptos 是分配给任务的堆栈的栈顶指针,
prio 是分配给任务的优先级。
返回值:OS_NO_ERR 任务成功建立
OS_PRIO_EXIST 此优先级上已有一个任务
OS_PRIO_INVALID 此优先级上已有一个休眠的任务
OSTaskCreateExt() 格式为:
INT8U OSTaskCreateExt (void (*task)(void *pd),
void *pdata,
OS_STK *ptos,
INT8U prio,
INT16U id,
OS_STK *pbos,
INT32U stk_size,
void *pext,
INT16U opt)
OSTaskCreateExt()需要九个参数!前四个参数(task,pdata,ptos和prio)与OSTaskCreate()的四个参数完全相同,连先后顺序都一样。
id 参数为要建立的任务创建一个特殊的标识符。该参数在μC/OS以后的升级版本中可能会用到,但在μC/OS-Ⅱ中还未使用。这个标识符可以扩展μC/OS-Ⅱ功能,使它可以执行的任务数超过目前的64个。但在这里,用户只要简单地将任务的id设置成与任务的优先级一样的值就可以了。
pbos 是指向任务的堆栈栈底的指针,用于堆栈的检验。
stk_size 用于指定堆栈成员数目的容量。也就是说,如果堆栈的入口宽度为4字节宽,那么stk_size为10000是指堆栈有40000个字节。该参数与pbos一样,也用于堆栈的检验。
pext 是指向用户附加的数据域的指针,用来扩展任务的OS_TCB。
opt 用于设定OSTaskCreateExt()的选项,指定是否允许堆栈检验,是否将堆栈清零,任务是否要进行浮点操作等等。μCOS_Ⅱ.H文件中有一个所有可能选项(OS_TASK_OPT_STK_CHK,OS_TASK_OPT_STK_CLR和OS_TASK_OPT_SAVE_FP)的常数表。每个选项占有opt的一位,并通过该位的置位来选定(用户在使用时只需要将以上OS_TASK_OPT_???选项常数进行位或(OR)操作就可以了)。
② 任务堆栈和堆栈检验函数OSTaskStkChk()
每个任务都有自己的堆栈空间。堆栈必须声明为OS_STK类型,并且由连续的内存空间组成。用户可以静态分配堆栈空间(在编译的时候分配)也可以动态地分配堆栈空间(在运行的时候分配)。
堆栈检验函数OSTaskStkChk()可以用来统计从堆栈栈底开始的堆栈空闲空间。这样用户就可以避免为任务分配过多的堆栈空间,从而减少自己的应用程序代码所需的RAM(内存)数量。
OSTaskStkChk() 格式为:
INT8U OSTaskStkChk (INT8U prio, OS_STK_DATA *pdata)
prio 是想检验的任务的优先级。
0S_STK_DATA数据结构用来保存有关任务堆栈的信息。
③ 删除任务OSTaskDel()
通过调用OSTaskDel()就可以完成删除任务的功能。OSTaskDel()一开始应确保用户所要删除的任务并非是空闲任务,因为删除空闲任务是不允许的。
OSTaskDel() 格式为:
INT8U OSTaskDel (INT8U prio)
prio是想删除的任务的优先级。
④ 请求删除任务OSTaskDelReq()
有时候,如果任务A拥有内存缓冲区或信号量之类的资源,而任务B想删除该任务,这些资源就可能由于没被释放而丢失。在这种情况下,用户可以想法子让拥有这些资源的任务在使用完资源后,先释放资源,再删除自己。用户可以通过OSTaskDelReq()函数来完成该功能。
发出删除任务请求的任务(任务B)和要删除的任务(任务A)都需要调用OSTaskDelReq()函数。
任务B在需要请求删除任务的情况下调用OSTaskDelReq()函数。如果要被删除的任务不存在(即任务已被删除或是还没被建立),OSTaskDelReq()返回OS_TASK_NOT_EXIST。如果OSTaskDelReq()的返回值为OS_NO_ERR,则表明请求已被接受但任务还没被删除。
任务A通过调用OSTaskDelReq(OS_PRIO_SELF)来确认自己是否需要被删除。
⑤ 改变任务的优先级OSTaskChangePrio()
在用户建立任务的时候会分配给任务一个优先级。在程序运行期间,用户可以通过调用OSTaskChangePrio()来改变任务的优先级。μC/OS-Ⅱ允许用户动态的改变任务的优先级。
OSTaskChangePrio() 格式为:
INT8U OSTaskChangePrio (INT8U oldprio, INT8U newprio)
oldprio 旧的优先级
newprio 新的优先级
μC/OS-Ⅱ不允许多个任务具有相同的优先级,所以OSTaskChangePrio()需要检验新优先级是否是合法的(即不存在具有新优先级的任务)。如果新优先级是合法的,则保留这个优先级。
⑥ 挂起任务OSTaskSuspend()
通过调用OSTaskSuspend()函数可以挂起任务。被挂起的任务只能通过调用OSTaskResume()函数来恢复。
OSTaskSuspend() 格式为:
INT8U OSTaskSuspend (INT8U prio)
prio是想挂起的任务的优先级。
返回值:OS_NO_ERR 任务被成功挂起
OS_SUSPEND_IDLE 任务已休眠,不能被挂起
OS_PRIO_INVALID 此优先级大于OS_MAX_TASK
OS_TASK_SUSP_PRIO 此优先级没有被注册的任务
⑦ 恢复任务OSTaskResume()
通过调用OSTaskResume()函数来恢复被挂起的任务。
OSTaskResume() 格式为:
INT8U OSTaskResume (INT8U prio)
prio是想恢复的任务的优先级。
返回值:OS_NO_ERR 任务被成功激活
OS_TASK_NOT_SUSP 任务并没有被挂起
OS_PRIO_INVALID 此优先级大于OS_MAX_TASK
OS_TASK_NOT_EXIST 此优先级没有被注册的任务
⑧ 获得有关任务的信息OSTaskQuery()
用户的应用程序可以通过调用OSTaskQuery()来获得自身或其它应用任务的信息。
OSTaskQuery() 格式为:
INT8U OSTaskQuery (INT8U prio, OS_TCB *pdata)
prio是想获得信息的任务的优先级。
OS_TCB是想获得信息的任务控制块
⑵ 时间管理
μC/OS-Ⅱ(其它内核也一样)要求用户提供定时中断来实现延时与超时控制等功能。这个定时中断叫做时钟节拍,它应该每秒发生10至100次。时钟节拍的实际频率是由用户的应用程序决定的。时钟节拍的频率越高,系统的负荷就越重。
与时钟节拍有关的系统服务主要有:
◇ 任务延时函数 OSTimeDly()
◇ 按时分秒延时函数OSTimeDlyHMSM()
◇ 结束延时函数 OSTimeDlyResume()
◇ 设置系统时间 OSTimeGet()
◇ 返回系统时间 OSTimeSet()
它们都包含在OS_TIME.C文件中。
① 任务延时函数 OSTimeDly()
OSTimeDly()函数可以使用户按时钟节拍数来定义延时时间。该函数的参数是延时的时钟节拍数_____一个1 到65535之间的数。使用该函数,用户的应用程序需要知道延时时间对应的时钟节拍的数目。
任务调用OSTimeDly()后,一旦规定的时间期满或者有其它的任务通过调用OSTimeDlyResume()取消了延时,它就会马上进入就绪状态。注意,只有当该任务在所有就绪任务中具有最高的优先级时,它才会立即运行。
OSTimeDly() 格式为:
void OSTimeDly (INT16U ticks)
ticks 延时的时钟节拍数(1~65535)
② 按时分秒延时函数 OSTimeDlyHMSM()
OSTimeDlyHMSM()函数可以使用户按小时(H)、分(M)、秒(S)和毫秒(m)来定义延时时间。
任务调用OSTimeDlyHMSM()后,一旦规定的时间期满或者有其它的任务通过调用OSTimeDlyResume()取消了延时,它就会马上处于就绪态。同样,只有当该任务在所有就绪态任务中具有最高的优先级时,它才会立即运行。
OSTimeDlyHMSM() 格式为:
INT8U OSTimeDlyHMSM (INT8U hours, INT8U minutes, INT8U seconds, INT16U milli)
hours 小时
minutes 分
seconds 秒
milli 毫秒
③ 结束延时函数 OSTimeDlyResume()
μC/OS-Ⅱ允许用户结束延时正处于延时期的任务。通过调用OSTimeDlyResume()可以取消指定任务的延时,不等待延时期满,就使指定的任务处于就绪态。实际上,OSTimeDlyResume()也可以唤醒正在等待事件的任务,虽然这一点并没有提到过。在这种情况下,等待事件发生的任务会考虑是否终止等待事件。
该函数的参数是要恢复的任务的优先级。
OSTimeDlyResume() 格式为:
INT8U OSTimeDlyResume (INT8U prio)
prio 是想唤醒的任务的优先级
返回值:OS_NO_ERR 任务被成功唤醒
OS_TIME_DLY 任务并没有休眠
OS_PRIO_INVALID 此优先级大于OS_MAX_TASK
OS_TASK_NOT_EXIST 此优先级没有被注册的任务
④ 设置系统时间 OSTimeSet()
通过调用OSTimeSet()可以改变时钟节拍计数器的值。
该函数的参数是时钟节拍计数器的新值。
OSTimeSet() 格式为:
void OSTimeSet (INT32U ticks)
ticks 是时钟节拍计数器的新值
⑤ 返回系统时间 OSTimeGet()
通过调用OSTimeGet()可以获得时钟节拍计数器的当前值。
OSTimeGet() 格式为:
INT32U OSTimeGet (void)
返回值:ticks 时钟节拍计数器的当前值
⑶ 内存管理
内存管理模块用来对需要管理的内存块进行简单的管理:分配(动态分配)和释放(动态回收)。它主要由一个数据结构体和五个函数组成:
◇ 内存控制块数据结构OS_MEM
◇ 内存分区建立函数OSMemCreate()
◇ 内存块分配函数OSMemGet()
◇ 内存块释放函数OSMemPut()
◇ 内存分区状态查询函数OSMemQuery()
◇ 内存控制块链表初始化函数OSMemInit()
① 内存控制块数据结构OS_MEM
为了便于内存的管理,在μC/OS-II中使用内存控制块(memory control blocks)的数据结构来跟踪每一个内存分区。
其定义如下:
typedef struct {
void *OSMemAddr;
void *OSMemFreeList;
INT32U OSMemBlkSize;
INT32U OSMemNBlks;
INT32U OSMemNFree;
} OS_MEM;
系统中每个内存分区必须有一个属于自己的内存控制块,只有这样,内存管理模块中的五个函数才能对这个内存分区进行管理和操作。
.OSMemAddr是指向内存分区起始地址的指针。它在建立内存分区时被初始化,在此之后就不能更改了。
.OSMemFreeList是指向下一个空闲内存控制块或者下一个空闲的内存块的指针,具体含义要根据该内存分区是否已经建立来决定。
.OSMemBlkSize是内存分区中内存块的大小,是用户建立该内存分区时指定的。
.OSMemNBlks是内存分区中总的内存块数量,也是用户建立该内存分区时指定的。
.OSMemNFree是内存分区中当前可以得空闲内存块数量。
如果要在μC/OS-II中使用内存管理,需要:
Ⅰ.打开配置文件OS_CFG.H,将开关量OS_MEM_EN设置为1:#define OS_MEM_EN 0
Ⅱ.打开配置文件OS_CFG.H,设置系统要建立的任务分区的数量:#define OS_MAX_MEM_PART,该常数值至少应为2。
② 内存分区建立函数OSMemCreate()
在使用一个内存分区之前,必须先建立该内存分区。这个操作可以通过调用OSMemCreate()函数来完成。
OSMemCreate() 格式为:
OSMemCreate(OSMemAddr, OSMemNBlks, OSMemBlkSize, &err)
该函数共有4个参数:内存分区的起始地址、分区内的内存块总块数、每个内存块的字节数和一个指向错误信息代码的指针。
③ 内存块分配函数OSMemGet()
应用程序调用OSMemGet()函数可以从已经建立的内存分区中申请一个内存块。该函数的唯一参数是指向特定内存分区的指针,该指针在建立内存分区时,由OSMemCreate()函数返回。显然,应用程序必须知道内存块的大小,并且在使用时不能超过该容量。
OSMemGet() 格式为:
void *OSMemGet (OS_MEM *pmem, INT8U *err)
*pmem 是指向特定内存分区的指针
*err 是指向错误信息代码的指针
④ 内存块释放函数OSMemPut()
用户创建的任务不再使用申请来的内存块的时候,必须及时的调用OSMemPut()来把内存块释放到相应的内存分区中去。需要注意的是,这个内存块从那个内存分区中申请来的就必须释放到那个内存分区中去,否则会造成系统崩溃;这个用户在编写任务的时候注意就可以避免了。
OSMemGet()和OSMemPut()应该成对使用;
OSMemPut() 格式为:
INT8U OSMemPut (OS_MEM *pmem, void *pblk)
*pmem 是指向特定内存分区的指针
*pblk 空闲内存块链表
⑤ 内存分区状态查询函数OSMemQuery()
在μC/OS-II 中,可以使用OSMemQuery()函数来查询一个特定内存分区的相关信息。通过该函数可以知道特定内存分区中内存块的大小、可用内存块数和正在使用的内存块数等信息。所有这些信息都放在一个叫OS_MEM_DATA的数据结构中。
OSMemQuery() 格式为:
INT8U OSMemQuery (OS_MEM *pmem, OS_MEM_DATA *pdata)
*pmem 是指向特定内存分区的指针
*pdata OS_MEM_DATA的数据结构
⑷ 资源管理
μC/OS-II 中的资源管理主要包括:信号灯、邮箱、消息队列等。
① 信号灯
信号灯管理模块主要由五个函数组成:
◇ 信号灯初始化函数OSSemInit()
◇ 信号灯获取函数OSSemPend()
◇ 信号灯计数器函数OSSemAccept()
◇信号灯释放函数OSSemPost()
◇ 信号灯删除函数OSSemClear()
Ⅰ、信号灯初始化函数OSSemInit()
OSSemInit()函数初始化一个信号灯,可用来同步对公共资源的存取。
OSSemInit() 格式为:
UBYTE OSSemInit (OS_SEM *psem, UWORD cnt)
*psem 是信号灯指针
cnt 可同时支持的最大进程数
返回值:OS_NO_ERR 信号灯被初始化
Ⅱ、信号灯获取函数OSSemPend()
OSSemPend()函数用来获取一个信号灯以及对其保护资源的存取。
OSSemPend() 格式为:
UBYTE OSSemPend(OS_SEM *psem, UWORD timeout)
*psem 是信号灯指针
timeout 以内核时钟节拍为单位的等待时间(1~65534)
返回值:OS_NO_ERR 获得信号灯
OS_SEM_NODATA 信号灯被占用( timeout =OS_NO_SUSP时)
OS_TIMEOUE 信号灯被占用(经过等待timeout 后)
Ⅲ、信号灯计数器函数OSSemAccept()
OSSemAccept()函数可用来利用信号灯实现事件计数器的功能。
OSSemAccept() 格式为:
UBYTE OSSemAccept(OS_SEM *psem, UWORD *cnt, UWORD timeout)
*psem 是信号灯指针
*cnt 变量指针,该变量获得计数值
timeout 以内核时钟节拍为单位的等待时间(1~65534)
返回值:OS_NO_ERR 至少有一次事件发生
OS_SEM_NODATA 计时器为0( timeout =OS_NO_SUSP时)
OS_TIMEOUE 计时器为0(经过等待timeout 后)
Ⅳ、信号灯释放函数OSSemPost()
OSSemPost()函数用来释放使用完的信号灯,释放被保护资源。
OSSemPost() 格式为:
UBYTE OSSemPost(OS_SEM *psem)
*psem 是信号灯指针
返回值:OS_NO_ERR 信号灯被释放
OS_SEM_OVF 信号灯操作出错(计数值太大)
Ⅴ、信号灯删除函数OSSemClear()
OSSemClear()函数用来删除信号灯的计数值。
OSSemClear() 格式为:
UBYTE OSSemClear(OS_SEM *psem)
*psem 是信号灯指针
② 邮箱
邮箱管理模块主要由三个函数组成:
◇ 邮箱初始化函数OSMboxInit()
◇ 邮箱获取函数OSMboxPend()
◇邮箱释放函数OSMboxPost()
Ⅰ、邮箱初始化函数OSMboxInit()
OSMboxInit()函数用来初始化一个信箱。
OSMboxInit() 格式为:
UBYTE OSMboxInit(OS_MBOX*pmbox)
*pmbox 是信箱指针
返回值:OS_NO_ERR 信箱被初始化
Ⅱ、邮箱获取函数OSMboxPend()
OSMboxPend()函数用来从信箱获取信息。
OSMboxPend()格式为:
UBYTE OSMboxPend(OS_MBOX*pmbox, void OS_FAR *msg, UWORD timeout)
*pmbox 是信箱指针
*msg 接收缓冲区指针
timeout 以内核时钟节拍为单位的等待时间(1~65534)
返回值:OS_NO_ERR 成功获取信息
OS_MBOX_NODATA 信箱中没有信息( timeout =OS_NO_SUSP时)
OS_TIMEOUE 信箱中没有信息(经过等待timeout 后)
Ⅲ、邮箱释放函数OSMboxPost()
OSMboxPost()函数用来向信箱发送一个信息。
OSMboxPost()格式为:
UBYTE OSMboxPost(OS_MBOX*pmbox, void OS_FAR *msg, UWORD timeout)
*pmbox 是信箱指针
*msg 发送缓冲区指针
timeout 以内核时钟节拍为单位的等待时间(1~65534)
返回值:OS_NO_ERR 成功发送信息
OS_MBOX_FULL 信箱已满( timeout =OS_NO_SUSP时)
OS_TIMEOUE 信箱已满(经过等待timeout 后)
③ 消息队列
消息队列管理模块主要由六个函数组成:
◇ 队列初始化函数OSQueueInit()
◇ 队列状态获取函数OSQueueInfo()
◇ 队列获取函数OSQueuePend()
◇ 队列发送函数OSQueuePost()
◇ 队列头发送函数OSQueueFrontPost()
◇ 队列删除函数OSQueueClear()
Ⅰ、队列初始化函数OSQueueInit()
OSQueueInit()函数用来初始化一个队列。
OSQueueInit() 格式为:
UBYTE OSQueueInit(OS_Q*pq, void OS_HUGE*buffer, UWORD size)
*pq 是队列指针
*buffer 是内核缓冲区指针
size 以字节为单位的内核缓冲区大小
返回值:OS_NO_ERR 队列被初始化
Ⅱ、队列状态获取函数OSQueueInfo()
OSQueueInfo()函数用来获取一个队列的状态。
OSQueueInfo() 格式为:
UBYTE OSQueueInfo(OS_Q*pq, UWORD*size, UWORD*used, UBYTE *prio)
*pq 是队列指针
*size 指向变量的指针,将获得队列大小
*used 指向变量的指针,将获得队列中已用字节数
*prio 指向变量的指针,将获得处于等待状态的任务的优先级
返回值:OS_NO_ERR 无错误
Ⅲ、队列获取函数OSQueuePend()
OSQueuePend()函数用来从队列中获取一个字节。
OSQueuePend() 格式为:
UBYTE OSQueuePend(OS_Q*pq, UBYTE OS_FAR *msg, UWORD timeout)
*pq 是队列指针
*msg 接收字节指针
timeout 以内核时钟节拍为单位的等待时间(1~65534)
若使 timeout=OS_NO_SUSP ,没有可用字节,函数也会立即返回;若使 timeout=OS_SUSPEND,没有可用字节,函数将一直等到有可用字节才返回。
返回值:OS_NO_ERR 成功获取信息
OS_Q_NODATA 队列中没有信息( timeout =OS_NO_SUSP时)
OS_TIMEOUE 队列中没有信息(经过等待timeout 后)
Ⅳ、队列发送函数OSQueuePost()
OSQueuePost()函数用来发送一个字节到队列中。
OSQueuePost() 格式为:
UBYTE OSQueuePost(OS_Q*pq, UBYTE OS_FAR *msg, UWORD timeout)
*pq 是队列指针
*msg 接收字节指针
timeout 以内核时钟节拍为单位的等待时间(1~65534)
若使 timeout=OS_NO_SUSP ,队列没有可用空间,函数也会立即返回;若使 timeout=OS_SUSPEND,队列没有可用空间,函数将一直等到有可用空间才返回。
返回值:OS_NO_ERR 成功发送
OS_Q_FULL 队列满( timeout =OS_NO_SUSP时)
OS_TIMEOUE 队列满(经过等待timeout 后)
Ⅴ、队列头发送函数OSQueueFrontPost()
OSQueueFrontPost()函数用来发送一个字节到队列头部。
OSQueueFrontPost() 格式为:
UBYTE OSQueueFrontPost(OS_Q*pq, UBYTE OS_FAR *msg, UWORD timeout)
*pq 是队列指针
*msg 接收字节指针
timeout 以内核时钟节拍为单位的等待时间(1~65534)
若使 timeout=OS_NO_SUSP ,队列没有可用空间,函数也会立即返回;若使 timeout=OS_SUSPEND,队列没有可用空间,函数将一直等到有可用空间才返回。
返回值:OS_NO_ERR 成功发送
OS_Q_FULL 队列满( timeout =OS_NO_SUSP时)
OS_TIMEOUE 队列满(经过等待timeout 后)
Ⅵ、队列删除函数OSQueueClear()
OSQueueClear()函数用来删除一个队列中的内容。
OSQueueClear() 格式为:
UBYTE OSQueueClear(OS_Q*pq)
*pq 是队列指针
返回值:OS_NO_ERR 队列中的内容被删除
3、μC/OS 51移植
移植的时候内核是不变的,开发者根据自己应用系统的需要来选择实时操作系统内核,开发者不能对内核随意访问,只能使用内核提供的功能服务来开发自己的应用系统。内核确定,那么所提供的系统管理能力,系统服务也就得到了限定。开发者只能在规定的范围内对系统作些改动。
μC/OS 51移植涉及到两个方面:与处理器相关的代码和与应用相关的代码。
⑴ 与处理器相关的代码
这是移植中最关键的部分。内核将应用系统和底层硬件有机的结合成一个实时系统,要使同一个内核能适用于不同的硬件体系,就需要在内核和硬件之间有一个中间层,这就是与处理器相关的代码。处理器不同,这部分代码也不同。
我们在移植时需要自己处理这部分代码,可以自己编写,也可以直接使用已经成功移植的代码。
在μC/OS中这一部分代码包括三个文件:OS_CPU.H, OS_CPU_A.ASM,OS_CPU_C.C。
① OS_CPU.H
包括了用#define定义的与处理器相关的常量,宏和类型定义。
具体来讲有系统数据类型定义,栈增长方向定义,关中断和开中断定义,系统软中断的定义等等。
② OS_CPU_A.ASM
这部分需要对处理器的寄存器进行操作,所以必须用汇编语言来编写。包括四个子函数:OSStartHighRdy(),OSCtxSw(),OSIntCtxSw(),OSTickISR()。
OSStartHighRdy()
在多任务系统启动函数OSStart()中调用。完成的功能是:设置系统运行标志位OSRunning = TRUE;将就绪表中最高优先级任务的栈指针Load到SP中,并强制中断返回。这样就绪的最高优先级任务就如同从中断里返回到运行态一样,使得整个系统得以运转。
OSCtxSw()
在任务级任务切换函数中调用的。任务级切换是通过SWI或者TRAP人为制造的中断来实现的ISR的向量地址必须指向OSCtxSw()。这一中断完成的功能:保存任务的环境变量(主要是寄存器的值,通过入栈来实现),将当前SP存入任务TCB中,载入就绪最高优先级任务的SP,恢复就绪最高优先级任务的环境变量,中断返回。这样就完成了任务级的切换。
OSIntCtxSw()
在退出中断服务函数OSIntExit()中调用,实现中断级任务切换。由于是在中断里调用,所以处理器的寄存器入栈工作已经做完,就不用作这部分工作了。具体完成的任务:调整栈指针(因为调用函数会使任务栈结构与系统任务切换时堆栈标准结构不一致),保存当前任务SP,载入就绪最高优先级任务的SP,恢复就绪最高优先级任务的环境变量,中断返回。这样就完成了中断级任务切换。
OSTickISR()
系统时钟节拍中断服务函数,这是一个周期性中断,为内核提供时钟节拍。频率越高系统负荷越重。其周期的大小决定了内核所能给应用系统提供的最小时间间隔服务。一般只限于ms级(跟MCU有关),对于要求更加苛刻的任务需要用户自己建立中断来解决。该函数具体内容:保存寄存器(如果硬件自动完成就可以省略),调用OSIntEnter(),调用OSTimeTick(),调用OSIntExit(),恢复寄存器,中断返回。
③ OS_CPU_C.C
μC/OS中共定义了6个函数在该文件中。但是最重要的是OSTaskStkInit(),其他都是对系统内核的扩展时用的。
OSTaskStkInit()
是在用户建立任务时系统内部自己调用的,对用户任务的堆栈进行初始化。使建立好的进入就绪态任务的堆栈与系统发生中断并且将环境变量保存完毕时的栈结构一致。这样就可以用中断返回指令使就绪的任务运行起来。
具体的入栈方式要根据不同mcu而定。需要参考用户使用的mcu说明书。同时还要考虑mcu的栈生成方式。这需要根据具体问题来分析,在此不做过多论述。
⑵ 与应用相关的代码
这一部分是用户根据自己的应用系统来定制合适的内核服务功能。包括两个文件:OS_CFG.H,INCLUDES.H。
OS_CFG.H
配置内核,用户根据需要对内核进行定制,留下需要的部分,去掉不需要的部分,设置系统的基本情况。比如系统可提供的最大任务数量,是否定制邮箱服务,是否需要系统提供任务挂起功能,是否提供任务优先级动态改变功能等等。
INCLUDES.H
系统头文件,整个实时系统程序所需要的文件,包括了内核和用户的头文件。
4、用户应用系统编写的模式
用户应用系统是整个实时系统的最高层,用户通过利用实时操作系统提供的服务来开发自己的具体程序。
kernel提供给用户一些功能函数,使得用户的系统建立更加方便,但是kernel内部不会处理用户的工作,对于整个系统的具体应用工作还得需要用户自己去考虑,如何利用好这些功能服务函数就成为一个比较重要的问题。
⑴ main函数的结构
void main (void)
{
初始化系统的硬件;
OSInit();
任务的建立,消息机制的建立;
OSStart();
}
这里需要的是在OSStart()执行之前不得启动中断,硬件系统还不能工作,必须先让软件系统进入工作状态后才行。
⑵ 中断的结构
ISR:
{
保存处理器寄存器的值;
调用OSIntEnter();
执行用户的工作;
调用OSIntExit();
恢复处理器寄存器的值;
RTI;
}
用户的中断形式和以前一样,没有什么大的变化,仅仅是在原来用户ISR的基础上在固定的位置加了两个函数:OSIntEnter(), OSIntExit()。
⑶ 各个任务的结构
void YourTask (void)
{
for(;;)
{
用户代码
调用的系统服务
}
}
在任务启动函数执行完后,系统会切换到最高优先级的任务去执行,此时,可以将系统硬件部分的启动放在该任务的最前边,仅仅是启动时执行一次,主要是启动系统的节拍中断,或者一些必须在多任务系统调度后才能初始化的部分,使系统的真正开始工作,达到软件硬件的基本同步。
Void HighestPrioTask(void)
{
OSStartHardware();
For (;;)
{
用户代码
调用的系统服务
}
}
用户可以按照这些格式去编写自己的任务,建立自己的应用系统。
习题六
1、什么是实时操作系统?
2、实时多任务操作系统与分时多任务操作系统有什么区别?
3、实时操作系统应具有哪些基本功能?
4、实时操作系统中的任务(Task)有哪几种基本状态?
5、请列举几种针对51CPU的实时操作系统。
6、RTX51实时操作系统有哪两种不同的版本,它们之间有什么区别?
7、RTX51是怎样在多个任务之间切换的?
8、μC/OS是一种什么样的操作系统?
9、μC/OS-II有哪些基本特点?
10、μC/OS-II的内核主要包括哪些组成部分?
11、μC/OS 51的移植主要涉及到哪几个代码文件?
1、μC/OS-II 的特点
可移植性:绝大部分μC/OS的源码是用移植性很强的ANSI C写的,和微处理器硬件相关的那部分是用汇编语言写的,汇编语言写的部分已经压到最低限度。
可固化:μC/OS是为嵌入式应用而设计的,用户可以通过固化手段将μC/OS嵌入到产品中成为产品的一部分。
可裁减:μC/OS系统由多个相对独立的、短小精炼的目标模块组成,用户可根据需要选择适当模块来裁剪和配置系统,这样,通过目标模块之间的按需组合,可以减少产品中的μC/OS所需的存储空间,这种裁减性是靠条件编译实现的。
占先式:μC/OS完全是占先式的实时内核,即μC/OS总是运行就绪条件下优先级最高的任务。
多任务:μC/OS可以管理64个任务,每个任务的优先级必须是不同的,其中系统占用8个,应用程序最多可以有56个任务。
可确定性:全部μC/OS的函数调用与服务的执行时间是可知的,即μC/OS系统服务的执行时间不依赖于应用程序任务的多少。
任务栈:μC/OS允许每个任务有不同的堆栈空间,以便压低应用程序对RAM的需求。
系统服务:μC/OS有多个相对独立的、短小精炼的目标模块组成,这些模块有:任务管理、时间管理、任务间的通信与同步、内存管理。其中:任务管理提供建立任务、删除任务、请求删除任务、任务的堆栈检查、改变任务的优先级、挂起任务、恢复任务和任务信息查询的系统调用;时间管理提供任务延时、取消任务延时和查询系统时间的系统调用;任务间通信与同步提供基于信号量、邮箱和消息队列机制的系统调用;内存管理提供内存分区的建立、分配、释放和查询的系统调用。
中断管理:中断可以使正在执行的任务暂时挂起,如果优先级更高的任务被该中断唤醒,则高优先级的任务在中断嵌套全部退出后立即执行,中断嵌套层数可达255层。
稳定性和可靠性:μC/OS自1992年以来已经有好几百个商业应用。
2、μC/OS-II 内核
实时操作系统对系统资源进行管理。主要包括任务调度、时间管理、内存管理、资源管理(信号灯、邮箱、消息队列)四大部分。μC/OS所有系统服务均由内核提供。内核将应用系统和底层硬件结合成一个完整的实时系统。
⑴ 任务调度
任务可以是一个无限的循环,也可以是在一次执行完毕后被删除掉。这里要注意的是,任务代码并不是被真正的删除了,而只是μC/OS-Ⅱ不再理会该任务代码,所以该任务代码不会再运行。
任务看起来与任何C函数一样,具有一个返回类型和一个参数,只是它从不返回。任务的返回类型必须被定义成void型。
任务的调度包括如何在用户的应用程序中建立任务、删除任务、改变任务的优先级、挂起和恢复任务,以及获得有关任务的信息。它主要由下列函数组成:
◇ 建立任务OSTaskCreate()
◇ 建立任务OSTaskCreateExt()
◇ 堆栈检验OSTaskStkChk()
◇ 删除任务OSTaskDel()
◇ 请求删除任务OSTaskDelReq()
◇ 改变任务的优先级OSTaskChangePrio()
◇ 挂起任务OSTaskSuspend()
◇ 恢复任务OSTaskResume()
◇ 获得有关任务的信息OSTaskQuery()
以上函数可以在OS_TASK文件中找到。
① 建立任务函数OSTaskCreate()或OSTaskCreateExt()
想让μC/OS-Ⅱ管理用户的任务,用户必须要先建立任务。用户可以通过传递任务地址和其它参数到以下两个函数之一来建立任务:OSTaskCreate() 或OSTaskCreateExt()。OSTaskCreate()与μC/OS是向下兼容的,OSTaskCreateExt()是OSTaskCreate()的扩展版本,提供了一些附加的功能。用两个函数中的任何一个都可以建立任务。
任务可以在多任务调度开始前建立,也可以在其它任务的执行过程中被建立。在开始多任务调度(即调用OSStart())前,用户必须建立至少一个任务。任务不能由中断服务程序(ISR)来建立。
OSTaskCreate() 格式为:
INT8U OSTaskCreate (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio)
OSTaskCreate()需要四个参数:
task 是任务代码的指针,
pdata 是当任务开始执行时传递给任务的参数的指针,
ptos 是分配给任务的堆栈的栈顶指针,
prio 是分配给任务的优先级。
返回值:OS_NO_ERR 任务成功建立
OS_PRIO_EXIST 此优先级上已有一个任务
OS_PRIO_INVALID 此优先级上已有一个休眠的任务
OSTaskCreateExt() 格式为:
INT8U OSTaskCreateExt (void (*task)(void *pd),
void *pdata,
OS_STK *ptos,
INT8U prio,
INT16U id,
OS_STK *pbos,
INT32U stk_size,
void *pext,
INT16U opt)
OSTaskCreateExt()需要九个参数!前四个参数(task,pdata,ptos和prio)与OSTaskCreate()的四个参数完全相同,连先后顺序都一样。
id 参数为要建立的任务创建一个特殊的标识符。该参数在μC/OS以后的升级版本中可能会用到,但在μC/OS-Ⅱ中还未使用。这个标识符可以扩展μC/OS-Ⅱ功能,使它可以执行的任务数超过目前的64个。但在这里,用户只要简单地将任务的id设置成与任务的优先级一样的值就可以了。
pbos 是指向任务的堆栈栈底的指针,用于堆栈的检验。
stk_size 用于指定堆栈成员数目的容量。也就是说,如果堆栈的入口宽度为4字节宽,那么stk_size为10000是指堆栈有40000个字节。该参数与pbos一样,也用于堆栈的检验。
pext 是指向用户附加的数据域的指针,用来扩展任务的OS_TCB。
opt 用于设定OSTaskCreateExt()的选项,指定是否允许堆栈检验,是否将堆栈清零,任务是否要进行浮点操作等等。μCOS_Ⅱ.H文件中有一个所有可能选项(OS_TASK_OPT_STK_CHK,OS_TASK_OPT_STK_CLR和OS_TASK_OPT_SAVE_FP)的常数表。每个选项占有opt的一位,并通过该位的置位来选定(用户在使用时只需要将以上OS_TASK_OPT_???选项常数进行位或(OR)操作就可以了)。
② 任务堆栈和堆栈检验函数OSTaskStkChk()
每个任务都有自己的堆栈空间。堆栈必须声明为OS_STK类型,并且由连续的内存空间组成。用户可以静态分配堆栈空间(在编译的时候分配)也可以动态地分配堆栈空间(在运行的时候分配)。
堆栈检验函数OSTaskStkChk()可以用来统计从堆栈栈底开始的堆栈空闲空间。这样用户就可以避免为任务分配过多的堆栈空间,从而减少自己的应用程序代码所需的RAM(内存)数量。
OSTaskStkChk() 格式为:
INT8U OSTaskStkChk (INT8U prio, OS_STK_DATA *pdata)
prio 是想检验的任务的优先级。
0S_STK_DATA数据结构用来保存有关任务堆栈的信息。
③ 删除任务OSTaskDel()
通过调用OSTaskDel()就可以完成删除任务的功能。OSTaskDel()一开始应确保用户所要删除的任务并非是空闲任务,因为删除空闲任务是不允许的。
OSTaskDel() 格式为:
INT8U OSTaskDel (INT8U prio)
prio是想删除的任务的优先级。
④ 请求删除任务OSTaskDelReq()
有时候,如果任务A拥有内存缓冲区或信号量之类的资源,而任务B想删除该任务,这些资源就可能由于没被释放而丢失。在这种情况下,用户可以想法子让拥有这些资源的任务在使用完资源后,先释放资源,再删除自己。用户可以通过OSTaskDelReq()函数来完成该功能。
发出删除任务请求的任务(任务B)和要删除的任务(任务A)都需要调用OSTaskDelReq()函数。
任务B在需要请求删除任务的情况下调用OSTaskDelReq()函数。如果要被删除的任务不存在(即任务已被删除或是还没被建立),OSTaskDelReq()返回OS_TASK_NOT_EXIST。如果OSTaskDelReq()的返回值为OS_NO_ERR,则表明请求已被接受但任务还没被删除。
任务A通过调用OSTaskDelReq(OS_PRIO_SELF)来确认自己是否需要被删除。
⑤ 改变任务的优先级OSTaskChangePrio()
在用户建立任务的时候会分配给任务一个优先级。在程序运行期间,用户可以通过调用OSTaskChangePrio()来改变任务的优先级。μC/OS-Ⅱ允许用户动态的改变任务的优先级。
OSTaskChangePrio() 格式为:
INT8U OSTaskChangePrio (INT8U oldprio, INT8U newprio)
oldprio 旧的优先级
newprio 新的优先级
μC/OS-Ⅱ不允许多个任务具有相同的优先级,所以OSTaskChangePrio()需要检验新优先级是否是合法的(即不存在具有新优先级的任务)。如果新优先级是合法的,则保留这个优先级。
⑥ 挂起任务OSTaskSuspend()
通过调用OSTaskSuspend()函数可以挂起任务。被挂起的任务只能通过调用OSTaskResume()函数来恢复。
OSTaskSuspend() 格式为:
INT8U OSTaskSuspend (INT8U prio)
prio是想挂起的任务的优先级。
返回值:OS_NO_ERR 任务被成功挂起
OS_SUSPEND_IDLE 任务已休眠,不能被挂起
OS_PRIO_INVALID 此优先级大于OS_MAX_TASK
OS_TASK_SUSP_PRIO 此优先级没有被注册的任务
⑦ 恢复任务OSTaskResume()
通过调用OSTaskResume()函数来恢复被挂起的任务。
OSTaskResume() 格式为:
INT8U OSTaskResume (INT8U prio)
prio是想恢复的任务的优先级。
返回值:OS_NO_ERR 任务被成功激活
OS_TASK_NOT_SUSP 任务并没有被挂起
OS_PRIO_INVALID 此优先级大于OS_MAX_TASK
OS_TASK_NOT_EXIST 此优先级没有被注册的任务
⑧ 获得有关任务的信息OSTaskQuery()
用户的应用程序可以通过调用OSTaskQuery()来获得自身或其它应用任务的信息。
OSTaskQuery() 格式为:
INT8U OSTaskQuery (INT8U prio, OS_TCB *pdata)
prio是想获得信息的任务的优先级。
OS_TCB是想获得信息的任务控制块
⑵ 时间管理
μC/OS-Ⅱ(其它内核也一样)要求用户提供定时中断来实现延时与超时控制等功能。这个定时中断叫做时钟节拍,它应该每秒发生10至100次。时钟节拍的实际频率是由用户的应用程序决定的。时钟节拍的频率越高,系统的负荷就越重。
与时钟节拍有关的系统服务主要有:
◇ 任务延时函数 OSTimeDly()
◇ 按时分秒延时函数OSTimeDlyHMSM()
◇ 结束延时函数 OSTimeDlyResume()
◇ 设置系统时间 OSTimeGet()
◇ 返回系统时间 OSTimeSet()
它们都包含在OS_TIME.C文件中。
① 任务延时函数 OSTimeDly()
OSTimeDly()函数可以使用户按时钟节拍数来定义延时时间。该函数的参数是延时的时钟节拍数_____一个1 到65535之间的数。使用该函数,用户的应用程序需要知道延时时间对应的时钟节拍的数目。
任务调用OSTimeDly()后,一旦规定的时间期满或者有其它的任务通过调用OSTimeDlyResume()取消了延时,它就会马上进入就绪状态。注意,只有当该任务在所有就绪任务中具有最高的优先级时,它才会立即运行。
OSTimeDly() 格式为:
void OSTimeDly (INT16U ticks)
ticks 延时的时钟节拍数(1~65535)
② 按时分秒延时函数 OSTimeDlyHMSM()
OSTimeDlyHMSM()函数可以使用户按小时(H)、分(M)、秒(S)和毫秒(m)来定义延时时间。
任务调用OSTimeDlyHMSM()后,一旦规定的时间期满或者有其它的任务通过调用OSTimeDlyResume()取消了延时,它就会马上处于就绪态。同样,只有当该任务在所有就绪态任务中具有最高的优先级时,它才会立即运行。
OSTimeDlyHMSM() 格式为:
INT8U OSTimeDlyHMSM (INT8U hours, INT8U minutes, INT8U seconds, INT16U milli)
hours 小时
minutes 分
seconds 秒
milli 毫秒
③ 结束延时函数 OSTimeDlyResume()
μC/OS-Ⅱ允许用户结束延时正处于延时期的任务。通过调用OSTimeDlyResume()可以取消指定任务的延时,不等待延时期满,就使指定的任务处于就绪态。实际上,OSTimeDlyResume()也可以唤醒正在等待事件的任务,虽然这一点并没有提到过。在这种情况下,等待事件发生的任务会考虑是否终止等待事件。
该函数的参数是要恢复的任务的优先级。
OSTimeDlyResume() 格式为:
INT8U OSTimeDlyResume (INT8U prio)
prio 是想唤醒的任务的优先级
返回值:OS_NO_ERR 任务被成功唤醒
OS_TIME_DLY 任务并没有休眠
OS_PRIO_INVALID 此优先级大于OS_MAX_TASK
OS_TASK_NOT_EXIST 此优先级没有被注册的任务
④ 设置系统时间 OSTimeSet()
通过调用OSTimeSet()可以改变时钟节拍计数器的值。
该函数的参数是时钟节拍计数器的新值。
OSTimeSet() 格式为:
void OSTimeSet (INT32U ticks)
ticks 是时钟节拍计数器的新值
⑤ 返回系统时间 OSTimeGet()
通过调用OSTimeGet()可以获得时钟节拍计数器的当前值。
OSTimeGet() 格式为:
INT32U OSTimeGet (void)
返回值:ticks 时钟节拍计数器的当前值
⑶ 内存管理
内存管理模块用来对需要管理的内存块进行简单的管理:分配(动态分配)和释放(动态回收)。它主要由一个数据结构体和五个函数组成:
◇ 内存控制块数据结构OS_MEM
◇ 内存分区建立函数OSMemCreate()
◇ 内存块分配函数OSMemGet()
◇ 内存块释放函数OSMemPut()
◇ 内存分区状态查询函数OSMemQuery()
◇ 内存控制块链表初始化函数OSMemInit()
① 内存控制块数据结构OS_MEM
为了便于内存的管理,在μC/OS-II中使用内存控制块(memory control blocks)的数据结构来跟踪每一个内存分区。
其定义如下:
typedef struct {
void *OSMemAddr;
void *OSMemFreeList;
INT32U OSMemBlkSize;
INT32U OSMemNBlks;
INT32U OSMemNFree;
} OS_MEM;
系统中每个内存分区必须有一个属于自己的内存控制块,只有这样,内存管理模块中的五个函数才能对这个内存分区进行管理和操作。
.OSMemAddr是指向内存分区起始地址的指针。它在建立内存分区时被初始化,在此之后就不能更改了。
.OSMemFreeList是指向下一个空闲内存控制块或者下一个空闲的内存块的指针,具体含义要根据该内存分区是否已经建立来决定。
.OSMemBlkSize是内存分区中内存块的大小,是用户建立该内存分区时指定的。
.OSMemNBlks是内存分区中总的内存块数量,也是用户建立该内存分区时指定的。
.OSMemNFree是内存分区中当前可以得空闲内存块数量。
如果要在μC/OS-II中使用内存管理,需要:
Ⅰ.打开配置文件OS_CFG.H,将开关量OS_MEM_EN设置为1:#define OS_MEM_EN 0
Ⅱ.打开配置文件OS_CFG.H,设置系统要建立的任务分区的数量:#define OS_MAX_MEM_PART,该常数值至少应为2。
② 内存分区建立函数OSMemCreate()
在使用一个内存分区之前,必须先建立该内存分区。这个操作可以通过调用OSMemCreate()函数来完成。
OSMemCreate() 格式为:
OSMemCreate(OSMemAddr, OSMemNBlks, OSMemBlkSize, &err)
该函数共有4个参数:内存分区的起始地址、分区内的内存块总块数、每个内存块的字节数和一个指向错误信息代码的指针。
③ 内存块分配函数OSMemGet()
应用程序调用OSMemGet()函数可以从已经建立的内存分区中申请一个内存块。该函数的唯一参数是指向特定内存分区的指针,该指针在建立内存分区时,由OSMemCreate()函数返回。显然,应用程序必须知道内存块的大小,并且在使用时不能超过该容量。
OSMemGet() 格式为:
void *OSMemGet (OS_MEM *pmem, INT8U *err)
*pmem 是指向特定内存分区的指针
*err 是指向错误信息代码的指针
④ 内存块释放函数OSMemPut()
用户创建的任务不再使用申请来的内存块的时候,必须及时的调用OSMemPut()来把内存块释放到相应的内存分区中去。需要注意的是,这个内存块从那个内存分区中申请来的就必须释放到那个内存分区中去,否则会造成系统崩溃;这个用户在编写任务的时候注意就可以避免了。
OSMemGet()和OSMemPut()应该成对使用;
OSMemPut() 格式为:
INT8U OSMemPut (OS_MEM *pmem, void *pblk)
*pmem 是指向特定内存分区的指针
*pblk 空闲内存块链表
⑤ 内存分区状态查询函数OSMemQuery()
在μC/OS-II 中,可以使用OSMemQuery()函数来查询一个特定内存分区的相关信息。通过该函数可以知道特定内存分区中内存块的大小、可用内存块数和正在使用的内存块数等信息。所有这些信息都放在一个叫OS_MEM_DATA的数据结构中。
OSMemQuery() 格式为:
INT8U OSMemQuery (OS_MEM *pmem, OS_MEM_DATA *pdata)
*pmem 是指向特定内存分区的指针
*pdata OS_MEM_DATA的数据结构
⑷ 资源管理
μC/OS-II 中的资源管理主要包括:信号灯、邮箱、消息队列等。
① 信号灯
信号灯管理模块主要由五个函数组成:
◇ 信号灯初始化函数OSSemInit()
◇ 信号灯获取函数OSSemPend()
◇ 信号灯计数器函数OSSemAccept()
◇信号灯释放函数OSSemPost()
◇ 信号灯删除函数OSSemClear()
Ⅰ、信号灯初始化函数OSSemInit()
OSSemInit()函数初始化一个信号灯,可用来同步对公共资源的存取。
OSSemInit() 格式为:
UBYTE OSSemInit (OS_SEM *psem, UWORD cnt)
*psem 是信号灯指针
cnt 可同时支持的最大进程数
返回值:OS_NO_ERR 信号灯被初始化
Ⅱ、信号灯获取函数OSSemPend()
OSSemPend()函数用来获取一个信号灯以及对其保护资源的存取。
OSSemPend() 格式为:
UBYTE OSSemPend(OS_SEM *psem, UWORD timeout)
*psem 是信号灯指针
timeout 以内核时钟节拍为单位的等待时间(1~65534)
返回值:OS_NO_ERR 获得信号灯
OS_SEM_NODATA 信号灯被占用( timeout =OS_NO_SUSP时)
OS_TIMEOUE 信号灯被占用(经过等待timeout 后)
Ⅲ、信号灯计数器函数OSSemAccept()
OSSemAccept()函数可用来利用信号灯实现事件计数器的功能。
OSSemAccept() 格式为:
UBYTE OSSemAccept(OS_SEM *psem, UWORD *cnt, UWORD timeout)
*psem 是信号灯指针
*cnt 变量指针,该变量获得计数值
timeout 以内核时钟节拍为单位的等待时间(1~65534)
返回值:OS_NO_ERR 至少有一次事件发生
OS_SEM_NODATA 计时器为0( timeout =OS_NO_SUSP时)
OS_TIMEOUE 计时器为0(经过等待timeout 后)
Ⅳ、信号灯释放函数OSSemPost()
OSSemPost()函数用来释放使用完的信号灯,释放被保护资源。
OSSemPost() 格式为:
UBYTE OSSemPost(OS_SEM *psem)
*psem 是信号灯指针
返回值:OS_NO_ERR 信号灯被释放
OS_SEM_OVF 信号灯操作出错(计数值太大)
Ⅴ、信号灯删除函数OSSemClear()
OSSemClear()函数用来删除信号灯的计数值。
OSSemClear() 格式为:
UBYTE OSSemClear(OS_SEM *psem)
*psem 是信号灯指针
② 邮箱
邮箱管理模块主要由三个函数组成:
◇ 邮箱初始化函数OSMboxInit()
◇ 邮箱获取函数OSMboxPend()
◇邮箱释放函数OSMboxPost()
Ⅰ、邮箱初始化函数OSMboxInit()
OSMboxInit()函数用来初始化一个信箱。
OSMboxInit() 格式为:
UBYTE OSMboxInit(OS_MBOX*pmbox)
*pmbox 是信箱指针
返回值:OS_NO_ERR 信箱被初始化
Ⅱ、邮箱获取函数OSMboxPend()
OSMboxPend()函数用来从信箱获取信息。
OSMboxPend()格式为:
UBYTE OSMboxPend(OS_MBOX*pmbox, void OS_FAR *msg, UWORD timeout)
*pmbox 是信箱指针
*msg 接收缓冲区指针
timeout 以内核时钟节拍为单位的等待时间(1~65534)
返回值:OS_NO_ERR 成功获取信息
OS_MBOX_NODATA 信箱中没有信息( timeout =OS_NO_SUSP时)
OS_TIMEOUE 信箱中没有信息(经过等待timeout 后)
Ⅲ、邮箱释放函数OSMboxPost()
OSMboxPost()函数用来向信箱发送一个信息。
OSMboxPost()格式为:
UBYTE OSMboxPost(OS_MBOX*pmbox, void OS_FAR *msg, UWORD timeout)
*pmbox 是信箱指针
*msg 发送缓冲区指针
timeout 以内核时钟节拍为单位的等待时间(1~65534)
返回值:OS_NO_ERR 成功发送信息
OS_MBOX_FULL 信箱已满( timeout =OS_NO_SUSP时)
OS_TIMEOUE 信箱已满(经过等待timeout 后)
③ 消息队列
消息队列管理模块主要由六个函数组成:
◇ 队列初始化函数OSQueueInit()
◇ 队列状态获取函数OSQueueInfo()
◇ 队列获取函数OSQueuePend()
◇ 队列发送函数OSQueuePost()
◇ 队列头发送函数OSQueueFrontPost()
◇ 队列删除函数OSQueueClear()
Ⅰ、队列初始化函数OSQueueInit()
OSQueueInit()函数用来初始化一个队列。
OSQueueInit() 格式为:
UBYTE OSQueueInit(OS_Q*pq, void OS_HUGE*buffer, UWORD size)
*pq 是队列指针
*buffer 是内核缓冲区指针
size 以字节为单位的内核缓冲区大小
返回值:OS_NO_ERR 队列被初始化
Ⅱ、队列状态获取函数OSQueueInfo()
OSQueueInfo()函数用来获取一个队列的状态。
OSQueueInfo() 格式为:
UBYTE OSQueueInfo(OS_Q*pq, UWORD*size, UWORD*used, UBYTE *prio)
*pq 是队列指针
*size 指向变量的指针,将获得队列大小
*used 指向变量的指针,将获得队列中已用字节数
*prio 指向变量的指针,将获得处于等待状态的任务的优先级
返回值:OS_NO_ERR 无错误
Ⅲ、队列获取函数OSQueuePend()
OSQueuePend()函数用来从队列中获取一个字节。
OSQueuePend() 格式为:
UBYTE OSQueuePend(OS_Q*pq, UBYTE OS_FAR *msg, UWORD timeout)
*pq 是队列指针
*msg 接收字节指针
timeout 以内核时钟节拍为单位的等待时间(1~65534)
若使 timeout=OS_NO_SUSP ,没有可用字节,函数也会立即返回;若使 timeout=OS_SUSPEND,没有可用字节,函数将一直等到有可用字节才返回。
返回值:OS_NO_ERR 成功获取信息
OS_Q_NODATA 队列中没有信息( timeout =OS_NO_SUSP时)
OS_TIMEOUE 队列中没有信息(经过等待timeout 后)
Ⅳ、队列发送函数OSQueuePost()
OSQueuePost()函数用来发送一个字节到队列中。
OSQueuePost() 格式为:
UBYTE OSQueuePost(OS_Q*pq, UBYTE OS_FAR *msg, UWORD timeout)
*pq 是队列指针
*msg 接收字节指针
timeout 以内核时钟节拍为单位的等待时间(1~65534)
若使 timeout=OS_NO_SUSP ,队列没有可用空间,函数也会立即返回;若使 timeout=OS_SUSPEND,队列没有可用空间,函数将一直等到有可用空间才返回。
返回值:OS_NO_ERR 成功发送
OS_Q_FULL 队列满( timeout =OS_NO_SUSP时)
OS_TIMEOUE 队列满(经过等待timeout 后)
Ⅴ、队列头发送函数OSQueueFrontPost()
OSQueueFrontPost()函数用来发送一个字节到队列头部。
OSQueueFrontPost() 格式为:
UBYTE OSQueueFrontPost(OS_Q*pq, UBYTE OS_FAR *msg, UWORD timeout)
*pq 是队列指针
*msg 接收字节指针
timeout 以内核时钟节拍为单位的等待时间(1~65534)
若使 timeout=OS_NO_SUSP ,队列没有可用空间,函数也会立即返回;若使 timeout=OS_SUSPEND,队列没有可用空间,函数将一直等到有可用空间才返回。
返回值:OS_NO_ERR 成功发送
OS_Q_FULL 队列满( timeout =OS_NO_SUSP时)
OS_TIMEOUE 队列满(经过等待timeout 后)
Ⅵ、队列删除函数OSQueueClear()
OSQueueClear()函数用来删除一个队列中的内容。
OSQueueClear() 格式为:
UBYTE OSQueueClear(OS_Q*pq)
*pq 是队列指针
返回值:OS_NO_ERR 队列中的内容被删除
3、μC/OS 51移植
移植的时候内核是不变的,开发者根据自己应用系统的需要来选择实时操作系统内核,开发者不能对内核随意访问,只能使用内核提供的功能服务来开发自己的应用系统。内核确定,那么所提供的系统管理能力,系统服务也就得到了限定。开发者只能在规定的范围内对系统作些改动。
μC/OS 51移植涉及到两个方面:与处理器相关的代码和与应用相关的代码。
⑴ 与处理器相关的代码
这是移植中最关键的部分。内核将应用系统和底层硬件有机的结合成一个实时系统,要使同一个内核能适用于不同的硬件体系,就需要在内核和硬件之间有一个中间层,这就是与处理器相关的代码。处理器不同,这部分代码也不同。
我们在移植时需要自己处理这部分代码,可以自己编写,也可以直接使用已经成功移植的代码。
在μC/OS中这一部分代码包括三个文件:OS_CPU.H, OS_CPU_A.ASM,OS_CPU_C.C。
① OS_CPU.H
包括了用#define定义的与处理器相关的常量,宏和类型定义。
具体来讲有系统数据类型定义,栈增长方向定义,关中断和开中断定义,系统软中断的定义等等。
② OS_CPU_A.ASM
这部分需要对处理器的寄存器进行操作,所以必须用汇编语言来编写。包括四个子函数:OSStartHighRdy(),OSCtxSw(),OSIntCtxSw(),OSTickISR()。
OSStartHighRdy()
在多任务系统启动函数OSStart()中调用。完成的功能是:设置系统运行标志位OSRunning = TRUE;将就绪表中最高优先级任务的栈指针Load到SP中,并强制中断返回。这样就绪的最高优先级任务就如同从中断里返回到运行态一样,使得整个系统得以运转。
OSCtxSw()
在任务级任务切换函数中调用的。任务级切换是通过SWI或者TRAP人为制造的中断来实现的ISR的向量地址必须指向OSCtxSw()。这一中断完成的功能:保存任务的环境变量(主要是寄存器的值,通过入栈来实现),将当前SP存入任务TCB中,载入就绪最高优先级任务的SP,恢复就绪最高优先级任务的环境变量,中断返回。这样就完成了任务级的切换。
OSIntCtxSw()
在退出中断服务函数OSIntExit()中调用,实现中断级任务切换。由于是在中断里调用,所以处理器的寄存器入栈工作已经做完,就不用作这部分工作了。具体完成的任务:调整栈指针(因为调用函数会使任务栈结构与系统任务切换时堆栈标准结构不一致),保存当前任务SP,载入就绪最高优先级任务的SP,恢复就绪最高优先级任务的环境变量,中断返回。这样就完成了中断级任务切换。
OSTickISR()
系统时钟节拍中断服务函数,这是一个周期性中断,为内核提供时钟节拍。频率越高系统负荷越重。其周期的大小决定了内核所能给应用系统提供的最小时间间隔服务。一般只限于ms级(跟MCU有关),对于要求更加苛刻的任务需要用户自己建立中断来解决。该函数具体内容:保存寄存器(如果硬件自动完成就可以省略),调用OSIntEnter(),调用OSTimeTick(),调用OSIntExit(),恢复寄存器,中断返回。
③ OS_CPU_C.C
μC/OS中共定义了6个函数在该文件中。但是最重要的是OSTaskStkInit(),其他都是对系统内核的扩展时用的。
OSTaskStkInit()
是在用户建立任务时系统内部自己调用的,对用户任务的堆栈进行初始化。使建立好的进入就绪态任务的堆栈与系统发生中断并且将环境变量保存完毕时的栈结构一致。这样就可以用中断返回指令使就绪的任务运行起来。
具体的入栈方式要根据不同mcu而定。需要参考用户使用的mcu说明书。同时还要考虑mcu的栈生成方式。这需要根据具体问题来分析,在此不做过多论述。
⑵ 与应用相关的代码
这一部分是用户根据自己的应用系统来定制合适的内核服务功能。包括两个文件:OS_CFG.H,INCLUDES.H。
OS_CFG.H
配置内核,用户根据需要对内核进行定制,留下需要的部分,去掉不需要的部分,设置系统的基本情况。比如系统可提供的最大任务数量,是否定制邮箱服务,是否需要系统提供任务挂起功能,是否提供任务优先级动态改变功能等等。
INCLUDES.H
系统头文件,整个实时系统程序所需要的文件,包括了内核和用户的头文件。
4、用户应用系统编写的模式
用户应用系统是整个实时系统的最高层,用户通过利用实时操作系统提供的服务来开发自己的具体程序。
kernel提供给用户一些功能函数,使得用户的系统建立更加方便,但是kernel内部不会处理用户的工作,对于整个系统的具体应用工作还得需要用户自己去考虑,如何利用好这些功能服务函数就成为一个比较重要的问题。
⑴ main函数的结构
void main (void)
{
初始化系统的硬件;
OSInit();
任务的建立,消息机制的建立;
OSStart();
}
这里需要的是在OSStart()执行之前不得启动中断,硬件系统还不能工作,必须先让软件系统进入工作状态后才行。
⑵ 中断的结构
ISR:
{
保存处理器寄存器的值;
调用OSIntEnter();
执行用户的工作;
调用OSIntExit();
恢复处理器寄存器的值;
RTI;
}
用户的中断形式和以前一样,没有什么大的变化,仅仅是在原来用户ISR的基础上在固定的位置加了两个函数:OSIntEnter(), OSIntExit()。
⑶ 各个任务的结构
void YourTask (void)
{
for(;;)
{
用户代码
调用的系统服务
}
}
在任务启动函数执行完后,系统会切换到最高优先级的任务去执行,此时,可以将系统硬件部分的启动放在该任务的最前边,仅仅是启动时执行一次,主要是启动系统的节拍中断,或者一些必须在多任务系统调度后才能初始化的部分,使系统的真正开始工作,达到软件硬件的基本同步。
Void HighestPrioTask(void)
{
OSStartHardware();
For (;;)
{
用户代码
调用的系统服务
}
}
用户可以按照这些格式去编写自己的任务,建立自己的应用系统。
习题六
1、什么是实时操作系统?
2、实时多任务操作系统与分时多任务操作系统有什么区别?
3、实时操作系统应具有哪些基本功能?
4、实时操作系统中的任务(Task)有哪几种基本状态?
5、请列举几种针对51CPU的实时操作系统。
6、RTX51实时操作系统有哪两种不同的版本,它们之间有什么区别?
7、RTX51是怎样在多个任务之间切换的?
8、μC/OS是一种什么样的操作系统?
9、μC/OS-II有哪些基本特点?
10、μC/OS-II的内核主要包括哪些组成部分?
11、μC/OS 51的移植主要涉及到哪几个代码文件?
<< Home