1 UCOSII定义的关键数据结构

  OS_EXT  INT8U             OSIntNesting;

  OSIntNesting用于判断当前系统是否正处于中断处理例程中。

  OS_EXT  INT8U             OSPrioCur;

  OSPrioCur表示当前进程的优先级。

  OS_EXT  INT8U             OSPrioHighRdy;

OSPrioHighRdy表示最高优先级任务的优先级。

  OS_EXT  OS_PRIO           OSRdyGrp;

OSRdyGrp主要来标记可运行任务优先级除去低位3位或4位后的group bit位。例如任务的优先级为65(假设超过了系统最大的优先级超过了63,那么OS_PRIO应该有16位),由于超过了63,所以这个任务的group bit位为2^(65>>4)=2^4,也就是OSRdyGrp的第5位(从低位开始算起)为1。

  OS_EXT  OS_PRIO           OSRdyTbl[OS_RDY_TBL_SIZE];

  准备运行的任务标记,对应位为1表示任务可以运行,否则不能。

  OS_EXT  BOOLEAN           OSRunning;

  标志内核是否正在运行。

  OS_EXT  INT8U             OSTaskCtr;

  任务被创建的个数。

  OS_EXT  OS_TCB           *OSTCBCur;

  指向当前正在运行任务的TCB(任务控制块)。

  OS_EXT  OS_TCB           *OSTCBFreeList;

  空闲TCB块指针。

  OS_EXT  OS_TCB           *OSTCBHighRdy;

  指向最高优先级任务的TCB块。

  OS_EXT  OS_TCB           *OSTCBList;

  正在使用的TCB块的双向链表。

  OS_EXT  OS_TCB           *OSTCBPrioTbl[OS_LOWEST_PRIO + 1u];

  每个优先级对应的已创建的TCB块。

  OS_EXT  OS_TCB            OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS];

  系统静态分配的一个系统中所有的TCB块,包括正在使用的和空闲的。

typedef struct os_tcb {
OS_STK *OSTCBStkPtr; //指向任务的栈顶
struct os_tcb *OSTCBNext; //指向TCB链表中的下一个TCB块
struct os_tcb *OSTCBPrev; //指向TCB链表中的前一个TCB块
INT32U OSTCBDly; //任务延迟时间
INT8U OSTCBStat; //任务当前状态
INT8U OSTCBStatPend; //任务悬挂状态
INT8U OSTCBPrio; //任务优先级(0表示最高优先级)
INT8U OSTCBX; //与任务优先级一致group bit位(一般是OSTCBPrio的低3位)
INT8U OSTCBY; //与任务优先级一致的ready table(一般是OSTCBPrio高5位)
OS_PRIO OSTCBBitX; //访问ready table的bit mask
OS_PRIO OSTCBBitY; //访问ready group的bit mask
} OS_TCB

  TCB块是任务的描述符,描述了任务的状态和运行时必须的信息。

2 系统初始化

在OSInit函数中初始化系统的各个数据结构,具体如下:

1、调用函数OS_InitMisc初始化OSIntNesting,OSTaskCtr,OSRunning等一些变量。

  2、调用函数OS_InitRdyList初始化准备运行的OSRdyTbl表位都为空。

  3、调用函数OS_InitTCBList和OSTCBPrioTbl表的数据都初始化为0,第i个控制块指向第i+1个控制块,并且都加入到OSTCBFreeList指向的链表中,把OSTCBList初始化为空链表。

  4、调用函数OS_InitTaskIdle来创建一个Idle任务,该任务是优先级最低的任务,任务号为65535,优先级为63。

