ucos在s3c2410上运行过程整体剖析-从main函数到UCOS初始化完毕(5)
- UID
- 1029342
- 性别
- 男
|
ucos在s3c2410上运行过程整体剖析-从main函数到UCOS初始化完毕(5)
首先,我们要先介绍一下OSRdyGrp和OSRdyTbl这两个东西,OSRdyGrp这个是一个无符号的八位数,OSRdyTbl是一个数组,是由八个八位无符号数组成的一个数组。这两个东东是干什么的那,前面说了,我们要知道系统中有哪些任务处于就绪态,那就需要记忆,要记忆就需要存储空间。那具体要记忆哪些信息那,如果一个任务被创建,我们既需要知道这个任务是否处于就绪态,还要知道这个就绪的任务优先级是多少。Ucos支持64个优先级,我们可以定义一个数组xx[64]来记录那个任务就绪了,比如优先级为15的任务就绪了,我们可以把xx[15]置一表示优先级为15的任务就绪了,置0表示任务未就绪。当我们有很多任务就绪时,这个数组中就有很多被置一,查找优先级最高的(在ucos中就是数值最小的)任务,那一种方法就是从数组的下标0开始扫描,当第一次遇到1时的数组下标,这个优先级的任务就是当前最高的优先级的任务。这也就实现了上述的两个目的,当然还有其他很多方法,我刚才想的这个方法有两个缺点,一是这样有点浪费空间,二是这样寻找最高优先级的算法复杂度有点高,不太符合实时调度的要求。咱们看看ucos的作者Jean J.Labrosse是怎么解决的这个问题。首先他用的是位变量来表示任务就没就绪,即用变量的每一位的数值是0还是1来表示任务就没就绪的状态。用某一位置一说明这个任务就绪,置零说明这个任务未就绪。关键问题是怎么在记录这两种状态的同时又能记录这个优先级的数值。使用这样一个方案,申请有64个位的数组,如果申请的变量是无符号8位的,那么我们就要申请xx[8],每个变量8位,数组共8个变量,因此有64个二进制位,用每一位的0和1表示就没就绪,同时被置一的位本身也就代表着数值的大小。看看下面的图:
这些是64个位变量,中间的我省略了,然后那,从右边往左边编号(从0编到63),你比如说优先级为15的任务就绪,你就可以把从左边数(当然从零开始),数到第15个位时把这个位置一即可。这64个位的空间怎么来的那,是申请了一个包括8个无符号数的数组,其数组下表是0~7.在c语言中也就是把这64个位空间,分为了8组,每组一个8位无符号变量,共8个变量。我们在c语言中对每一位置位时就可以先看这个优先级按照这种方法应该在第几组里置位,然后再确定在这个组里的具体位置。我们知道优先级变量OSTCBPrio是一个无符号的8位数。咱们就以一个具体事例来说明怎么利用这个方法来实现对相应的位置一。比如现在有一个优先级为12的任务就绪,把这个十进制的数值化为二进制为00001100,你把低三位看成是组里的第几个数的话,那高三位就代表这个优先级的任务所在的组号。(其中低三位是红色颜色标注的,高三位是蓝色颜色标注的),那么我们就知道这个优先级的任务属于001(即第一组),在组内的位置是100(即第四个)。这样我们就很容易用代码实现对某一固定的位置一操作。至于代码的真正实现,我们到分析代码时在具体分析。仅看到这些,Jean J.Labrosse的实现方法也就是节约了内存空间而已,但精华不止这些,还有另外一个很重要的问题就是怎么从这些就绪的任务中迅速找出优先级最高的来。上面介绍了利用OSRdyTbl这个数组如何实现记录任务的优先级和是否就绪的状态。现在来看一下OSRdyGrp这个变量,这个变量用来记忆那组里有就绪的任务。比如第三组里有就绪的任务,那这个变量的第三位的位变量就置一。这两个变量的逻辑结构如下:
那好,我们前面也已经知道了我们用OSRdyTbl这个数组的每一位是0还是1来表示这个任务是否就绪。用这个1处于的位置(从左向右数的位变量的个数)来表示这个优先级的大小。
那怎么得到最高优先级那(在ucos里是数值最小的),当然是利用OSRdyTbl这个数组,前面说的是创建任务时对其进行置位操作,现在就利用已经置位的OSRdyTbl这个数组来找到最小的优先级,方法很简单,就是从左到右扫描当遇到第一个1时的位变量的位置的数值就是当前的最高优先级。当然我们不是直接在OSRdyTbl数组里直接线性扫描的。先看OSRdyGrp这个变量,看看那个组里有就绪的任务,同样的方法遇到第一个一的位的位数即为最高优先级的任务所在的组号。所在的组号乘以8再加上在组里的位置,即得到当前优先级最高的任务的优先级。但现在的问题是我怎么根据OSRdyGrp和OSRdyTbl[]的值得到他们第一个1是第几位哪。只有建立一个索引表,这个表以他们的数值为索引,以他们的值中从左边数第一个1的位置为内容。即
你比如说你的OSRdyGrp的值最后是00110110的第一个1的位置是1,第零个数是0,第一个数是1.
而根据这个表,数组下标为0x46的内容是1.
根据上面讲的这些内容,你就能实现记录任务优先级的值和状态,还有就是快速的实现查找就绪任务中优先级最高的任务。
上面说的有点乱,要真正理解这个机制还需要看源码或者是看《嵌入式实时操作系统ucos-ii》。下面对这个机制做一个总结。
我们的两个目的:一,记录优先级及优先级的状态。二,在就绪的任务中实现快速查找优先级最高的任务。
作者所用方法中的精华:
1, 利用位变量存储任务状态,节约存储空间。同时把优先级的数值用申请的连续空间的位的位置来表示。
2, 在进行置位、清零操作时,利用分组的概念实现。因为64个连续的位空间是由8个8位的无符号数组成的,其下标是0~7,然后任务的优先级也可以看成是第几组的第几个。比如:00011100就可以看成是第三组的第四个优先级。
3, 为了实现快速查找,作者又增加了一个变量OSRdyGrp,某一位置一表示这一组内有就绪的任务,否则没有就绪任务。比如,OSRdyGrp的值为00000011就表示第0组和第一组都有就绪任务,在这种情况下肯定第0组的任务优先级高。也就是说第一个遇到1的位的位置代表的是优先级高的任务所在的组。同样的道理,OSRdyTbl[0]的值假设是00110010这个数第一个位置是1(都是从0开始数)。那就代表第零组的第一个任务就绪了,而且是优先级最高的。现在看来,最主要的问题是找第一个1,因为它的位置代表的优先级最高(也即代表的数值最小,其他的数值肯定比这个数值大)。为了快速的知道OSRdyGrp和OSRdyTbl[OSRdyGrp]第一个1在什么位置,我们建立了一个数组OSUnMapTbl[256],这个数组的下标是无符号8位变量的所有值,即OSRdyGrp或者OSRdyTbl[OSRdyGrp]的所有取值情况,而数组的内容是这个数的第一个1的位的位置(从右边数)。这样根据OSRdyGrp和OSRdyTbl[OSRdyGrp]的值就直接可以通过查表OSUnMapTbl[256]和简单的计算就能的到最高优先级的数值了。计算方法是通过OSRdyGrp这个索引查到的数值乘以8(因为每组八个)加上通过OSRdyTbl[OSRdyGrp]这个索引查表得到的数。这个方法是利用了空间换取时间效率的高效算法,O(1)算法,算法复杂度不变,无论有多少任务就绪(当然现在是64个)。
上面就是对ucos任务调度算法的一个分析。
下面说一下其他几个全局变量:
OSTaskCtr记录的是操作系统中创建任务的数量。
OSCtxSwCtr是操作系统任务切换的次数
OSIdleCtr是当空闲任务运行时这个变量持续加一。用这个变量可以计算cpu的利用率,具体方法可以参考《嵌入式实时操作系统,ucos-ii》的第三章的统计任务有关章节。
OSIntNesting是记录中断嵌套层数,进入一层中断这个值就加一,推出中断后就减一。
这样,我们就对这个ucos操作系统有了一个宏观的认识,到现在为止,我们的ucos已经初始化完毕,下一步我们就该创建任务了,当创建完任务后,开启ucos的多任务调度后,ucos就开始真正运行。下一部分就主要讲解ucos创建任务到多任务开始调度运行的所有过程。
写到后面突然发现一个很重要的问题没有说明,那就是有关开中断和关中断的相关问题。
在ucos内部,当处理一些共享性的资源时我们要实现互斥访问,即对这些共享资源要进行保护。比如说ucos对TCB进行操作时,你要保证你的操作代码要对TCB有完全的读写权限,还要保证只有你这一段代码在操纵TCB。要实现对TCB的互斥访问,以避免因竞争共享资源而产生的错误。要实现对共享资源的互斥访问,方法有很多,在ucos中主要使用的是关中断的方法。在ucos定义了两个宏; OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。
这两段代码都是用汇编书写的,其实开关中断很简单,就是对CPSR的I、F位置一实现关闭中断。
在这里可能有一个地方挺难理解,那就是这个关中断的代码是可嵌套的。那出现了两个问题:
关中断可嵌套有什么意义那?怎么实现的中断可嵌套那?
先说一下中断嵌套的意义,
其实大家分析一下,所谓保护临界段代码就是关中断,在操作系统内部本身就是关中断、然后开中断。这是没有问题的,但大家想一下,操作系统给用户提供了一些编程接口,当用户希望用同样关中断的方法来保护共享数据时。比如用户写的代码如下:
关中断
调用系统服务
用户其他操作
开中断
大家想一下,本来用户想保存从用户关中断和开中断这些代码的,不过你调用了操作系统的服务,如果你是用的方法是不可嵌套的,那在退出操作系统代码时已经开中断了。违背了用户的愿望。所以我们操作系统提供可嵌套的关 开中断的方法。这种嵌套是针对高层应用程序的。在操作系统内部这个嵌套是没有任何意义的。在操作系统内部一般在使用临界资源之前关中断,在使用完毕后开中断,OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()都是成对使用的。 |
|
|
|
|
|