1、操作系统涉及的管理用表
操作系统实际上是一种管理软件。所谓管理,就是在了解被管理对象的基本信息之后,在需要时,按照某种规则对这些被管理对象进行分配、调度等操作。
管理工作的基础在于详细地被管理对象的信息,用表来记录被管理对象的信息,必要时还要随着情况的变化及时更新信息。熟悉操作系统中使用的各种表格就是学习操作系统的要点。
操作系统中,从存储的角度看:
结构体最适合用来表述(不同属性)一张表
数组结构最适合用来表述(同属性)一张表
位图结构(特殊的数组)最适合表述任务的状态
1.1 盘点哪些表需要重点掌握
1.2 参考资料
任哲---uC/OS-II 入门
任哲---嵌入式操作系统uCOS-II原理及应用
2、任务控制块链表、注册表
对于uC/OS-II系统而言,对任务进行管理和调度是最主要的工作。uC/OS-II多任务实时系统的任务从代码上看,依旧是一个个C函数。从任务的存储结构上看,任务由任务程序代码(函数)、任务堆栈(私有堆栈)和任务控制块三部分组成。
任务控制块:关联了任务代码的结构体(存在RAM中),任务控制块记录了任务的各个属性,同时也关联了任务堆栈;
任务堆栈:用来保存任务的工作环境,创建了一个虚拟的处理器;
任务程序代码(函数):任务的执行部分。
我们学习的步骤是从任务程序代码---->任务控制块---->任务堆栈
2.0 任务程序代码以及由此得出任务控制块这张表需要记录哪些属性
任务程序代码基本上都是结构如此
void MyTask(void *pdata)
{
for(;;;)
{
可以被中断的用户代码;
OS_ENTER_CRITICAL(); //进入临界段(关中断)
不可以被中断的用户代码;
OS_EXIT_CRITICAL(); //退出临界段(开中断)
可以被中断的用户代码;
}
}
任务都是这样的一个无限循环结构。如果没有程序调度,CPU就一直在这个大循环和中断服务程序中运行(裸机就是这样)。要想在一个一个任务之间切换,就必须知道这样一个事实,程序计数器PC这个寄存器就是一个指挥棒,它指向哪里,CPU就运行到哪里,因此任务之间切换,就是把待运行任务的地址赋值給PC寄存器。
任务切换和相应中断,这里就有两个CPU运行环境要切换。新旧CPU运行环境要切换,如同两个变量要切换切换过一样,需要一个临时变量,即需要三个空间(框)。CPU的寄存器环境是任务切换的核心框,老的CPU运行环境需要先放到正在运行任务的私有框中,在从待运行任务的私有框中获取CPU运行环境放入到CPU寄存器这个框中。至少两个任务私有框,才能完成任务的切换。这些CPU运行环境(即各个CPU寄存器值),这些寄存器的值可以放在任务的私有堆栈中,因此这个框中至少需要放CPU运行环境-----任务堆栈(私有堆栈);同时为了方便在任务堆栈中找到这些寄存器的值(方便恢复),因此要在这个框中记录堆栈的栈顶地址;
在任务程序代码中调用其他函数是,需要保存CPU寄存器中的内容,因此任务需要配有私有的堆栈;
一个任务如何表征自己是唯一的,在uC/OS-II任务的优先级是唯一的,因此可以用任务的优先级作为任务的标识,因此此框中要有任务优先级;
任务有多种状态,因此此框中要有任务状态;
此框应该还要有任务堆栈一些信息,任务本身的一些信息,为了形成双向链表,因此要有指向前一个和后一个的程序控制块的指针。
上面大概的表述,已经让我弄清在程序控制块这个框中应该需要哪些信息。接下去仔细看看任务控制块。
2.1 任务控制块
对于任务的控制就是这些C函数要想被操作系统调度管理,就应该具有一个控制用数据结构,就是程序控制块。
程序控制块在操作系统诸多管理用表中最为重要,现在以一个最简单的程序控制块结构为例说明
typedef struct tcb{
char * code_name; //代码名称
int p; //重要性级别
int v_number; //版本号
void (* fun)(void); //指向被管理代码的指针
}TCB; //指针fun指向被管理代码地址,通过这个结构来控制管理与之关联的代码。
这个结构如下图所示

任务的程序控制块(TCB, task control block)相当于一个任务的身份证,系统就是通过程序控制块来管理任务。没有程序控制块的任务就不是一个任务,这是因为它不能被系统管理。 在创建任务时(调用OSTaskCreate()函数),OSTaskCreate()函数就会对程序控制块中所有成员(记录任务的堆栈指针、任务的当前状态,任务优先级别)赋予该任务相关的数据。
如uC/OS-II的程序控制块的结构比这个复杂,用到了具有两级结构的程序控制块