3 创建启动任务

  然后我们自己调用OSTaskCreate函数创建我们自己的任务,具体如下:

  1、先判断系统当前是否处于中断,如果是,则返回,因为当系统处于中断时是不允许创建任务的。

  2、判断需要创建任务的优先级是否存在其它相同优先级的任务,如果存在,则返回,即相同优先级的任务只能有一个。

  3、调用函数OSTaskStkInit初始化任务的栈。

  4、调用函数OS_TCBInit初始化任务的TCB块。这个函数主要是从OSTCBFreeList链表获取空闲的TCB块并初始化这个控制块,然后将这个控制块从OSTCBFreeList链表中删除,并插入到OSTCBList链表中。

  如果现在内核正在运行,那么调用函数OS_Sched切换任务。

  OS_Sched函数主要用来切换任务,如果当前系统不处于中断例程中,就通过调用函数OS_SchedNew来获取当前已经准备好的最高优先级的任务(最高优先级主要通过OSRdyGrp和OSRdyTbl来计算得到),然后调用函数OS_TASK_SW来将任务切换到当前优先级最高的任务。

  OS_TASK_SW函数则调用os_cpu_a.asm文件中的汇编函数OSCtxSw来触发PendSV异常来完成任务的切换,至于为什么要这样做可以参考前面讲的重点的PendSV。

  PendSV异常的处理的伪代码如下:

PendSVHandler:
if (PSP != NULL) {//判断程序的堆栈指针是否为空,为空说明这是第一个任务
//当调用PendSVHandler()时,
//CPU 就会自动保存xPSR、PC、LR、R12、R0-R3 寄存器到堆栈
//保存后,CUP 的栈SP指针会切换到使用主堆栈指针MSP上
//我们只需检测进入栈指针PSP是否为NULL就知道是否进行任务切换
//因此当我们第一次启动任务是,OSStartHighRdy()就把PSP设为NULL,
//避免系统以为已经进行任务切换
Save R4-R11 onto task stack; //手动保存 R4-R11
OSTCBCur->OSTCBStkPtr = SP; //保存进入栈指针PSP到任务控制块
//以便下次继续任务运行时继续使用原来的栈
}
OSTaskSwHook(); //此处便于我们使用钩子函数来拓展功能
OSPrioCur = OSPrioHighRdy; //获取最高优先级就绪任务的优先级
  OSTCBCur = OSTCBHighRdy; //获取最高优先级就绪任务的任务控制块指针
  PSP = OSTCBHighRdy->OSTCBStkPtr; //保存进入栈指针
  Restore R4-R11 from new task stack; //从新的栈恢复 R4-R11 寄存器
Return from exception; //返回

  PendSV异常处理代码很简单,就是保存现场,将当前程序的堆栈指针保存到TCB块中,并将下一个要运行任务的栈指针更新到PSP中,同时恢复下一个运行任务的现场。

4 运行系统

  一切都已经准备就绪,那么我们就调用函数OSStart运行系统。该函数和任务切换函数OS_TASK_SW差不多,只不过它会调用函数OSStartHighRdy将cpu的PSP初始化为0来运行我们系统中的第一个任务并将OSRunning标记为true来表示我们的内核开始运行了。

5 任务切换时机

  从OS_Sched函数和OSStartHighRdy函数可以看出,它们总是选择当前可运行的最高优先级任务来运行,所以只有最高优先级的任务不让出cpu,低优先级的任务永远也不可能运行,所以发生任务切换的唯一时机就是高优先级的进行主动让出cpu。高优先级的任务可以调用函数OSTimeDlyHMSM来让自己延迟从而让出cpu,OSTimeDlyHMSM本质上是调用函数OSTimeDly来实现让出cpu的操作。下面来看看这个函数的具体实现:

void  OSTimeDly (INT32U ticks)
{
if (OSIntNesting > 0u) { //系统正处于中断中
return;
}
if (OSLockNesting > 0u) { //调度器加锁了
return;
}
if (ticks > 0u) { //延迟时间大于0
OS_ENTER_CRITICAL();
y = OSTCBCur->OSTCBY; //获取当前优先级的高位
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX; //清空表中对应当前任务的bit位
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBDly = ticks; //设置当前任务的延迟时间
OS_EXIT_CRITICAL();
OS_Sched(); //切换任务
}
}

  这个函数其实就是将本任务在OSRdyTbl表和OSRdyGrp对应的标记位清0,使得后面的调度函数OS_Sched可以选择其它优先级的任务,即当前任务让出cpu。这个函数还做的事就是设置当前任务的延迟时间到任务的TCB块中,这个时间会在SysTick处理函数中每个节拍的减少,直到减为0时,会将这个任务对应的bit位加入到OSRdyTbl表和OSRdyGrp中来获取cpu。

