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. iOS判断对象相等 重写isEqual、isEqualToClass、hash

    相等的概念是探究哲学和数学的核心,并且对道德.公正和公共政策的问题有着深远的影响. 从一个经验主义者的角度来看,两个物体不能依据一些观测标准中分辨出来,它们就是相等的.在人文方面,平等主义者认为相等意 ...

  2. compositionEnd 和 input 事件(中文输入法问题)

    网上用 compositionstart + compositionend + input 解决中文输入法问题的办法 node.addEventListener('compositionstart', ...

  3. Python之路第一课Day8--随堂笔记(socket 承接上节---网络编程)

    本节内容 Socket介绍 Socket参数介绍 基本Socket实例 Socket实现多连接处理 通过Socket实现简单SSH 通过Socket实现文件传送 作业:开发一个支持多用户在线的FTP程 ...

  4. java并发编程(五)正确使用volatile

    转载请注明出处:     volatile用处说明     在JDK1.2之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的.而随着JVM的成熟和优化,现在在多线程 ...

  5. Spring MVC注解的一些案列

    1.  spring MVC-annotation(注解)的配置文件ApplicationContext.xml <?xml version="1.0" encoding=& ...

  6. web图片识别

    <!doctype html><html lang="en"><head> <meta charset="UTF-8" ...

  7. C语言系统时间读取

    1 读出系统时间(每隔一秒)#include#includeint main(){  while(1) {    time_t t;    t= time(0); struct tm *p;     ...

  8. CQOI 2016 k远点对

    题目大意:n个点,求第k远的点对的距离 KD树裸题 注意要用堆维护第k远 #include<bits/stdc++.h> #define ll unsigned long long #de ...

  9. Hadoop学习笔记—9.Partitioner与自定义Partitioner

    一.初步探索Partitioner 1.1 再次回顾Map阶段五大步骤 在第四篇博文<初识MapReduce>中,我们认识了MapReduce的八大步凑,其中在Map阶段总共五个步骤,如下 ...

  10. SQL Server 深入解析索引存储(上)

    标签:SQL SERVER/MSSQL SERVER/数据库/DBA/索引体系结构/堆/聚集索引 概述 最近要分享一个课件就重新把这块知识整理了一遍出来,篇幅有点长,想要理解的透彻还是要上机实践. 聚 ...