您好,欢迎来到筏尚旅游网。
搜索
您的当前位置:首页uCOSII实时操作系统通信机制之内核分析

uCOSII实时操作系统通信机制之内核分析

来源:筏尚旅游网



μC/OS-II通信机制之内核分析
摘要:本文主要着重对μC/OS-III通信机制的内核分析,研究μC/OS-II内核通信机制的实现方式及实现的技巧,同时分析其中不足之处。

1.引言
μC/OS-II是一种可移植的,可植入ROM的,可裁剪的,抢占式的,实时多任务操作系统内核。它被广泛应用于微处理器、微控制器和数字信号处理器。

uC/OS-II只是一个实时操作系统内核,它仅仅包含了任务调度,任务管理,时间管理,内存管理和任务间的通信和同步等基本功能。没有提供输入输出管理,文件系统,网络等额外的服务。但由于uC/OS-II良好的可扩展性和源码开放,这些非必须的功能完全可以由用户自己根据需要分别实现。

μC/OS-II这款操作系统内核简单易学,通过对其源代码的分析,可以加深我们对操作系统内核的理解,为学习linux等大型操作系统打下基础。

2.操作系统中任务间的通信机制
内核中多个任务之间不可避免的存在相互协同的关系,来完成一定的内核功能。这种协同最直观的就是任务间相互通信。。包括VxWorks等所有的嵌入式操作系统一般都会提供许多任务间通信的方法,通常包括:
1)共享内存,数据的简单共享。

2)信号量,基本的互斥和同步。

3)消息队列和管道,同一CPU内多任务间消息传递。

4Socket和远程调用,任务间透明的网络通信。