6 任务调度算法

  UCOSII涉及的调度算法比较的简单,正如在任务切换时机里说的,只要高优先级的任务不让出cpu,低优先级的任务永远不可能得到cpu,调度器总是选择系统中已经就绪的最高优先级任务运行。问题就来了,调度器怎样确定到当前系统中任务的最高优先级是多少呢?当然最笨的方法是将OSTCBList链表都遍历一遍,这样肯定是可以的。但这样效率是很低的,特别是系统中就绪任务比较多的时候,它的时间复杂度位O(n),n为系统中就绪任务的个数。UCOSII采用了一种比较巧妙的方法实现在O(1)的复杂度获得这个最高优先级值。

  当一个任务就绪时,这个任务的优先级OSTCBPrio被拆分成两个部分,低4位的值存放在任务控制块的OSTCBX中,高4位存放在OSTCBY中。然后将2^OSTCBX的值存放在控制块的OSTCBBitX中,将2^OSTCBY存放在OSTCBBitY中。通过下面方法更新OSRdyTbl表和OSRdyGrp变量。

  OSRdyGrp |= ptcb->OSTCBBitY;

  OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;

  OSRdyGrp存放了当前系统中所有就绪任务优先级高4位表示的bit位,OSRdyTbl表其实就是一个位图,用来标记系统中所有就绪任务的优先级,每一个优先级对应一个bit位。

有了这些信息,怎样来获取当前就绪任务中最高优先级的值呢?系统维护了一张表:

INT8U  const  OSUnMapTbl[256] = {

0u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x00 to 0x0F*/

4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x10 to 0x1F*/

5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x20 to 0x2F*/

4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x30 to 0x3F*/

6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x40 to 0x4F*/

4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x50 to 0x5F*/

5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x60 to 0x6F*/

4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x70 to 0x7F*/

7u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x80 to 0x8F*/

4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x90 to 0x9F*/

5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xA0 to 0xAF*/

4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xB0 to 0xBF*/

6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xC0 to 0xCF*/

4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xD0 to 0xDF*/

5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xE0 to 0xEF*/

4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u  /* 0xF0 to 0xFF*/

};

  通过函数OS_SchedNew来获取最高的优先级值:

static  void  OS_SchedNew (void)
{
INT8U y;
OS_PRIO *ptbl;
if ((OSRdyGrp & 0xFFu) != 0u) {
y = OSUnMapTbl[OSRdyGrp & 0xFFu];
} else {
y = OSUnMapTbl[(OS_PRIO)(OSRdyGrp >> 8u) & 0xFFu] + 8u;
}
ptbl = &OSRdyTbl[y];
if ((*ptbl & 0xFFu) != 0u) {
OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(*ptbl & 0xFFu)]);
} else {
OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u);
}
}

  这个函数先根据OSRdyGrp值这张表来获得最高优先级的高4位的值y,然后通过OSRdyTbl[y]来获得低4位的值x,这样最终的值就是y<<4 + x。为什么这样就可以得到最高优先级呢?现在来看一个例子:假设OSRdyGrp = 10,对应的二进制值为1010,那么就绪任务中优先级前4位最小的值为1,由于OSRdyGrp的二进制值第二位为1(这个可以通过前面计算OSRdyGrp的方法来理解)。然后通过这个1,我们可以知道最高优先级在OSRdyTbl对应的bit位在OSRdyTbl[1]中,然后通过OSRdyTbl[1]来查表OSUnMapTbl同样可以获得最高优先级的低4位的值。然后合并这两个值就可以获得最高优先级的值了。

