2009年4月11日

μC/OS-II 研究系列 - 002

任务

有两种典型的嵌入式开发, 一种是所谓「前/后台系统」开发, foreground / background system development, 一种是基于某个操作系统做开发. 对于后者而言, 开发的主要工作就是编写快速而有效的用户空间任务, user space task.
因此, 研究操作系统对任务的标识和管理, Task Identification and Management, 非常重要. 这里针对 μC/OS-II 2.52 版本展开说明.
---------------------------------
如何创建任务:
有很多 API 函数用于操作任务, 包括创建任务 OSTaskCreate() OSTaskCreateExt(), 删除任务 OSTaskDel() OSTaskDelReq(), 任务堆栈检测 OSTaskStkChk(), 挂起和恢复任务 OSTaskSuspend() OSTaskResume(), 改变任务优先级 OSTaskChangePrio(), 等等.
这里仅仅是粗略介绍 OSTaskCreate() OSTaskCreateExt(), 详细的介绍将在后续文章「任务管理接口」中详细介绍.
#if OS_TASK_CREATE_EN > 0
INT8U  OSTaskCreate (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio);
#endif

#if OS_TASK_CREATE_EXT_EN > 0
INT8U  OSTaskCreateExt (void   (*task)(void *pd),
void    *pdata,
OS_STK  *ptos,
INT8U    prio,
INT16U   id,
OS_STK  *pbos,
INT32U   stk_size,
void    *pext,
INT16U   opt);
#endif

首先要说明, 类似 INT32U 的类型, 就是表示32位的无符号整型, 譬如 #typedef unsigned long int INT32U, 依次类推 INT8U INT16U.
其次, 关于指针类型, 都是以小写字母 p 开头, 譬如 *ptos *pbos 等等.

ok, 下面介绍几个要点:
#1
宏标记 OS_TASK_CREATE_EN OS_TASK_CREATE_EXT_EN, 用意在于「可裁减性」, 也就是所谓的 scalable. 譬如, 如果用户需求很简单, 没必要使用扩展任务创建模式, 就 #define OS_TASK_CREATE_EXT_EN 0, 这样可以缩减所需的程序空间大小.
#2
任务创建函数 OSTaskCreate() 用于简单的创建一个任务, 需要指明任务入口 void (*task)(void *pdata), 被处理数据的指针 void *pdata, 任务优先级 INT8U prio 以及任务堆栈栈顶指针 OS_STK *ptos.
扩展型任务创建函数 OSTaskCreateExt() 用于创建一个能够更加灵活操控的任务, 除了需要指明上述的基本参数外, 还有 ---
INT16U id, 任务的额外标识, 当前, 这个参数无用. 目前, 任务的 prio 就是其标识, id 只是留作后续版本扩展用,
OS_STK *pbos, 堆栈栈底. 注意, 这里的栈底并非活跃堆栈的底部, 而是堆栈容量的底部, 也就是堆栈生长的极限位置.
INT32U stk_size,堆栈大小. 并非字节容量, 而是指针容纳量. 譬如32位的机器, 当 stk_size = 100U 时, 实际堆栈容量应该是400 Bytes,
void *pext, 指向某个自定义扩展数据块, 也就是除了 pdata 之外的另外一个能够被操作的数据块.
INT16U opt, 设定选项, 如 OS_TASK_OPT_STK_CHK OS_TASK_OPT_STK_CLR OS_TASK_OPT_SAVE_FP, 分别代表允许堆栈检测, 堆栈清0, 需要进行浮点操作; 用「位或」运算符来操作之, 譬如, OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR.
#3
关于任务堆栈.
由于处理器架构的区别, 有 high->low & low->high 两种堆栈生长方向. 譬如51系列单片机, 都是从低向高生长, 而对于 8086/8088 以及当下的 IA32 架构, 则是从高向低生长.
如果不太理解, 我在这里画了一个堆栈从高向低生长的示意图:
压入堆栈前:
high address
(0x2665) = 某地址a    --- top_of_stack
.....
...      = NULL
...      = NULL
...      = NULL
.....
(0x2600) = NULL
low address

压入堆栈后:
high address
(0x2665) = 某地址a
(0x2664) = (EFLAGS)
(0x2663) = (CS)
(0x2662) = (EIP)    --- top_of_stack
...      = NULL
...      = NULL
.....
(0x2600) = NULL
low address

