资源描述
单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,第,4,章,任务的同步与通信,系统中的多个任务在运行时,经常需要互相无冲突地访问同一个共享资源,或者需要互相支持和依赖,甚至有时还要互相加以必要的限制和制约,才保证任务的顺利运行。因此,操作系统必须具有对任务的运行进行协调的能力,从而使任务之间可以无冲突、流畅地同步运行,而不致导致灾难性的后果。,与人们依靠通信来互相沟通,从而使人际关系和谐、工作顺利的做法一样,计算机系统是依靠任务之间的良好通信来保证任务与任务的同步的。,例如,两个任务:任务,A,和任务,B,,,它们需要通过访问同一个数据缓冲区合作完成一项工作,任务,A,负责向缓冲区写入数据,任务,B,负责从缓冲区读取该数据。显然,当任务,A,还未向缓冲区写入数据时(缓冲区为空时),任务,B,因不能从缓冲区得到有效数据而应该处于等待状态,只有等任务,A,向缓冲区写入了数据之后,才应该通知任务,B,去取数据。,例如,任务,A,和任务,B,共享一台打印机,如果系统已经把打印机分配给了任务,A,,,则任务,B,因不能获得打印机的使用权而应该处于等待状态,只有当任务,A,把打印机释放后,系统才能唤醒任务,B,使其获得打印机的使用权。如果这两个任务不这样做,那么也会造成极大的混乱,。,总之,多个任务共享同一资源或有工作顺序要求时,在正式工作之前要互相打招呼,。,黄宏:别走啊!,宋丹丹:我自己的腿,我爱走就走,你管不着!,黄宏:腿是你自己的,但手是咱俩的呀!,事件,任务间的同步依赖于任务间的通信。在,C/OS-II,中,是使用信号量、邮箱(消息邮箱)和消息队列这些被称作事件的中间环节来实现任务之间的通信的,。,宋丹丹,黄宏,一个简单的信号量,1/0,收信方,发信方,共享资源,事件控制块,为了把描述事件的数据结构统一起来,,C/OS-II,使用叫做事件控制块,ECB,的数据结构来描述诸如信号量、邮箱(消息邮箱)和消息队列这些事件。事件控制块中包含包括等待任务表在内的所有有关事件的数据,typedef,struct,INT8U,OSEventType,;/,事件的类型,INT16U,OSEventCnt,;/,信号量计数器,void*,OSEventPtr,;/,消息或消息队列的指针,INT8U,OSEventGrp,;/,等待事件的任务组,INT8U,OSEventTblOS_EVENT_TBL_SIZE,;/,任务等待表,OS_EVENT;,把一个任务置于等待状态要调用,OS_EventTaskWait,(),函数。该函数的原型为:,void,OS_EventTaskWait,(,OS_EVENT*,pevent,/,事件控制块的指针,),;,函数,OS_EventTaskWait,(),,,将在任务调用函数,OSPend,(),请求一个事件时,被,OSPend,(),所调用。,如果一个正在等待的任务具备了可以运行的条件,那么就要使它进入就绪状态。这时要调用,OS_EventTaskRdy,(),函数。该函数的作用就是把调用这个函数的任务在任务等待表中的位置清,0,(解除等待状态)后,再把任务在任务就绪表中对应的位置,1,,然后引发一次任务调度。,OS_EventTaskRdy,(),函数的原型为:,INT8U,OS_EventTaskRdy,(,OS_EVENT*,pevent,/,事件控制块的指针,void*,msg,/,未使用,INT8U,msk,/,清除,TCB,状态标志掩码,),;,函数,OS_EventTaskRdy,(),将在任务调用函数,OSPost(),发送一个事件时,被函数,OSPost(),所调用。,如果一个正在等待事件的任务已经超过了等待的时间,却仍因为没有获取事件等原因而未具备可以运行的条件,却又要使它进入就绪状态,这时要调用,OS_EventTO,(),函数。,OS_EventTO,(),函数的原型为:,void,OS_EventTO,(,OS_EVENT*,pevent,/,事件控制块的指针,),;,函数,OS_EventTO,(),将在任务调用,OSPend,(),请求一个事件时,被函数,OSPend,(),所调用。,空事件控制块链表,在,C/OS-II,初始化时,系统会在初始化函数,OSInit,(),中按应用程序使用事件的总数,OS_MAX_EVENTS,(,在文件,OS_CFG.H,中定义),创建,OS_MAX_EVENTS,个空事件控制块并借用成员,OSEventPtr,作为链接指针,把这些空事件控制块链接成一个单向链表。由于链表中的所有控制块尚未与具体事件相关联,故该链表叫做空事件控制块链表。以后,每当应用程序创建一个事件时,系统就会从链表中取出一个空事件控制块,并对它进行初始化以描述该事件。而当应用程序删除一个事件时,就会将该事件的控制块归还给空事件控制块链表,信号量及其操作,在使用信号量之前,应用程序必须调用函数,OSSemCreate,(),来创建一个信号量,,OSSemCreate,(),的原型为:,OS_EVENT *,OSSemCreate,(,INT16U,cnt,/,信号量计数器初值,),;,函数的返回值为已创建的信号量的指针,。,任务通过调用函数,OSSemPend,(),请求信号量,函数,OSSemPend,(),的原型如下:,void,OSSemPend,(OS_EVENT*,pevent,/,信号量的指针,INT16U timeout,/,等待时限,INT8U*err),;,/,错误信息,参数,pevent,是被请求信号量的指针。,为防止任务因得不到信号量而处于长期的等待状态,函数,OSSemPend,允许用参数,timeout,设置一个等待时间的限制,当任务等待的时间超过,timeout,时可以结束等待状态而进入就绪状态。如果参数,timeout,被设置为,0,,则表明任务的等待时间为无限长。,任务获得信号量,并在访问共享资源结束以后,必须要释放信号量,释放信号量也叫做发送信号量,发送信号量需调用函数,OSSemPost,(),。,OSSemPost,(),函数在对信号量的计数器操作之前,首先要检查是否还有等待该信号量的任务。如果没有,就把信号量计数器,OSEventCnt,加一;如果有,则调用调度器,OS_Sched,(),去运行等待任务中优先级别最高的任务。,函数,OSSemPost,(),的原型为:,INT8U,OSSemPost,(,OS_EVENT*,pevent,/,信号量的指针,),;,调用函数成功后,函数返回值为,OS_ON_ERR,,,否则会根据具体错误返回,OS_ERR_EVENT_TYPE,、,OS_SEM_OVF,。,应用程序如果不需要某个信号量了,那么可以调用函数,OSSemDel,(),来删除该信号量,这个函数的原型为:,OS_EVENT *,OSSemDel,(,OS_EVENT*,pevent,/,信号量的指针,INT8U opt,/,删除条件选项,INT8U*err/,错误信息,),;,互斥型信号量和任务优先级反转,在可剥夺型内核中,当任务以独占方式使用共享资源时,会出现低优先级任务先于高优先级任务而被运行的现象,这种现象叫做任务优先级反转,。在一般情况下是不允许出现这种任务优先级反转现象的,下面就对优先级的反转现象做一个详细的分析,以期找出原因及解决方法。,图,4-15,描述了,A,、,B,、,C,三个任务的运行情况。其中,任务,A,的优先级别高于任务,B,,,任务,B,的优先级别高于任务,C,。,任务,A,和任务,C,都要使用同一个共享资源,S,,,而用于保护该资源的信号量在同一时间只能允许一个任务以独占的方式对该资源进行访问,即这个信号量是一个互斥型信号量。,通过例子可以发现,使用信号量的任务是否能够运行是受任务的优先级别和是否占用信号量两个条件约束的,而信号量的约束高于优先级别的约束。于是当出现低优先级别的任务与高优先级别的任务使用同一个信号量,而系统中还存有别的中等优先级别的任务时,如果低优先级别的任务先获得了信号量,就会使高级别的任务处于等待状态,而那些不使用该信号量的中等级别的任务却可以剥夺低优先级别的任务的,CPU,使用权而先于高优先级别的任务而运行了。,解决问题的办法之一,是使获得信号量任务的优先级别在使用共享资源期间暂时提升到所有任务最高优先级的高一个级别上,以使该任务不被其他的任务所打断,从而能尽快地使用完共享资源并释放信号量,然后在释放了信号量之后再恢复该任务原来的优先级别。,互斥型信号量,在描述互斥型信号量的事件控制块中,除了成员,OSEventType,要赋以常数,OS_EVENT_TYPE_MUTEX,以表明这是一个互斥型信号量和仍然没有使用成员,OSEventPtr,之外,成员,OSEventCnt,被分成了低位和高位两部分:低位用来存放信号值(该值为,0 xFF,时,信号为有效,否则信号为无效),高位用来存放为了避免出现优先级反转现象而要提升的优先级别,prio,。,创建互斥型信号量需要调用函数,OSMutexCreate,(),。,函数,OSMutexCreate,(),的原型如下:,OS_EVENT *,OSMutexCreate,(,INT8U,prio,/,优先级别,INT8U*err/,错误信息,),;,函数,OSMutexCreate,(),从空事件控制块链表获取一个事件控制块,把成员,OSEventType,赋以常数,OS_EVENT_TYPE_MUTEX,以表明这是一个互斥型信号量,然后再把成员,OSEventCnt,的高,8,位赋以,prio,(,欲提升的优先级别),低,8,位赋以常数,OS_MUTEX_AVAILABLE,(,该常数值为,0 xFFFF,),的低,8,位(,0 xFF,),以表明信号量尚未被任何任务所占用,处于有效状态。,当任务需要访问一个独占式共享资源时,就要调用函数,OSMutexPend,(),来请求管理这个资源的互斥型信号量,如果信号量有信号(,OSEventCnt,的低,8,位为,0 xFF,),,则意味着目前尚无任务占用资源,于是任务可以继续运行并对该资源进行访问,否则就进入等待状态,直至占用这个资源的其他任务释放了该信号量。,函数,OSMutexPend,(),的原型为:,void,OSMutexPend,(,OS_EVENT*,pevent,/,互斥型信号量指针,INT16U timeout,/,等待时限,INT8U*err/,错误信息,),;,任务可以通过调用函数,OSMutexPost,(),发送一个互斥型信号量,这个函数的原型为:,INT8U,OSMutexPost,(,OS_EVENT*,pevent,/,互斥型信号量指针,),;,消息邮箱及其操作,如果把数据缓冲区的指针赋给一个事件控制块的成员,OSEventPrt,,,同时使事件控制块的成员,OSEventType,为常数,OS_EVENT_TYPE_MBOX,,,则该事件控制块就叫做消息邮箱,消息邮箱是在两个需要通信的任务之间通过传递数据缓冲区指针的方法来通信的。,创建邮箱需要调用函数,OSMboxCreate,(),,,这个函数的原型为:,OS_EVENT *,OSMboxCreate,(,void*,msg,/,消息指针,),;,函数中的参数,msg,为消息的指针,函数的返回值为消息邮箱的指针。,调用函数,OSMboxCreate,(),需先定义,msg,的初始值。在一般的情况下,这个初始值为,NULL,;,但也可以事先定义一个邮箱,然后把这个邮箱的指针作为参数传递到函数,OSMboxCreate,(),中,使之一开始就指向一个邮箱。,任务可以通过调用函数,OSMboxPost,(),向消息邮箱发送消息,这个函数的原型为:,INT8U,OSMboxPost,(,OS_EVENT*,pevent,/,消息邮箱指针,void*,msg,/,消息指针,),;,当一个任务请求邮箱时需要调用函数,OSMboxPend,(),,,这个函数的主要作用就是查看邮箱指针,OSEventPtr,是否为,NULL,,,如果不是,NULL,就把邮箱中的消息指针返回给调用函数的任务,同时用,OS_NO_ERR,通过函数的参数,err,通知任务获取消息成功;如果邮箱指针,OSEventPtr,是,NULL,,,则使任务进入等待状态,并引发一次任务调度。,函数,OSMboxPend,(),的原型为:,void *,OSMboxPend,(,OS_EVENT*,pevent,/,请求消息邮箱指针,INT16U timeout,/,等待时限,INT8U*err/,错误信息,),;,消息队列及其操作,使用消息队列可以在任务之间传递多条消息。消息队列由三个部分组成:事件控制块、消息队列和消息。,当把事件控制块成员,OSEventType,的值置为,OS_EVENT_TYPE_Q,时,该事件控制块描述的就是一个消息队列。,消息队列的数据结构如图,4-21,所示。从图中可以看到,消息队列相当于一个共用一个任务等待列表的消息邮箱数组,事件控制块成员,OSEventPtr,指向了一个叫做队列控制块(,OS_Q,),的结构,该结构管理了一个数组,MsgTbl,,,该数组中的元素都是一些指向消息的指针。,其中,可以移动的指针为,OSQIn,和,OSQOut,,,而指针,OSQStart,和,OSQEnd,只是一个标志(常指针)。当可移动的指针,OSQIn,或,OSQOut,移动到数组末尾,也就是与,OSQEnd,相等时,可移动的指针将会被调整到数组的起始位置,OSQStart,。,也就是说,从效果上来看,指针,OSQEnd,与,OSQStart,等值。于是,这个由消息指针构成的数组就头尾衔接起来形成了一个如图所示的循环的队列。,为了对图所示的消息指针数组进行有效的管理,,C/OS-II,把消息指针数组的基本参数都记录在一个叫做队列控制块的结构中,队列控制块的结构如下:,typedef,struct,os_q,struct,os_q,*,OSQPtr,;,void*,OSQStart,;,void*,OSQEnd,;,void*,OSQIn,;,void*,OSQOut,;,INT16U,OSQSize,;,INT16U,OSQEntries,;,OS_Q;,在,C/OS-II,初始化时,系统将按文件,OS_CFG.H,中的配置常数,OS_MAX_QS,定义,OS_MAX_QS,个队列控制块,并用队列控制块中的指针,OSQPtr,将所有队列控制块链接为链表。由于这时还没有使用它们,故这个链表叫做空队列控制块链表,创建一个消息队列首先需要定义一指针数组,然后把各个消息数据缓冲区的首地址存入这个数组中,然后再调用函数,OSQCreate,(),来创建消息队列。创建消息队列函数,OSQCreate,(),的原型为:,OS_EVENT,OSQCreate,(,void*start,/,指针数组的地址,INT16U size/,数组长度,);,请求消息队列的目的是为了从消息队列中获取消息。任务请求消息队列需要调用函数,OSQPend,(),,,该函数的原型为:,void*,OSQPend,(,OS_EVENT*,pevent,/,所请求的消息队列的指针,INT16U timeout,/,等待时限,INT8U*err/,错误信息,);,任务需要通过调用函数,OSQPost,(),或,OSQPostFront,(),来向消息队列发送消息。函数,OSQPost,(),以,FIFO,(,先进先出)的方式组织消息队列,函数,OSQPostFront,(),以,LIFO,(,后进先出)的方式组织消息队列。这两个函数的原型分别为:,INT8U,OSQPost,(,OS_EVENT*,pevent,/,消息队列的指针,void*,msg,/,消息指针,);,和,INT8U,OSQPost,(,OS_EVENT*,pevent,/,消息队列的指针,void*,msg,/,消息指针,);,函数中的参数,msg,为待发消息的指针。,信号量集,在实际应用中,任务常常需要与多个事件同步,即要根据多个信号量组合作用的结果来决定任务的运行方式。,C/OS-II,为了实现多个信号量组合的功能定义了一种特殊的数据结构,信号量集。,信号量集所能管理的信号量都是一些二值信号,所有信号量集实质上是一种可以对多个输入的逻辑信号进行基本逻辑运算的组合逻辑,其示意图如图,5-1,所示,信号量集的标志组,不同于信号量、消息邮箱、消息队列等事件,,C/OS-II,不使用事件控制块来描述信号量集,而使用了一个叫做标志组的结构,OS_FLAG_GRP,。,OS_FLAG_GRP,结构如下:,typedef,struct,INT8U,OSFlagType,;/,识别是否为信号量集的标志,void*,OSFlagWaitList,;/,指向等待任务链表的指针,OS_FLAGS,OSFlagFlags,;/,所有信号列表,OS_FLAG_GRP;,成员,OSFlagWaitList,是一个指针,当一个信号量集被创建后,这个指针指向了这个信号量集的等待任务链表。,等待任务链表,与其他前面介绍过的事件不同,信号量集用一个双向链表来组织等待任务,每一个等待任务都是该链表中的一个节点(,Node,)。,标志组,OS_FLAG_GRP,的成员,OSFlagWaitList,就指向了信号量集的这个等待任务链表。,等待任务链表节点,OS_FLAG_NODE,的结构如下:,typedef,struct,void *,OSFlagNodeNext,;/,指向下一个节点的指针,void *,OSFlagNodePrev,;/,指向前一个节点的指针,void*,OSFlagNodeTCB,;/,指向对应任务控制块的指针,void*,OSFlagNodeFlagGrp,;/,反向指向信号量集的指针,OS_FLAGS,OSFlagNodeFlags,;/,信号过滤器,INT8U,OSFlagNodeWaitType,;/,定义逻辑运算关系的数据,OS_FLAG_NODE;,给等待任务链表添加节点的函数为,OS_FlagBlock,(),,,这个函数的原型为:,static void,OS_FlagBlock,(,OS_FLAG_GRP*,pgrp,/,信号量集指针,OS_FLAG_NODE*,pnode,/,待添加的等待任务节点指针,OS_FLAGS flags,/,指定等待信号的数据,INT8U wait_type,/,信号与等待任务之间的逻辑,INT16U timeout/,等待时限,),;,这个函数将在请求信号量集函数,OSFlagPend,(),中被调用。,从等待任务链表中删除一个节点的函数为,OS_FlagUnlink,(),,,这个函数的原型为:,void,OS_FlagUnlink,(OS_FLAG_NODE*,pnode,),;,这个函数将在发送信号量集函数,OSFlagPost,(),中被调用。,信号量集的操作,任务可以通过调用函数,OSFlagCreate,(),来创建一个信号量集。,OSFlagCreate,(),的函数原型为:,OS_FLAG_GRP *,OSFlagCreate,(,OS_FLAGS flags,/,信号的初始值,INT8U*err/,错误信息,),;,任务可以通过调用函数,OSFlagPend,(),请求一个信号量集,,OSFlagPend,(),函数的原型为:,OS_FLAGS,OSFlagPend,(,OS_FLAG_GRP*,pgrp,/,所请求的信号量集指针,OS_FLAGS flags,/,滤波器,INT8U wait_type,/,逻辑运算类型,INT16U timeout,/,等待时限,INT8U*err/,错误信息,),;,任务可以通过调用函数,OSFlagPost,(),向信号量集发信号,,OSFlagPost,(),函数的原型为:,OS_FLAGS,OSFlagPost,(,OS_FLAG_GRP*,pgrp,/,信号量集指针,OS_FLAGS flags,/,选择所要发送的信号,INT8U opt,/,信号有效的选项,INT8U*err/,错误信息,),;,所谓任务向信号量集发信号,就是对信号量集标志组中的信号进行置“,1”,(置位)或置“,0”,(复位)的操作。至于对信号量集中的哪些信号进行操作,用函数中的参数,flags,来指定;对指定的信号是置“,1”,还是置“,0”,,用函数中的参数,opt,来指定(,opt=OS_FLAG_SET,为置“,1”,操作;,opt=OS_FLAG_CLR,为置“,0”,操作)。,第,6,章 内存的,动态分配,应用程序在运行中为了某种特殊需要,经常需要临时获得一些内存空间,因此作为一个比较完善的操作系统必须具有动态分配内存的能力。,能否合理、有效地对内存储器进行分配和管理,是衡量一个操作系统品质的指标之一。特别地对于实时操作系统来说,还应该保证系统在动态分配内存时,它的执行时间必须是可确定的。,C/OS-II,改进了,ANSI C,用来动态分配和释放内存的,malloc,(),和,free(),函数,使它们可以对大小固定的内存块进行操作,从而使,malloc,(),和,free(),函数的执行时间成为可确定的,满足了实时操作系统的要求。,内存控制块,C/OS-II,对内存进行两级管理,即把一个大片连续的内存空间分成了若干个分区,每个分区又分成了若干个大小相等的内存块来进行管理。操作系统以分区为单位来管理动态内存,而任务以内存块为单位来获得和释放动态内存。内存分区及内存块的使用情况则由表,内存控制块,来记录。,本节首先介绍内存分区和分区中的内存块,然后再介绍内存控制块。,可动态分配内存的划分,应用程序如果要使用动态内存的话,则要首先在内存中划分出可以进行动态分配的区域,这个划分出来区域叫做内存分区,每个分区要包含若干个内存块。,C/OS-II,要求同一个分区中的内存块的字节数必须相等,而且每个分区与该分区的内存块的数据类型必须相同。,在内存中划分一个内存分区与内存块的方法非常简单,只要定义一个二维数组就可以了,其中的每个一维数组就是一个内存块。例如,定义一个用来存储,INT16U,类型数据,有,10,个内存块,每个内存块长度为,10,的内存分区的代码如下:,INT16U IntMemBuf1010;,需要注意的是,上面这个定义只是在内存中划分出了分区及内存块的区域,还不是一个真正的可以动态分配的内存区,如图,6-1(a),所示。只有当把内存控制块与分区关联起来之后,系统才能对其进行相应的管理和控制,它才能是一个真正的动态内存区,为了使系统能够感知和有效地管理内存分区,,C/OS-II,给每个内存分区定义了一个叫做内存控制块(,OS_MEM,),的数据结构。系统就用这个内存控制块来记录和跟踪每一个内存分区的状态。内存控制块的结构如下:,typedef,struct,void *,OSMemAddr,;/,内存分区的指针,void *,OSMemFreeList,;/,内存控制块链表的指针,INT32U,OSMemBlkSize,;/,内存块的长度,INT32U,OSMemNBlks,;/,分区内内存块的数目,INT32U,OSMemNFree,;/,分区内当前可分配的内存块的数目,OS_MEM;,当应用程序调用函数,OSMemCreate,(),建立了一个内存分区之后,内存控制块与内存分区和内存块之间的关系如图,动态内存的管理,划分了欲使用的分区和内存块之后,应用程序可以通过调用函数,OSMemCreate,(),来建立一个内存分区,,OSMemCreate,(),函数的原型为:,OS_MEM*,OSMemCreate,(,void*,addr,/,内存分区的起始地址,INT32U,nblks,/,分区中内存块的数目,INT32U,blksize,/,每个内存块的字节数,INT8U*err/,错误信息,);,在应用程序需要一个内存块时,应用程序可以通过调用函数,OSMemGet,(),向某内存分区请求获得一个内存块,,OSMemGet,(),函数的原型为:,void *,OSMemGet,(,OS_MEM*,pmem,/,内存分区的指针,INT8U*err/,错误信息,),;,当应用程序不再使用一个内存块时,必须要及时地将它释放。应用程序通过调用函数,OSMemPut,(),来释放一个内存块,,OSMemPut,(),函数的原型为:,INT8U,OSMemPut,(,OS_MEM *,pmem,/,内存块所属内存分区的指针,void*,pblk,/,待释放内存块的指针,),;,应用程序可以通过调用函数,OSMemQuery,(),来查询一个分区目前的状态信息,函数函数,OSMemQuery,(),的原型为:,INT8U,OSMemQuery,(,OS_MEM*,pmem,/,待查询的内存控制块的指针,OS_MEM_DATA*,pdata,/,存放分区状态信息的结构的指针,),其中参数,pdata,是一个,OS_MEM_DATA,类型的结构,该结构的定义如下:,typedef,struct,void *,OSAddr,;/,内存分区的指针,void *,OSFreeList,;/,分区内内存块链表的头指针,INT32U,OSBlkSize,;/,内存块的长度,INT32U,OSNBlks,;/,分区内内存块的数目,INT32U,OSNFree,;/,分区内空闲内存块的数目,INT32U,OSNUsed,;/,已被分配的内存块数目,OS_MEM_DATA;,
展开阅读全文