UCOSII内核代码分析的更多相关文章

  1. 从linux内核代码分析操作系统启动过程

    朱宇轲 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 在本次的实验中, ...

  2. Linux启动过程的内核代码分析

    参考上文: http://www.cnblogs.com/long123king/p/3543872.html http://www.cnblogs.com/long123king/p/3545688 ...

  3. inux2.6.xx内核代码分析( 72节)

    http://blog.csdn.net/ustc_dylan/article/category/469214

  4. (转):从内核代码聊聊pipe的实现

    来源: http://luodw.cc/2016/07/09/pipeof/ 用linux也有两年多了,从命令,系统调用,到内核原理一路学过来,我发现我是深深喜欢上这个系统:使用起来就是一个字&quo ...

  5. 深入理解mmap--内核代码分析及驱动demo示例

    mmap是一个很常用的系统调用,无论是分配内存.读写大文件.链接动态库文件,还是多进程间共享内存,都可以看到其身影.本文首先介绍了进程地址空间和mmap,然后分析了内核代码以了解其实现,最后通过一个简 ...

  6. Linux内核分析—完成一个简单的时间片轮转多道程序内核代码

    ---恢复内容开始--- 20135125陈智威 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-10 ...

  7. Linux内核中的GPIO系统之(3):pin controller driver代码分析

    一.前言 对于一个嵌入式软件工程师,我们的软件模块经常和硬件打交道,pin control subsystem也不例外,被它驱动的硬件叫做pin controller(一般ARM soc的datash ...

  8. Linux内核启动代码分析二之开发板相关驱动程序加载分析

    Linux内核启动代码分析二之开发板相关驱动程序加载分析 1 从linux开始启动的函数start_kernel开始分析,该函数位于linux-2.6.22/init/main.c  start_ke ...

  9. linux内核中链表代码分析---list.h头文件分析(一)【转】

    转自:http://blog.chinaunix.net/uid-30254565-id-5637596.html linux内核中链表代码分析---list.h头文件分析(一) 16年2月27日17 ...

随机推荐

  1. Python 爬虫2——环境配置

    关于环境配置的操作,其实非常简单,假如不使用第三方的框架的话,只需要安装Python即可完成后续的操作. 一.Python的安装和配置: windows系统的安装配置过程如下,假如是Mac系统,可参考 ...

  2. PHP防止跨域提交表单

    在提交的服务段的数据进行验证.   $servername=$_SERVER['SERVER_NAME'];//当前运行脚本所在服务器主机的名字.  $sub_from=$_SERVER[" ...

  3. Delphi中stringlist分割字符串的用法

    Delphi中stringlist分割字符串的用法 TStrings是一个抽象类,在实际开发中,是除了基本类型外,应用得最多的. 常规的用法大家都知道,现在来讨论它的一些高级的用法. 1.CommaT ...

  4. Tomcat无故自动退出的问题

    我在这篇文章<写一个脚本,自动启动Tomcat>中提到Tomcat会无缘无故退出,而且在日志中找不到原因.后来终于知道为什么了: 由于内存不足,被OOM Killer杀死的!由于是直接被系 ...

  5. SQL Server 备份迁移策略

    标签:SQL SERVER/MSSQL SERVER/数据库/DBA/xp_cmdshell/备份压缩 概述 当备份空间不是很充裕的情况下需要找方法将备份文件拷贝到专用的备份机器上去,特别是存储空间不 ...

  6. C语言 · 回文数

    问题描述 1221是一个非常特殊的数,它从左边读和从右边读是一样的,编程求所有这样的四位十进制数. 输出格式 按从小到大的顺序输出满足条件的四位十进制数.   方案一: int main(){ int ...

  7. xamarin UWP自定义圆角按钮

    uwp自带的button本身不支持圆角属性,所以要通过自定义控件实现. 通过设置Button的Background=“{x:Null}”设置为Null使背景为空,再设置Button.Content中的 ...

  8. 有关bootstrap之排版

    1.标题 HTML中的所有标题标签,从<h1> 到 <h6> 均可用.另外,还提供了.h1 到.h6 class,为的是给inline属性的文本赋予标题的样式. 在标题内还可以 ...

  9. Gitcafe绑定自定义域名

    之前将自己练习写作工具替换为Markdown,部署工具改为Hexo,并且将托管地搬家到GitCafe之后,便是被各种的舒爽所围绕.具体折腾的详情参见使用Hexo搭建专属Blog.她们的组合方便快捷,让 ...

  10. salesforce 零基础学习(四十七) 数据加密简单介绍

    对于一个项目来说,除了稳定性以及健壮性以外,还需要有较好的安全性,此篇博客简单描述salesforce中关于安全性的一点小知识,特别感谢公司中的nate大神和鹏哥让我学到了新得知识. 项目简单背景: ...