μC/OS-II 的任务堆栈是彼此独立的, 各个任务的堆栈大小并非完全相同, 而是可以对每个任务指定堆栈大小. 这对于节约系统资源很有帮助.
在调试时, 可以通过 OSTaskStkChk() 来检测堆栈使用情况, 从而为每个任务划分恰当的堆栈空间大小. 此函数并非系统默认启动, 其详细信息, 后续文章会说明.
#4
大家肯定注意到了, 在 OSTaskCreateExt() 中, 既有 stk_size 堆栈大小, 又有 ptospbos 指针. 很明显, 三个变量中, 仅仅需要两个就ok了, 第三个可以通过计算得出.
那为何要传递三个变量呢?
这就是典型的以空间换取时间 --- 占用的 RAM 空间虽然增加了, 但是在某些应用上, 譬如计算堆栈使用量等等, 运行时间得到了压缩. 对于实时操作系统, 这是划算的.
#5
任务优先级. 数字越小, 优先级越高.
范围是 0~OS_LOWEST_PRIO, OS_LOWEST_PRIO 是自定义的常量.
最多可以定义的任务优先级数量是64级, 也就是0~63. 但是, 最低级永远都是给空闲任务 OSTaskIdle() 使用的. 而且由于将来扩展的需要, 最好 0~3 & (OS_LOWEST_PRIO - 3)~(OS_LOWEST_PRIO) 不要使用.
---------------------------------
用户任务的大致形式
永远不会返回的无限循环. 函数的返回值应该被设置成 void 类型, 而任务的参数应该是指向被处理的数据的指针, void *pointer_to_data_be_handled. 譬如,
void task_name(void *pdata)
{
/* 用户代码 */
while(1) {
/* 用户代码 */
}
}

---------------------------------
任务状态
主要是5种状态.
dormant 休眠, 也就是任务驻留在程序空间, 没有交付给系统进行调度.
ready 就绪, 也就是随时可以被调度程序调度.
running 运行, 整个系统中永远只能有一个任务处于运行状态.
waiting 等待, 譬如任务正在等待某个信号量被释放 OSSemPend(), 抑或是将自身延时某段时间 OSTimeDly(clock_ticks_numbers) 供系统做调度等等.
interrupt 被中断, 顾名思义也就是被中断啦.
参考示意图如下, 从书上抓图抓下来的. 上面附带了详细的状态转移函数调用.
点击查看大图.

---------------------------------
任务控制块
首先指出:
这是系统里最重要的一个自定义结构体. 定义在 ucos_ii.h 文件中.
typedef struct os_tcb {
OS_STK          *OSTCBStkPtr;      /* Pointer to current top of stack                              */

#if OS_TASK_CREATE_EXT_EN > 0
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 > 0
OS_EVENT        *OSTCBEventPtr;    /* Pointer to event control block                               */
#endif

#if ((OS_Q_EN > 0) && (OS_MAX_QS > 0)) || (OS_MBOX_EN > 0)
void            *OSTCBMsg;         /* Message received from OSMboxPost() or OSQPost()              */
#endif

#if (OS_VERSION >= 251) && (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0)
#if OS_TASK_DEL_EN > 0
OS_FLAG_NODE    *OSTCBFlagNode;    /* Pointer to event flag node                                   */
#endif
OS_FLAGS         OSTCBFlagsRdy;    /* Event flags that made task ready to run                      */
#endif

INT16U           OSTCBDly;         /* Nbr ticks to delay task or, timeout waiting for event        */
INT8U            OSTCBStat;        /* Task status                                                  */

INT8U            OSTCBPrio;        /* Task priority (0 == highest, 63 == lowest)                   */

INT8U            OSTCBX;           /* Bit position in group  corresponding to task priority (0..7) */
INT8U            OSTCBY;           /* Index into ready table corresponding to task priority        */

INT8U            OSTCBBitX;        /* Bit mask to access bit position in ready table               */
INT8U            OSTCBBitY;        /* Bit mask to access bit position in ready group               */

#if OS_TASK_DEL_EN > 0
BOOLEAN          OSTCBDelReq;      /* Indicates whether a task needs to delete itself              */
#endif
} OS_TCB;