μC/OS-II中设计了五种通讯机制,或者说是同步机制,分别是信号量(semaphore)体(mutualexclusion semaphore)(5Signals,用于异常处理。,事件组(eventflag),邮箱(messagebox)和队列,互斥

queue

析论述。

我们知道通信机制是发生在任务之间的,换句话说任务与通信机制存在着关联。

在内核又是如何处理这种关联呢?通信机制具体来说是信号量,互斥量,邮箱,队列等。通信机制协调的关系一般是针对两个以上的任务,比如说当两个任务互斥的访问共享资源,就需要一个互斥量,这个互斥量就关联着这两个任务。同样的道理,其他通信机制也是关联着两个以上的任务。那么设计内核时就要考虑如何将他们的关系有效协调地统一管理起来。

在内核中是通过一个事件控制块来实现通信机制与任务的联系。这里所说的事件就是指任务间进行通信时传递信号的统称。这里其实是利用到了一种抽象的思想,即将各种通信机制的共同点抽象出来,于是内核就设计了一个事件控制块的数据结构,这个数据结构是所有通信机制都会用到的,这样的设计就节省了不少代码,因为有共同点意味着有重复代码。

既然知道了事件控制块是实现通信机制代码共享的关键,那么下面我们就来分析下它的数据结构。





typedefstruct
{
INT8UOSEventType; //事件类型
INT8UOSEventGrp; //等待任务所在的组
INT16UOSEventCnt; //当事件是信号量时的计数器
void*OSEventPtr; //指向消息或消息队列的指针
INT8UOSEventTbl[OS_EVENT_TBL_SIZE]; //等待任务列表
}OS_EVENT;

第一个变量是事件类型,它可以是信号量,互斥型信号量,邮箱或是消息队列中的一种。OSEventPtr是一个泛型指针,只在所定义的事件是邮箱或者消息队列时才会用到,用来指向一个消息,指针类型设计成泛型的,这是一个设计技巧,因为还不知道具体的消息是什么样的数据结构类型。OSEventGrpOSEventTbl[OS_EVENT_TBL_SIZE]用来控制等待某事件的任务,换句话说是等待某事件的任务列表信息。通过查询这个列表信息可以知道有哪些任务在等待这个事件。OSEventCnt是计数器,当事件定义的是信号量或者是互斥型信号量时会用到。

从这个事件控制块的数据结构我们可以看出,它是具有抽象性质的,即可以定义成不同的事件类型,可根据具体情况进行不同类型的定义,它具有一定的通用性,又具有一定的针对性(如OSEventPtr只对邮箱或者消息队列有用)。也就是说这样的设计抽象的程序不够,存在着一定的数据冗余,比如说当事件定义成信号量时,根本用不到OSEventPtr这个变量,

下面分析下事件控制块是如何管理其等待的任务。 首先等待一个事件的任务可能是多个,而且任务加入等待列队的时间不一样,那么又
它就相当是一个冗余变量,这是内核设计存在的一个缺陷。

64 个任务的信息。在内核中,所有任务优先级被分成8组(每组8个优先级),分别
对应于OSEventGrp8 位,这个变量被定义成8位的数据类型,每一位都用来指示是否有
任务在等待事件发生的状态。当某组中有有任务处于等待事件的状态时,OSEventGrp对应
的位就会被置1。相应地,该任务在OSEventTbl[]中对应位也被置1

那么实现一个任务置于等待事件的任务列表中或者从等待事件的任务列表中使任务脱离等待状态或者在等待事件列表中查找优先级最高的任务的算法又怎么样的呢?

将一个任务插入到等待事件的任务列表中

pevent-> OSEventGrp |= OSMapTbl[prio >> 3];

pevent-> OSEventTbl[prio>>3] = OSMapTbl[prio & 0x07];

从等待事件的任务列表中使任务脱离等待状态:

if ((pevent -> OSEventTbl[prio >>3] &= ~OSMapTbl[prio &0x07]) == 0)
{

pevent -> OSEventGrp &= ~OSMapTbl[prio >>3]; }




在等待事件的任务列表中查找优先级最高的任务:

y= OSUnMapTbl[pevent -> OSEventGrp];

x= OSUnMapTbl[pevent -> OSEventTbl[y];

prio= (y << 3) + x;

从上述代码中可以看到用到了两个数组,一个是OSMapTbl[],一个是OSUnMapTbl[]

它们是已经定义的映射表,如OSMapTbl[]的内容是{1248163264128},

个数组出现的目的是为了更方便的置位。说白点,使用OSMapTbl[index]的作用

是更方便的把某个数值的第index位置1

从上述的代码中我们可以看出算法的原理是:任务的优先级的最低3位决

定了该任务在相应的OSEventTbl[]中的位置,紧接着的高3位则决定了该任务优先级在

OSEventTbl[]中的字节索引。

处理事件与任务的关系时需要处理的三个共同问题:使一个任务进入就绪态,使一个

任务进入等待某事件发生的状态,由于等待超时而将任务置为就绪态。分别对应于三个函数。

这样的处理可以避免大量重复的代码,因为这些处理是所有通信机制所必须的,没有必要为

下面提出一个问题,既然事件控制块是所有通信机制的抽象,那么具体的通信机制又
每个通信机制就写一个独特的函数来处理。

块,

行赋值,使之成为表示一个信号量的数据结构。并将些事件控制块进行初始化,初始化的主

要工作是将事件的任务等待列表清空为0。并把创建好的事件控制块指针返回。这个返回的

事件控制块指针就是一个信号量,等待信号量与发送信号量都是用到这个指针的。

那么某个任务发送一个创建好的信号量又是如何实现的?信号量在创建时会有个计数

器,这个计数器标志着可用资源数,创建信号量时可以初始为0。当一个任务发送一个信号

量时,需要考虑几个问题:发送的信号量是否真的存在?有没有任务在等待这个信号量?信

号量的计数器值有没有超过范围?当发送的信号不存在时或者信号量的计数器的值超过范

围要直接返回一个错误代码。如果有任务在等待这个信号量,那么就让这个任务等待列表中

最高优先级的任务马上进入就绪态准备运行。计数器不用加1,因为资源已经消耗了(有任

务得到信号量进入就绪态)。如果没有任务在等待这个信号量,那么计数个器值加1,表示

又多一个信号量可用。

而任务等待一个信号量的实现方式又是如何设计的呢?内核支持超时等待机制,即可

以限定等待的时间,在限定的时间内没有收到信号量时任务会自动进入就绪态准备运行。等

待一个信号量本质上就判断一个信号量的计数值是否大于0,如果大于0说明信号量可用,

等待的信号量已经到达,任务可以执行后面的程序,然后还要把计数值减1,表示消耗了一




个信号量。如果计数值为0(等待的信号量还没有到),那么任务要进入休眠态,实现的方法是将限定等待的时间值任务赋给任务的延时值。

任务控制块的数据结构中有一个变量是延时值,用来控制休眠的时间,改变这个延时值就相当于是改变这个任务的休眠时间,一般处于就绪态的任务延时值是为0的。任务进入就绪态后就重新进行任务的调度。

当这个任务从休眠态回复到运行态时有两种可能:一种是超时了,一种是在限定的时间内得到了一个信号量,那么在恢复运行态时就要进行一个判断。那现在提出一个问题是:如何区分这两种情况呢?内核是作何处理的呢?

原来任务控制块中有一个变量是状态变量,这个变量标志着任务是处于何种状态,是在等待一个信号量还是在已经得到了一个信号量。任务在休眠前这个变量表示的是任务处于等待一个信号量的状态。如果等待超时后还没有得到信号量,那么这个状态变量是没有发生变化的,任务恢复运行时可以判断这个变量,如果表示处于等待一个信号量的状态,那么就意味着等待超时了,则任务就要进入就绪态准备运行。如果任务在进入休眠态期间,有其他任务释放信号量时,就会发现有任务在等待这个信号量,并将这个任务处于就绪态准备运行,同时改变这个任务的状态变量,让它表示这个任务已经得到信号量了。任务恢复运行时就会知道是得到了信号量,然后可以处理后面的内容。

这个有点像面向对象的思想,如果抽象类一般是父类,其子类就是具体实现的类。创建一个子类后返回的指针值可以用抽象类的指针指向它。创建了一个信号量后返回 通过以上分析我们可以很清楚的知道事件控制块是如何与具体的通信机制进行了结合。

是一个信号量。

下面对消息邮箱的设计进行一个说明,进一步说明内核中是如何将事件控制块与具体通信机制进行结合的。

首先要介绍的是内核是如何实现创建一个邮箱的。邮箱意味着传递的是一个消息,在这个实时操作系统中邮箱只允许存放一个消息。消息的类型可以自定义。创建邮箱函数需要传递进来一个消息指针,类型是泛型的。创建一个事件控制块(在内核中是从空闲的事件控制块的链表中取出的),然后对这个事件控制块的数据变量初始化,使之具有邮箱的特征,这与信号量的创建有点类似。传递进来的消息指针也会保存在事件控制块中,
最后把这个事件控制块的指针返回。

向邮箱发送一则消息的实现方式。向邮箱发送一则消息的函数要两个参数,一个是定义为邮箱的事件控制块指针,一个是要发送的消息的指针。发送要要先进行判断有没有任务在等待这个邮箱中的消息,如果有任务在等待这个邮箱里的消息,则将等待列表中优先级最高的任务置于就绪态。并重新调度任务。如果还没有任务在等待这个邮箱里的消息,那么下一步就是要判断这个邮箱里有没有存有消息,如果已经存有消息了,那么就要返回一个表示消息已满的错误代码,因为邮箱只支持存放一个消息。如果邮箱是空的,那么就将要发送的消息的指针存放在邮箱中,即赋给事件控制块里的一个表示消息的指针变量。




等待邮箱里消息的实现方式。邮箱等待也支持等待超时机制,这个机制与前文所述的

信号量等待超时机制相类似。判断邮箱中是否有消息的方法很简单,只要判断下事件控制块

中指向消息的指针变量是不是为0,如果为0说明消息是空的,还没有等到消息,如果是非

空的,说明消息已经到了,将此消息返回,并把邮箱中的消息清空,清空的方法也很简单,

只需将消息指针指向0即可。如果消息为空,那么任务就要进入休眠态,这个机制与信号量

的机制一样,在此不作过多论述。当任务从休眠态中恢复运行时又再进行一次判断看消息是

否为空,如果不为空说明在休眠态期间邮箱已经有任务往里面存放消息了。如果为消息为空,

说明是等待超时了,要将任务置于就绪态。





因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- efsc.cn 版权所有

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务