1. 整个操作系统启动的大致过程是这样的:
- 初始化系统定时器,用于操作系统的心跳
- 初始化操作系统(调用OSInit()函数),这个函数内部大致执行以下的工作
- 初始化变量
- 初始化各个链表:任务控制块链表、事件链表
- 创建Idle和统计线程
- 创建用户任务
- 开启任务调度
2. 操作系统开启任务调度后流程:
执行第一个任务->系统Tick中断->系统时钟处理->搜寻最高优先级任务->如果当前任务不是最高优先级任务,则触发一次任务调度->否则进行正常的中断返回
3. 如何阅读操作系统源码
在明白了uC/OS - II的大致流程后,有点迷茫了,大概有这样几个困惑:
- 不知道该从哪里下手
- 我是想完完整整把uC/OS - II搞懂,但不知道怎样才算完完整整搞懂
- 有人说会用就行,不知道怎样才算会用
对上述困惑,也有了以下的一些分析:
- 手里有一本任哲的《嵌入式实时操作系统 uC/OS - II原理及应用(第3版)》,已经看过一遍了,下面可以从线程开始,接着任务间同步和通信,内存管理这样一块一块的看,看一点实践一点
- 手里还有邵贝贝的翻译的《嵌入式实时操作系统uC/OS - II(第2版)》,大家都说这本书比较权威,但可能不太适合初学者,我觉得到现在也基本过了初学者的阶段了,应该可以系统地看一遍,慢慢找寻知识间的联系,估计最终能明白什么叫完完整整搞懂uC/OS - II
- 对于怎样才算会用,我觉得,其实不管RTT也好,uC/OS - II也好,只要能实现自己想要的功能的,就算是会用了。而我阅读源码,只是为了懂的更深一点,心里总是觉得一些东西不搞懂总归会有一天被绊倒,也不知道对不对,按照马斯洛层次需求理论,可能我处在自我实现的需要这一阶段吧。
4. 什么叫做移植
为特定的处理器编写特定代码的过程成为“移植”1。
5. 临界段
在阅读c语言教程时经常会碰到临界段这个概念,从字面上一直不好理解。查阅了英文资料,临界段的英文原文叫做Critical Section或者Critical Region,意思是特别重要的、不能被中断打断执行的代码段。所以一般把关中断的操作叫做进入临界段,开中断的操作叫做退出临界段2。
6. 上下文切换
上下文切换一词在操作系统中经常提到,对应英文为Context Switches,本质上是指CPU寄存器内容的切换,实际效果对应于系统运行任务的切换3。
7. CPU利用率
作为系统设计的一条原则,CPU的利用率应该小于60%-70%4。
8. 任务
操作系统中,任务通常是无限循环5,形如:
void YourTask(void *pdata)
{
for (;;)
{
/** 用户操作 */
do something...
/** 调用系统函数 */
OSTimeDly(1);
}
}
9. OSUnMapTbl表的由来
在uC/OS - II中,使任务进入就绪状态的代码如下:
OSRdyGrp |= OSMapTbl[prio>>3];
OSRdyTbl[prio>>3] |= OSMapTbl[prio&0x07];
其中,OSMapTbl[]
数组内容如下:
下标 | 位掩码(二进制) |
---|---|
0 | 00000001 |
1 | 00000010 |
2 | 00000100 |
3 | 00001000 |
4 | 00010000 |
5 | 00100000 |
6 | 01000000 |
7 | 10000000 |
而使得任务脱离就绪状态的代码如下:
if ((OSRdyTbl[prio>>3] &= ~OSMapTbl[prio&0x07]) == 0)
{
OSRdyGrp &= ~OSMapTbl[prio>>3];
}
在获取当前优先级最高的任务时,为了保证系统的实时性,即不管最高优先级任务在哪,系统查询到最高优先级任务所花费的时间要固定。因此不能采用常规的循环查询的方法,此处,uC/OS - II采用了直接查表的方法,每次都能以相同的时间获取最高优先级任务。原理6如下:
1. 先搞个例子感受下
某一时刻OSRdyGrp
的值为0b00010001
,而此时OSRdyTbl[]
中的值为:
OSRdyTbl[0] = 0b00000110;
OSRdyTbl[4] = 0b00010000;
则时刻系统中就绪任务优先级如下:
0 * 8 + 1 = 1;
0 * 8 + 2 = 2;
4 * 8 + 4 = 36;
即系统中的优先级为1,2,36
2. 分析
在上述例子中,假设OSRdyGrp
二进制表示下最低位为1的位置为Y
,OSRdyTbl[Y]
最低位为1的位置为X
,则系统中最高优先级为:
prio = Y * 8 + X;
因此,要想求得系统最高优先级任务,只需把0x00 - 0xFF
(OSRdyGrp
和OSRdyTbl[i]
都是8位二进制数,共有0x00 - 0xFF
种二进制表示)二进制表示的最低位为1的位置列成表就行:
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 */
};
最终,求最高优先级任务代码如下:
Y = OSUnMapTbl[OSRdyGrp];
X = OSUnMapTbl[OSRdyTbl[Y]];
prio = Y * 8 + X;
10. 为何要看操作系统?
看懂一个操作系统,有以下好处:
- 在开发时能够对系统的特性了解的更深,方便功能的实现
- 学习高手写代码的思路
- 在使用同类操作系统时能够很快上手
11. 何时开关中断?
当中断程序可能会改变一段代码中用到的某些变量的值的时候,就需要关中断来保证代码执行的正确性。执行完这段代码后要立即开中断保证系统对异步事件的处理速度(实时性)。上述的那段代码叫做临界段。
12. 2015-04-23
感觉要自己写出汇编部分的代码还是比较困难