在uC/OS-II就是一个能对这些小任务的运行进行管理和调用的多任务操作系统。
任务堆栈可以看成一种特殊结构体,这里要注意的是指向代码的指针是CPU程序指针(程序计数器,PC)的副本。
PC指针的值总是待要执行指令的地址,它反映了一个程序运行进度。
看一看uC/OS中程序控制块链表

看看代码看看任务控制块
typedef struct os_tcb {
OS_STK *OSTCBStkPtr; /* Pointer to current top of stack */
#if OS_TASK_CREATE_EXT_EN > 0u
void *OSTCBExtPtr; /* Pointer to user definable data for TCB extension */
OS_STK *OSTCBStkBottom; /* Pointer to bottom of stack */
INT32U OSTCBStkSize; /* Size of task stack (in number of stack elements) */
INT16U OSTCBOpt; /* Task options as passed by OSTaskCreateExt() */
INT16U OSTCBId; /* Task ID (0..65535) */
#endif
struct os_tcb *OSTCBNext; /* Pointer to next TCB in the TCB list */
struct os_tcb *OSTCBPrev; /* Pointer to previous TCB in the TCB list */
#if (OS_EVENT_EN)
OS_EVENT *OSTCBEventPtr; /* Pointer to event control block */
#endif
#if (OS_EVENT_EN) && (OS_EVENT_MULTI_EN > 0u)
OS_EVENT **OSTCBEventMultiPtr; /* Pointer to multiple event control blocks */
#endif
#if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
void *OSTCBMsg; /* Message received from OSMboxPost() or OSQPost() */
#endif
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
#if OS_TASK_DEL_EN > 0u
OS_FLAG_NODE *OSTCBFlagNode; /* Pointer to event flag node */
#endif
OS_FLAGS OSTCBFlagsRdy; /* Event flags that made task ready to run */
#endif
INT32U OSTCBDly; /* Nbr ticks to delay task or, timeout waiting for event */
INT8U OSTCBStat; /* Task status */
INT8U OSTCBStatPend; /* Task PEND status */
INT8U OSTCBPrio; /* Task priority (0 == highest) */
INT8U OSTCBX; /* Bit position in group corresponding to task priority */
INT8U OSTCBY; /* Index into ready table corresponding to task priority */
OS_PRIO OSTCBBitX; /* Bit mask to access bit position in ready table */
OS_PRIO OSTCBBitY; /* Bit mask to access bit position in ready group */
#if OS_TASK_DEL_EN > 0u
INT8U OSTCBDelReq; /* Indicates whether a task needs to delete itself */
#endif
#if OS_TASK_PROFILE_EN > 0u
INT32U OSTCBCtxSwCtr; /* Number of time the task was switched in */
INT32U OSTCBCyclesTot; /* Total number of clock cycles the task has been running */
INT32U OSTCBCyclesStart; /* Snapshot of cycle counter at start of task resumption */
OS_STK *OSTCBStkBase; /* Pointer to the beginning of the task stack */
INT32U OSTCBStkUsed; /* Number of bytes used from the stack */
#endif
#if OS_TASK_NAME_EN > 0u
INT8U *OSTCBTaskName;
#endif
#if OS_TASK_REG_TBL_SIZE > 0u
INT32U OSTCBRegTbl[OS_TASK_REG_TBL_SIZE];
#endif
} OS_TCB;
回想2.0中对任务控制表的盘点,这上面成员中对我们及其重要的有
OS_STK OSTCBStkPtr 指向任务堆栈栈顶
INT8U OSTCBPrio; 任务优先级
INT8U OSTCBStat; 任务状态
2.1.0 uC/OS-II任务的状态和优先级
现在讲一讲uC/OS-II任务的状态和优先级这两个属性。
2.1.1 任务状态
uC/OS-II任务状态的转化
任务的状态 说明
睡眠状态 任务只是以代码的形式驻留在程序空间(ROM或RAM),还没有交给操作系 统管理时的情况——睡眠状态。任务在没有被配备任务控制块或被剥夺了任 务控制块时的状态——任务的睡眠状态。
就绪状态 如果系统给任务配备了任务控制块,且在任务就绪表中进行了就绪等级, 则任务就具备了运行的充分条件——任务的就绪状态
运行状态 处于就绪状态的任务如果经调度器判断获得了CPU的使用权,则任务就进入 运行状态。任何时刻有且只有一个任务处于运行状态(uC/OS-II是针对单核)
等待状态 正在运行的任务,如果需要等待一段时间或需要等待一个事件发生在运行 时,该任务就会把CPU的使用权让渡给其他任务而是任务进入等待状态
中断服务状态 一个正在运行的任务一旦响应中断请求就会中止运行而去执行中断服务程序 ——中断服务状态
任务状态间的切换

