在上篇中, 我出尔反尔了一回, 没有按照上上篇的「预告篇」来写这个系列. 在这篇里, 咱们继续出尔反尔. 囧
下周五要和一个老师去某公司谈一个项目的具体事宜, 所以接下来一段时间里, 除了毕业设计, 就是那个项目了, 争取两个月时间, 也就是这个月, 和下个月搞定之.
至于这个「研究系列」的命运, 参考文章末尾.
ok, 步入正题.
对于一个操作系统而言, 任务和任务, 以及任务和中断服务子程序之间, 同步和通信是必不可少的.
既然需要同步和通信, 那么彼此之间发送的信号, signal, 就是其媒介了. 在
μC/OS-II
中, 信号被看作是事件, event, 而用于描述和表征「事件」的, 是被称为事件控制块的结构体, 简称做 ECB
, 也就是 Event Control Block, 下文对「事件控制块」和 ECB
不加区分, 混合使用.---------------------------------
三种同步方式
#1
信号量 Semaphore:
譬如, 对某资源A实施信号量保护, 也就是, 只有获得信号量的任务, 才能够访问资源A.
如果该资源允许 N 个任务同时访问, 那么信号量定义成 N 就ok了.
每被一个任务捕获, 信号量减一. 当信号量为0时, 所有试图访问A的任务都会被返回一个出错代码, 抑或是 suspend, 开始等待另外的任务释放信号量.
#2
互斥量 Mutex: 也就是 mutual exclusion.
如果打个比方, 就是「一山不容二虎」, 你可以先将其简单的理解成
semaphore = 1;
时的情况.不过, 在
μC/OS-II
中, 千万不能这么简单理解, 否则代码就 100% 功能紊乱了. 下文中将加以详细的描述.#3
事件标志组 Event Flag Group: 还是举例吧, 语言表达没有直接写代码迅速.
下文中将贯穿这个思想, 毕竟有时候, 读代码确实比看文字更容易理解整体的架构, especially when the author himself become impatient of writing the tedious article. -_-
首先指出, 下面的代码可不是能够直接用于
μC/OS-II
的代码, 是我为了方便描述而临时写的一段.typedef unsigned long os_event_flag_grp; /* 事件标志组中的某bit置1时, 表示某事件发生 */ #define FLAG_TYPE_SET 1 #define FLAG_TYPE_CLR 0 os_event_flag_grp flag_grp; if (something_a) flag_grp |= 0x0001; if (something_b) flag_grp |= 0x8000; if (something_c && something_d && something_e) flag_grp |= 0x0400;
明白了么? 譬如当
something_b
发生时, 标志组的第15个bit就会置一. 至于内核中具体的实现方式, 下文中将加以描述.---------------------------------
事件控制块
这算是内核中最为重要的结构体之一了. 其重要程度, 丝毫不逊色于前面提到的「任务控制块」, task control block.
为了贯彻上文中提到的「写代码, 读代码, 少打字」的主张, 咳咳, 大家伙直接看源码吧:
#if (OS_EVENT_EN > 0) && (OS_MAX_EVENTS > 0) typedef struct { INT8U OSEventType; /* Type of event control block (see OS_EVENT_TYPE_???) */ INT8U OSEventGrp; /* Group corresponding to tasks waiting for event to occur */ INT16U OSEventCnt; /* Semaphore Count (not used if other EVENT type) */ void *OSEventPtr; /* Pointer to message or queue structure */ INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; /* List of tasks waiting for event to occur */ } OS_EVENT; #endif
这里要特别指出的是, 在v2.52的后续版本中, 此结构体还多出了一个
OSEventName[]
, 顾名思义, 也就是用来存储 ECB 的名字了.#1
OSEventType
是事件类型, 也就是譬如 semaphore, mutex, message box, message queue 等等.譬如, 信号量 sem 和互斥量 mutex 都是使用 ECB 来描述. 因此, 在调用相应的系统函数前, 就要先检查, 被处理的 ECB 究竟是被 sem 调用了, 还是被 mutex 调用了.
#2
OSEventGrp & OSEventTbl[]
用来存放正在等待此 event 的 tasks 的信息. 其原理和系统的任务就绪表一模一样.#3
OSEventCnt
是一个16位的整型.当事件是信号量时, 其用于表示信号量的数值.
当事件是互斥量时, 高8位用于存放 PIP, Priority Inheritance Priority, 低8位用于存放当前占用此 mutex 的任务的优先级.
#4
void *OSEventPtr
元素. 可以这么说, 在消息队列和消息邮箱中, 这个指针才会被真正用到, 所以这里就先不提了.在「空余ECB列表」中, 用于指向下一个空余的ECB. 在信号量和互斥量中, 指向
NULL
指针, 也就是不被使用.#5
事件控制块的数量是有限的.
这个最大数值由
os_cfg.h
中的 #define OS_MAX_EVENTS ?
指定.系统中有一个所谓「空余ECB列表」, 由一个指针变量,
*OSEventFreeList
, 指向一串没有被使用的ECB, 形成一个单向链表. ok, 你或许已经联想到了, 和前面一篇文章的 *OSMemFreeList
一样的道理.如果需要创建一个 sem 抑或是 mutex, 那么必须先从这个单向链表中分配到一个空余ECB.
#6
如何操控某个ECB? 也就是:
如何初始化ECB? 如何将某个任务添加到某个事件的 waiting list 中? 如何在事件发生时, 确定该由 waiting list 中的哪个任务捕获该事件? 如果任务等待了太长的时间, 超过了其「忍耐的极限」, 也就是 time out 的时候, 作何处理?
带着这些疑问, 请您分别阅读源代码中这几个函数:
OS_EventWaitListInit();
OS_EventTaskWait();
OS_EventTaskRdy();
OS_EventTO();
** 注意: 这几个函数都是 kernel 的内部函数, 供其他系统函数调用.
用户空间的代码严禁直接调用. 也就是说, 您可以和我一样, 出于对内核大换血的态度来研究之, 但是, 别在用户空间的 task 中直接使用之.
---------------------------------
---------------------------------
....
---------------------------------
彻底懒得写了. 太多了. 这个东西不是线性的描述, 而是图状的描述, 所以写起来感觉格外艰难. 如果只是写「API使用指南」倒是轻松多了, 毕竟外围的 API 都做了很好的粒度和范畴切分, 可以按照 linear 的顺序一步步介绍.
而且, 感觉这个 serial 貌似已经超越了起初 study-note 的初衷, 成为「内核剖析指南」了.
ok, 罢笔. -_-
下面将我没有写完的内容在这里列出来:
任务间同步: 信号量 Semaphore; 互斥量 Mutex; 事件标志组 Event Flag Group. 任务间通信: 消息邮箱 Message box; 消息队列 Message Queue. 基础结构: 任务管理 Task management; 时间管理 Time management. 其他: 就绪表, 任务调度; 任务切换, 中断里的切换; 时钟节拍; 系统初始化 & 启动.
然后大致说一下:
#1
信号量和互斥量一定要弄清楚. 特别是互斥量的 PIP 机制, 由于内核缺乏「优先级继承」的机制, 容易出现优先级反转的问题, 所以在互斥量中人为引入 PIP, 能够在一定程度上破解优先级反转.
#2
事件标志组也很重要, 而且范围广泛, 运用很灵活. 某一组事件发生后, 所有关联的 task 全部被激活. 这一点和信号量以及互斥量不同, 这也决定了其使用的场合也不同.
在使用时, 尤其要记得
wait_type | OS_FLAG_CONSUME
的组合等待模式设置.#3
消息邮箱和消息队列非常重要, 是任务间通信的重要方式. 至于所谓「消息」, message, 本质上也就是一个 pointer 了, pointer 用于指向若干任务间通信的某个信息片.
#4
任务管理, 没得说, 创建任务删除任务挂起恢复任务都得靠它. 时间管理, 更加不用说了, 在时序系统中, 其重要性应该算是路人皆知了. 这两部分都是最基础的构件, 需要重点掌握.
#5
就绪表, 任务调度. 其实这部分的实现挺容易的. 就是讲述起来需要图文结合才行, 大家伙如果有兴趣, 就自己看看源代码, 看看我先前提到的
MicroC/OS II: The Real Time Kernel
吧.#6
任务切换, 中断里的切换. 这两者是不同的.
具体的实现和硬件联系紧密, 移植时需要用汇编编写之.
不过我只是知道原理, 暂时还没来得及具体研究针对某个处理器的移植范例. 囧
针对处理器的移植范例可以在官方站点上下载.
#7
时钟节拍. 依靠硬件的定时装置, 定时运行
OSTimeTick()
, TCB 中的 OSTCBDly
就依靠这个来定时自动减一, 一旦到0, 便进入就绪态等待调度.#8
系统初始化 & 启动. 算是一个小综合部分了. 同用户空间 task 编写联系也很紧密.
ok, 到此为止吧. 我终于体会到那些英文原版的作者为何都要在 preface 中加上一句: 感谢我的家人没有对我日复一日的敲击键盘和不理不睬感到愤怒.
我在这里也写上一句, 算是后记吧, 哈哈哈哈. 囧
I hope you, the kind reader, will not find this serial be wordy or even tedious, though myself think it somewhat really is.
I will be very happy and proud if you step into the world of μC/OS-II & RTOS with this serial of articles.
Enjoy your adventure. You brave hacker. ;)
- End of Serial -
没有评论:
发表评论
不要使用过激的暴力或者色情词汇.
不要充当勇猛小飞侠 --- 飘过 飞过 扑扑翅膀飞走 被雷得外焦里嫩地飞走.
万万不可充当小乌龟 --- 爬过.
构建河蟹社会 责任你有 我有 大家有 -_-