东西太多, 懒得总结了. 囧
大致就是根据 OS_CFG.h 中的宏定义, 裁减性的定义任务控制块 OS_TCB 结构体.
需要指出的是, 这里的 struct os_tcb *OSTCBNext; struct os_tcb *OSTCBPrev; 主要是用于构建空任务控制块单向链表 OSTCBTbl[], 以及系统当前任务的任务控制块list OS_TCB *OSTCBList; 指针所指向的双向链表.
前者是依靠 OS_TCB *OSTCBFreeList; 指向的, 每当一个任务被创建, 就将当前 OSTCBFreeList 指向的空任务控制块交付给此任务, 并将此控制块初始化, 而 OSTCBFreeList 指针也在单向链表中向后移动一位; 同理, 每当一个任务被删除, 也就是成为休眠状态后, 这个任务的控制块被清空并归还到单向链表中, OSTCBFreeList 也向前移动一位.
后者系统当前任务的任务控制块list双向链表结构, 也是很重要的一个结构. 时钟节拍函数 OSTimeTick() 就是用这个链表来刷新各个任务的 OSTCBDly 值. 这个双向链表的表头指针是 OSTCBList, 永远指向最新创建的某个任务. 如果一个任务A被创建, 那么就是插入到 OSTCBList 的前面, 而 OSTCBList 也指向这个最新创建的任务A.
ok, 或许你已经考虑到了. 由于系统优先级最多只有 OS_LOWST_PRIO + 1 个, 而任务的表示也仅仅只能够由 prio 来区分, 那么 OSTCBFreeList 所指向的空任务控制块单向链表中的元素, 最多也只能够有 OS_LOWST_PRIO + 1 个.

你看明白了么? 如果没有, 还是下载文档来看吧.
去 gigapedia 搜索一下我前面提到的那本书 MicroC/OS II: The Real Time Kernel, 就可以找到了. 毕竟人家花了N长时间写作, 所以更加全面. 我这里的介绍显得有些太快而且太简略了点.
不过呢, 最好还是下载源代码阅读. 在官方站点上就有最新版本的下载. google 可以找到以往的老版本, 譬如和文档配套的2.52版本.
---------------------------------
初始化任务控制块
再次指出: 这也是内核源码中很重要的一部分.
前面已经介绍了创建任务, 也介绍了操作系统对于任务的控制模块 OS_TCB. 也提到了在创建一个任务的时候, 对空任务控制块单向链表系统当前任务的任务控制块list的影响.
这些影响, 都体现在任务创建函数, OSTaskCreate()OSTaskCreateExt() 中. 而这两个函数, 其核心部分都是一个函数, OS_TCBInit().

这里跑题指出一点, 如果各位在内核源码中碰到了 OS_xxx() 形式的函数, 那么肯定是系统内部函数, 也就是不需要各位显式调用, 系统的内部封装函数罢了.

这段函数的源代码在 os_core.c 文件中, 基本的内容前面都指出来了, 不再赘述. 各位直接看源码吧.
---------------------------------
总结
本篇文章内容很多, 这里再次梳理一遍:
#1. 如何创建任务? OSTaskCreate() OSTaskCreateExt(), 两者的参数列表参见上文中的第一个section.
#2. 用户任务的大致形式? 一个无限循环, 永远不会返回.
#3. 任务有哪些状态? 一共5种. 休眠, 就绪, 运行, 等待, 被中断.
#4. 任务怎么被系统识别和处理? 通过任务控制块. 参见上述对于 OS_TCB 结构体的描述和源代码. 同时务必明确两个系统级别的链表, 空任务控制块单向链表, 以及系统当前任务的任务控制块list双向链表. 前者由 OSTCBFreeList 指针指向当前空任务控制块, 随时等待着被付给某个新创建的任务. 后者由 OSTCBList 指针指向最新创建的某个任务的任务控制块.
#4. 新任务被创建时, 操作系统如何初始化任务控制块? 主要做哪些工作? 参考上面的源代码, 以及上面的#3「任务怎么被系统识别和处理」.
---------------------------------
ok, 终于写完了.
我真是精神可嘉啊. 囧. 前不久肌肉训练过了头, 加之生活习惯不好, 经常熬夜, 导致颈椎部位的肌肉组织有点拉伤, 今天下午去武汉体育学院医院做了推拿和拔火罐, 舒服多了.
而且现在我的Ubuntu上设置了keyboard typing break, 每隔30分钟就强制关闭键盘响应2分钟, 活动颈椎肌肉, 做一做颈椎保健操. 很舒服.

下篇预告:
操作系统初始化, OSInit(),
操作系统启动, OSStart(),
下下篇预告:
系统任务就绪表 OSRdyGrp, OSRdyTbl[],
操作系统的任务调度器, OS_Sched(void),
如何给调度器加锁, OSSchedLock(void), 和解锁, OSSchedUnlock(void),
任务级别的任务切换, OS_TASK_SW(), 一般使用汇编完成, 这里仅仅是写出pseudo code就ok了.

- EOF -

没有评论:

发表评论

不要使用过激的暴力或者色情词汇.
不要充当勇猛小飞侠 --- 飘过 飞过 扑扑翅膀飞走 被雷得外焦里嫩地飞走.
万万不可充当小乌龟 --- 爬过.
构建河蟹社会 责任你有 我有 大家有 -_-

Creative Commons License 转载请指明出处. 谢谢合作.
/***********************
author: jtuki
http://jtuki.blogspot.com/
***********************/