可以从睡眠状态和等待状态转到就绪状态;
只能从就绪状态或中断服务状态回到运行状态;
2.1.2 任务优先级
当系统中有多个任务需要运行(处于就绪态的任务S)时,操作系统必须在这些待运行任务重选择一个来运行。调度器采用何种规则来从就绪态的任务中选择一个来运行。uC/OS-II采用的是优先级抢占式规则,即在系统中创建任务时要按照其任务的重要性分配一个唯一的优先级级别。这样,优先级高的任务先运行,优先级低的任务后运行。
uC/OS-II系统最多能创建64个任务,因此任务的优先级别最多有64级,每个级别都用一个整数数字来表示,即0、1、2...63,数字越小,优先级别越高。在uC/OS-II的任务个数和最低优先级都可以在配置文件OS_CFG.H中用OS_LOWEST_PRIO来配置,任务的总数也不能超过OS_LOWEST_PRIO+1个。系统总是把最低优先级别OS_LOWEST_PRIO自动赋给空闲任务;如果我们还使能了统计任务,则系统还会把优先级别OS_LOWEST_PRIO-1自动赋给统计任务。0~OS_LOWEST_PRIO-2优先级可以赋给用户任务。
创建用户任务时,应该显式定义一个任务的优先级。
由于在uC/OS-II中每个任务都具有唯一的优先级别,因此这个优先级别也可以作为任务在系统中的标识identification。
上面描述了任务的最重要的属性任务优先级和任务状态,但是我们知道在任务切换、中断响应和调用函数需要任务私有堆栈来保存上下文环境。下面讲讲任务堆栈
2.2任务堆栈
根据2.0里的分析,因此uC/OS-II任务的任务控制块都含有一个指向该任务堆栈的指针。
在创建任务堆栈的栈区,只要定义一个OS_STK类型的数组即可;
OS_STK TaskStk[1024]; 在任务创建函数把栈顶指针传递给任务即可。
2.2.1 任务切换
在任务切换时,对于通用寄存器PUSH和POLL指定进入压栈和出栈;但是对于PC这个程序计数器不能用这种指令来操控。
如下图所示
被中止的任务,把CPU中的各寄存器的值压栈(存入被中止任务的私有堆栈中)------图中步骤1
其中堆栈寄存器SP的在私有堆栈中的地址被存入任务控制块TCB的OSTCBStkPtr 成员中----图中步骤2
从待运行任务的任务控制块TCB中OSTCBStkPtr成员获取的该任务的私有堆栈存储的堆栈指针SP的地址-----图中步骤3
并从SP开始把该任务的各个寄存器值出栈恢复到CPU的寄存器中去-----图中步骤4

PC程序计数器的切换
由于处理器一般没有对程序指针寄存器PC的出栈和入栈指针。
目前的方法是引发一次中断(或一次调用),并让中断向量指向OSCtxSw()------中断服务程序-----利用系统在跳转到中断服务程序时会自动把断点指针压入堆栈的功能,就把断点指针存入堆栈;
在OSCtxSw()里把其他CPU的寄存器的值也存入待中止的任务的私有堆栈中;在把CPU的SP指针指向待运行任务的私有堆栈栈顶,从私有堆栈恢复待运行任务的工作环境,在用中断返回指令IRET(或有相同功能的指令)把断点指针推入到CPU的PC寄存器中,恢复待运行任务的断点。这样就实现了断点的保存和恢复。
现在还要解决如何在众多任务控制块管理起来,说是管理其实就是两件事情:
1、如何快速找到众多任务中优先级最高的任务;
2、如何新添加任务和退出任务
下面就来讲一讲
2.3 用任务控制块来管理任务
这一部分,任务控制块为了找起来方便,把任务控制块做成双向链表组织起来,这一链表称为注册表;
uC/OS-II系统中占据CPU的是就绪状态任务s中优先级最高的那一个。所以
我们第一步先确定哪些任务处于就绪态;
第二步就绪任务s中如何找到哪个优先级最高
对于第一步,我们用就绪表---位图来解决
2.3.0 注册表
当被管理代码数量不大,且程序控制块结构比较小时,可以用数组作为程序控制块注册表。
对于uC/OS-II来说,采取链表作为程序控制块注册表,通过在程序控制块中再添加两个指针成员,一个用来指向前一个控制块,一个用来指向下一个控制块,把所有被管理代码的控制块用链表组织起来。

