第三章 内核结构
3.00 临界段,OS_ENTER_CRITICAL() 和 OS_EXIT_CRITICAL()
3种方法实现上边两个函数:(1) OS_CRITICAL_METHOD==1,用关中断实现OS_ENTER_CRITICAL,用开中断实现OS_EXIT_CRITLCAL();此方法的缺点是如果进入该函数前系统的中断状态是关中断,调用函数后则会改变其中断状态。(2)OS_CRITICAL_METHOD==2, 用在堆栈中保存现在的中断开关状态,然后关中断来实现OS_ENTER_CRITICAL,用从堆栈中弹出原来的中断状态来实现OS_EXIT_CRITICAL();此方法存在的问题是:有些编译器不够灵敏,不能正确进行出入栈操作而引起严重错误。(3)OS_CRITICAL_METHOD==3,用户可以得到当前处理器的状态字值,并保存在一个C函数的局部变量里,这个变量可以用于恢复PSW。
3.01 任务
Ucosii 最多管理64个任务,但有些被系统保留,所以推荐使用其中的56个,不用前4个和后4个;为了使ucosii管理用户任务,必须在建立任务时,将任务的起始地址与其他参数传递给OSTaskCreate()或OSTaskCreateExt()中的一个。
3.02任务状态 (睡眠态、就绪态、运行态、中断服务态、等待态)
当所有任务都在等待事件的发生或等待延迟时间的结束时,ucosii执行空闲任务OSTaskIdle().
3.03任务控制块(OS_TCB)
一旦任务建立,一个任务控制块OS_TCB就被赋值,任务控制块是一个数据结构,当任务的CPU使用权被剥夺时,任务控制块用来保存该任务的状态,当任务重新得到CPU的使用权时,任务控制块能确保任务从当时被中断的那一点继续执行。OS_TCB全部驻留在RAM中。
3.04就绪表
每个就绪的任务都放在就绪表(ready list)中,就绪表中有两个变量:OSRdyGrp和OSRdyTbl[]. 找出进入就绪态的优先级最高的任务的办法(ucosii提供了一个表OSUnMapTbl[]): y = OSUnMapTbl[OSRdyGrp]; x = OSUnMapTbl[OSRdyTbl[y]]; prio = (y<<3) + x ;
附:若从就绪表的两个变量,通过查找法或直观地看,也可以找到处于就绪态的优先级最高的任务,但是比较麻烦,ucosii加入了算法,通过查表的办法可以直接得到处于就绪态的优先级最高的任务,比较方便。
3.05任务调度
Ucosii总是运行进入就绪态任务中优先级最高的任务。确定哪个任务优先级最高由调度器(SCHEDULER)完成。任务级调度由函数OSSched()完成;中断级调度由另一个函数OSIntExt()完成; OSShed()的所有代码都属于临界代码,为防止寻找就绪态优先级最高的任务时中断服务程序把一个或几个任务的就绪位置位,中断是关掉的。
3.06任务级的任务切换 OS_TASK_SW()
任务切换很简单: 将被挂起任务的处理器寄存器推入堆栈,然后将较高优先级任务的寄存器值从栈中恢复到寄存器中。
3.07给调度器上锁和开锁
OSSchedlock()用于禁止任务调度;OSSchedUnlock() 开锁 ;两个函数必须成对使用。
变量OSLockNesting跟踪OSSchedLock()函数被调用的次数,以允许嵌套的函数包含临界段代码,这段代码其他任务不得干预。Ucosii允许嵌套深度达255层,当OSLockNesting==0时,调度重新得到允许。
调用了OSSchedLock()后,用户程序不得调用可能使当前任务挂起的系统功能函数,若调用了系统会被锁住。
3.08空闲任务 OSTaskIdle()
其永远被设为最低优先级OS_LOWEST_PRIO,且不能被应用软件删除。
空闲任务是永远处于就绪态的,故不要在OSTaskIdleHook()中调用任何可以使任务挂起的PEND函数.
3.09统计任务OSTaskStat()
若将OS_TASK_STAT_EN设为1,这个任务就会建立。一旦得到了允许,OSTaskStat()就会每秒运行1次,计算当前CPU的利用率,将计算的值放在一个有符号8位整数OSCPUsage中,精确度为1%。
若打算使用统计任务,必须在初始化时建立的第一个也是唯一的任务中调用统计任务的初始化函数OSStatInit();换句话说,在调用系统启动函数OSStart()之前,用户初始化代码中必须建立一个任务,在这个任务中调用系统统计初始化函数OSStatInit(),然后再建立应用程序中的其他任务。
3.10 ucosii中的中断
进入中断函数: OSIntEnter() 从中断服务中退出函数OSIntExit()
3.11 时钟节拍
必须在多任务系统启动以后,也就是在调用OSStart()之后,再开启时钟节拍器,换句话说,调用OSStart()之后应该做的第一件事就是初始化定时器中断,通常易犯的错误是将时钟节拍器放在OSInit()之后,OSStart()之前开始。
Ucosii中的时钟节拍服务是通过在中断服务程序中调用OSTimeTick()实现的。
3.12 uosii的初始化 OSInit()
3.13 Ucosii的启动 OSStart()
启动ucosii之前,至少必须建立一个应用任务。
调用OSStart()时,OSStart()从任务就绪表中找出优先级最高的任务的任务控制块;
OSStart()调用高优先级就绪任务启动函数OSStartHighRdy(),将任务栈中保存的值弹回到CPU寄存器中,然后执行一条中断返回指令,中断返回指令强制执行该任务代码。
3.14 获取当前UCOSII的版本号
调用函数OSVersion(),其返回值是版本号乘以100。
第四章 任务管理
任务有两种结构:(1)可以是一个无限的循环,(2)也可以在一次执行完毕后被删除掉。这里的删除并不是真正的删除,而是ucosii不再理会该任务的代码。
4.00建立任务 OSTaskCreate()
任务可以在多任务调度开始前建立,也可以在其他任务的执行过程中建立。
在开始多任务调度OSStart()前,必须建立至少一个任务。
任务不能在中断服务程序(ISR)中建立。
OSTaskCreate( void (*task)(void *pd), void *pdata, OS_STK,*ptos, INT8U prio)
4个参数:
Task 是指向任务代码的指针; pdata是任务开始执行时,传递给任务的参数的指针
Ptos 是分配给任务的堆栈的栈顶指针; prio是分配给任务的优先级。
4.01建立任务 OSTaskCreateExt()
OSTaskCreateExt( void (*task)(void *pd), void *pdata, OS_STK,*ptos, INT8U prio,
INT16U id, OS_STK *pbos, INT32U stk_size, void *pext, INT16U opt)
此函数需要9个参数,前4个同上,后5个参数为:
Id 为要建立的任务创建一个特殊的标志符,目前未用;
pbos 指向任务堆栈栈底的指针,用于堆栈的检验;
stk_size 用于指定堆栈的容量,也是用于堆栈的检验;
pext 指向用户附加的数据域的指针,用来扩展任务的任务控制块OS_TCB
opt 用于设定OSTaskCreateExt()的选项;
402任务堆栈
堆栈必须声明为OS_STK类型,可以静态分配堆栈空间,也可以动态分配堆栈空间,但两种声明都必须放置在函数的外边。
Static OS_STK MyTaskStack[stack_size]; OS_STK MyTaskStack[stack_size];
在调用上边两个任务建立函数时,必须知道处理器的堆栈是递增的还是递减的,因为上边函数有一个参数ptos是要传递栈顶指针的,只有知道递增或递减,才能确定是传&TaskStack[0]还是&TaskStack[TASK_STACK_SIZE -1];
403堆栈检验 OSTaskStkChk()
把需要进行堆栈检验的任务的优先级作为OSTaskStkChk()的参数并调用,可以检验任何任务的堆栈,并不仅仅只能检验正在运行的任务。
404删除任务 OSTaskDel() ----任务将返回并处于休眠状态
405请求删除任务 OSTaskDelReq()
每个任务都会占用一些内存缓冲区或信号量,如果另一个任务试图删除该任务,这些被占用的资源就会因为没有被释放而丢失。需要想办法让拥有这些资源的任务在使用完这些资源后,先释放资源,再删除自己,于是可以用到上边的函数,发出删除任务请求的函数和打算被删除的任务都必须调用这个函数。
406改变任务的优先级OSTaskChangePrio()
不可改变空闲任务的优先级,但可以改变调用本函数的任务或其他任务的优先级。
407挂起任务 OSTaskSuspend()
被挂起的任务只能通过调用OSTaskResume()函数来恢复。任务可以挂起自己或其他任务。
408恢复任务 OSTaskResume()
409获得任务的信息 OSTaskQuery()
此函数获得的是指定任务的任务控制块OS_TCB中内容的拷贝。
第五章 时间管理
Ucosii 要求提供定时器中断(时钟节拍)实现延时和超时控制等功能,它应发生10-100次/秒。 这里介绍5个与时间有关的函数,必须在 OS_CFG.H中使能了才能用。
500任务延时函数 OSTimeDly()
延时长短由指定的时钟节拍数目(0-65535)来确定;调用该函数会使系统进行一次任务调度,去执行下一个优先级最高的任务。任务调用该函数后,一旦规定的时间满了或有其他任务通过调用OSTimeDlyResume()取消了延时,该任务就会立即进入就绪状态。
501 按时分秒延时函数 OSTimeDlyHMSM()
INT8U OSTimeDlyHMSM(INT8U hours,INT8U minutes,INT8U seconds,INT16U milli)
502 恢复延时的任务 OSTimeDlyResume()
INT8U OSTimeDlyResume(INT8U prio) 通过指定要恢复任务的优先级来调用函数。
503 系统时间 OSTimeGet() 和 OSTimeSet()
无论时钟节拍何时发生,ucosii都会将一个32位计数器加一,这个计数器在OSStart()初始化多任务时被置0,并在4294967295个节拍完成一遍时,重新开始计数。
可以通过OSTimeGet()获得计数器当前值,也可以通过OSTimeSet()改变计数器的值
第六章 事件控制块
任务或中断服务程序可以通过事件控制块(ECB)向另外的任务发信号,这里的信号被看成是事件。(1)中断服务程序不能等待事件控制块给它发送信号;(2)处于等待状态状态的任务可以设置等待超时;(3)多个任务同时等待一事件的发生,当事件发生时,只有优先级最高的任务得到该事件并进入就绪状态;
Ucosii通过ucos_ii.h中定义的OS_EVENT数据结构,维护一个事件控制块ECB的所有信息。该结构中除了包含事件本身的定义外,还定义了等待该事件的所有任务列表。 每个信号量、互斥型信号量、消息邮箱、消息队列都应分配到一个事件控制块。
600 将任务置于等待事件的任务列表
Pevent->OSEventGrp |= OSMapTbl[prio>>3];
Pevent->OSEventTbl[prio>>3] |= OSMapTbl[prio&0x07];
601 从等待事件的任务列表中使任务脱离等待状态
清除任务在OSEventTbl[]中的相应位,并且如果其所在组中没有其他处于等待事件的任务,OSEventGrp中的相应位也要清除掉。
602 在等待事件的任务列表中查找优先级最高的任务
和查找处于就绪状态的任务列表一样,通过查OSUnMapTbl[]得出。
603 空余事件控制块链表
事件控制块的总数由应用程序所需要的信号量、互斥型信号量、邮箱、消息队列的总数决定。所有事件控制块ECB被链接成一个单向链表---空余事件控制块链表。
604 初始化一个事件控制块 OS_EventWaitListInit()
当建立一个信号量、邮箱、消息队列时,相应的建立函数OS??Create()通过调用OS_EventWaitListInit(),对事件控制块中的等待任务列表进行初始化。该函数初始化一个空的等待任务列表,初始化完成时,表中没有任何等待事件的任务。函数传递一个指针变量给事件控制块,这个指针变量就是创建信号量、邮箱等时分配的事件控制块指针pevent.
605 使一个任务进入就绪态 OS_EventTaskRdy()
当某个事件发生了,要将等待该事件任务列表中最高优先级任务置于就绪态时,信号量、邮箱等所对应的POST函数调用OS_EventTaskRdy(),以实现该操作。
606 使一个任务进入等待某事件发生状态 OS_EventTaskWait()
当某任务须等待一个事件的发生时,信号量、邮箱等会通过相应的PEND函数调用函数OSEventTaskWait(),使当前任务从就绪任务列表中脱离就绪态,并放到相应事件的事件控制块ECB的等待任务表中。
607 由于等待超时而将任务置为就绪态 OS_EventTO()
如果在预先指定的等待时限内任务等待的事件没有发生,那么OSTimeTick()函数会因为等待超时而将任务的状态置为就绪态。这种情况下,信号量、邮箱等通过PEND函数调用OS_EventTO()函数,以完成这项工作。
第七章 信号量管理
信号量有2部分组成:(1)16位的无符号整型信号量的计数值(0-65535);(2)由等待该信号量的任务组成的等待任务表。
Ucosii提供了6个对信号量进行操作的函数,当OS_CFG.H中OS_SEM_EN设为0时,所有的信号量函数都不能用,若配置为1时,OSSemCreate(),OSSemPend(),OSSemPost()必须有。
700 建立一个信号量 OSSemCreate()
该函数用来建立一个信号量,并对它付初值。中断服务程序不能调用该函数
701 删除一个信号量 OSSemDel()
当OS_CFG.H中的OS_SEM_DEL_EN=1时,该函数才被编译。在删除信号量之前,必须首先删除操作该信号量的所有任务。中断服务程序不能调用该函数。
702 等待一个信号量 OSSemPend()
真正将任务置入睡眠状态的操作在OS_EventTaskWait()函数中执行;由于得不到信号量,当前任务就不能再处于就绪态了,于是调用任务调度函数让下一个处于最高优先级的任务运行,这样调用OSSempend()函数的任务被挂起了,直到信号量出现才能继续运行。中断服务程序不能调用该函数。
703 发出一个信号量 OSSemPost()
因为这个函数使更重要的任务进入了就绪态,故调用这个函数的任务就不能继续执行了。当中断服务子程序调用这个函数时,不会发生任务切换,要等到最外层的中断服务程序调用了OSIntExit()后才能进行。
704 无等待的请求一个信号量 OSSemAccept()
当任务请求一个信号量时,如果该信号量暂时无效,也可以让该任务简单的返回,而不是进入睡眠等待状态。
705 查询一个信号量的当前状态 OSSemQuery()
在应用程序中,随时可以调用函数OSSemQuery()来查询一个信号量的当前状态。该函数有2个参数:(1)指向信号量对应的事件控制块的指针pevent ;(2)指向用于记录信号量信息的数据结构OS_SEM_DATA的指针pdata.
第八章 互斥型信号量管理
任务可以用互斥型信号量实现对共享资源的独占式处理,互斥型信号量也称作mutex,mutex是二值信号量,除了有普通信号量的机制外,还有些其他特性(如降解优先级反转的问题)。
互斥型信号量由3个元素组成:
(1)1个标志,指示mutex是否可以使用(0或1);
(2)1个优先级,准备一旦高优先级的任务需要这个mutex,赋给占有mutex的任务; (3)一个等待该mutex的任务列表。
800 建立一个互斥型信号量 OSMutexCreate()
801 删除一个互斥型信号量 OSMutexDel()
802 等待一个互斥型信号量 OSMutexPend()
803 释放一个互斥型信号量 OSMmtexPost()
804 无等待的获取互斥型信号量(任务不挂起) OSMutexAccept()
805 获取互斥型信号量的当前状态 OSMutexQuery()
这章里变得6个函数和上一章的主要区别就在于: 互斥型信号量主要用于对共享资源进行独占式处理;并且增加了一个用于解决优先级反转的优先级。
第九章 事件标志组管理
事件标志组由2部分组成: (1) 用来保存当前事件组中各事件状态的一些标志位;(2)等待这些标志位置位或清除的任务列表。
与前面的信号量、互斥型信号量一样,也需要通过OS_CFG.H中的事件标志组控制开关量的选择如OS_FLAG_EN 来使用或禁止事件标志组。
900 深入事件标志组
当一个任务开始等待某些事件标志位时,就建立一个OS_FLAG_NODE数据结构。当这些等待的事件标志位发生以后,这个数据结构就会被删除。换句话说:当调用OSFlagPend()时,建立OS_FLAG_NODE.
901 建立一个事件标志组 OSFlagCreate()
902 删除一个事件标志组 OSFlagDel()
903 等待事件标志组的事件标志位 OSFlagPend()
904 置位或清0事件标志组中的事件标志 OSFlagPost()
905 无等待地获得事件标志组中的事件标志 OSFlagAccept()
这个函数和OSFlagPend()非常相似,不同仅在于:如果等待的事件标志没有发生,那么该函数并不停止任务的运行以等待事件标志 ,而是简单的返回一个出错代码。 这个函数可以在中断服务子程序中调用。
906 查询事件标志组的状态 OSFlagQuery()
第十章 消息邮箱管理
消息邮箱是UCOSII的另一种通信机制,可以使一个任务或中断服务程序向另一个任务发送一个指针型变量。该指针指向一个包含了“消息”的特定数据结构。
与前面的信号量、互斥型信号量、事件标志组一样,也需要通过OS_CFG.H文件中配置的常数值来确定是否编译该函数。
应用程序可以使用任意多个邮箱,其最大数目由OS_CFG.H文件中配置常数OS_MAX_ENVENTS设定。
1000 建立一个邮箱 OSMboxCreate()
通过调用用上边的函数建立一个邮箱,并且需要定义指针的初始值。
1001 删除一个邮箱 OSMboxDel()
1002 等待邮箱中的消息 OSMboxPend()
1003 向邮箱发送一则消息 OSMboxPost()
1004 向邮箱发送一则消息 OSMboxPostOpt()
它可以完全替代原有的OSMboxPost(),另外,该函数可以向等待邮箱的所有任务发送消息(广播)。
1005 无等待地从邮箱中得到一则消息 OSMboxAccpet()
这个函数的另一用途是: 清空邮箱中现有的内容。
1006 查询一个邮箱的状态 OSMboxQuery()
1007 用邮箱作为二值信号量
一个邮箱可以被用作二值信号量,首先,在初始化时,将邮箱设置为一个非空指针,这样,一个任务可以调用OSMboxPend()函数请求一个信号量,然后通过调用OSMboxPost()函数释放一个信号量。
1008 用邮箱实现延时,而不使用 OSTimeDly()
邮箱的等待超时功能可以用来模仿OSTimeDly()函数的延时,如果在指定时间段TIMEOUT内没有消息到来,函数继续运行。
第十一章 消息队列管理
可以将消息队列看作是多个消息邮箱组成的数组,只是他们共用一个等待任务列表。
第十二章 内存管理
Ucosii 把连续的大块内存按分区来管理。每个分区中包含整数个大小相同的内存块,利用这种机制,ucosii对malloc()和free()函数进行了改进,使得它们可以得到和释放固定大小的内存块。
1200内存控制块
为便于内存管理,在ucosii中使用内存控制块的数据结构跟踪每一个内存分区,系统中的每个内存分区都有它自己的内存控制块。
1201建立一个内存分区 OSMemCreate()
在程序运行期间,经过多次的内存分配和释放后,同一分区内的各内存块之间的连接顺序会发生很大的变化。
1202 分配一个内存块 OSMemGet()
该函数从已经建立的内存分区中申请一个内存块;函数的唯一参数是指向特定内存分区的指针。 中断服务程序中可以调用这个函数,因为在暂时没有内存块可用的情况下,OSMemGet()不会等待,而是立即返回NULL指针。
1203 释放一个内存块 OSMemPut()
当应用程序不再使用某一内存块时,必须及时地把它释放,并放回到相应的内存分区中,这个操作由上边的函数完成。必须注意: 这个函数并不知道该内存块是属于哪个内存分区的。
1204 查询一个内存分区的状态 OSMemQuery()
通过该函数来查询一个特定内存分区的有关信息:可以知道特定内存分区中内存块的大小,可用内存数目、已用内存数目等信息。所有的信息都放在一个叫OS_MEM_DATA的数据结构中。
1205 使用内存分区
1206 等待内存分区中的一个内存块
在内存分区在暂时没有可用空余内存块时,让一个申请内存块的任务等待也是可行的;但UCOSII本身在内存管理上并不支持这项功能,如果确实需要,则可通过为特定内存分区增加计数型信号量的方法,实现这一功能。