为了提高链表的查询效率,系统常常为链表在配一个数组,而这个数组的各个元素则存储了指向各个链表成员的指针,这样就可以通过数组来实现链表元素的快速随机查询。
系统通过程序控制块来了解代码的相关信息。
/*********************************************************************************************************************/
uC/OS-II的任务有两种:用户任务和系统任务。
注:
用户任务:为了解决应用问题而编写的。
系统任务:为应用程序提供某种服务或为系统本身服务的。对于嵌入式硬件而言,除了掉电或者时钟脉冲丢失,否则是不能停止工作的。系统运行后,不能保准有一个用户任务存在,因此需要一个任务(哪怕只是空操作的循环),保证设备不会停止工作,这个任务可被称为系统任务。uC/OS-II定义了两个系统任务:空闲任务和统计任务。
空闲任务:系统在运行中可能在某个时间内无用户任务可运行而处于空闲状态,为了使CPU在没有用户程序可执行时有事可做,uC/OS-II提供了一个空闲任务void OS_TaskIdle (void *p_arg),在无用户任务可运行时运行空闲任务。
统计任务:该任务用于每1S计算一次CPU在单位内被使用的时间,并把计算结果以百分比的形式存放在变量OSCPUsage中,便于其他应用程序来获取CPU的利用率。是否使用统计任务,用户可以根据OS_TASK_STAT_EN宏来配置使能与否。
/******************************************************************************************************************/
2.3.2 任务就绪表------查找最高优先级的就绪任务
uC/OS-II中,任务就绪表就是一个位图,由于uC/OS-II至多只有64个任务,因此用类型为INT8U的数组OSRdyTbl[]来表述即可。
任务就绪表如下所示:以任务优先级别(作为任务标识)的高低为顺序,每个任务有一个二进制位bit来表示就绪状态---1就绪;0不就绪

接下去,我们就需要查找最高优先级的任务了,在实时操作系统中 ,任务操作都必须在时间上必须是常量(意思是机器码运行时间是定的)。因此需循环查找这个方法是不可用的。
uC/OS-II系统定义了一个INT8U类型的变量OSRdyGrp,它视就绪表的每个元素为一个任务组(task group),因此它的每一bit表述一个任务组中有没有任务就绪

例如OSRdyGrp=0b01101101,就表示OSRdyTbl[0] OSRdyTbl[2] OSRdyTbl[3] OSRdyTbl[5] OSRdyTbl[6]任务组中有任务就绪。
现在我们可以根据优先级->快速查找对应任务是否就绪;
由于至多64个任务,因此优先级可以用6位来表示 0x00111111;
根据高3为(D5D4D3)就可以确定任务在OSRdyGrp的哪个位---根据此位状态就可以任务组的状态,1--有就绪的;
根据高3位(D5D4D3)也可以确定OSRdyTbl数组的下标
根据低3位(D2D1D0)就可以确认任务对应OSRdyTbl元素的哪一个位----根据此位状态就可以确定任务的状态, 1--就绪
在uC/OS-II实时操作系统中调度器程序从任务就绪表中找到最高优先级的任务;就按照
先从OSRdyGrp 找到最高优先级程序的高三位[D5D4D3]
在从OSRdyTbl[D5D4D3]元素中找到最高优先级的任务
这里的查找依靠一个数组,如何来查找的以后在分析????-------------OSUnMapTbl数组
2.3.3任务就绪表------新添和删除任务
在就绪表里做不到新添任务和删除任务,但是它可以完成就绪任务的登记和注销
登记:就是把优先级为prio的任务置为就绪状态:
OSRdyGrp | = OSMapTbl[prio >> 3];
OSRdyTbl[prio>>3] |= (prio & 0x7);
注销:就是把优先级为prio的任务置脱离就绪状态
if(((OSRdyTbl[prio>>3]) &= -OSMapTbl[prio&0x07]))==0)
OSRdyGrp &=OSRdyTbl[prio>>3];
其他表后续补充
本文深入解析uC/OS-II操作系统中的任务管理机制,包括任务控制块、任务状态与优先级、任务堆栈、任务切换及中断响应。阐述了如何通过任务控制块链表和注册表管理任务,以及如何利用就绪表快速查找最高优先级任务。
1301

被折叠的 条评论
为什么被折叠?



