第三章:内核结构
临界段:也就是需要保证原子操作的代码段。由这里的说法,绝大多数或者说所有的(
?
)内核都是都是使用开关中断的方式来处理。在
uC/OS-II
中,使用
OS_ENTER_CRITICAL()
和
OS_EXIT_CRITICAL()
两个定义在
OS_CPU.H
中的宏来开关中断。
任务:提到一个概念,说任务销毁时,仅仅是内核不再理会这个任务了,即代码不再运行了,任务绝对不会返回什么。其实如果有返回,又返回给谁呢?
任务控制块:
uC/OS-II
使用它来保存一个任务的状态,一个
OS_TCB
数据结构(即任务控制块)记录一个任务的状态。其中包括任务的栈地址、传给任务的消息
(
任务的消息
?
和消息队列是什么关系?
)
、任务的延时、优先级、任务状态属性等等。
就绪表:
uC/OS-II
最多支持
64
个任务(包括系统任务),使用一个
BYTE
(
OSRdyGrp
)和一个最大
(
与任务数相关
)BYTE[8]
的数组(
OSRdyTbl[]
)表示它们的就绪状态。
OSRdyGrp
的第
n
位代表第
n
组中是
(1)
否
(0)
存在就绪任务,
OSRdyTbl[n]
的第
n
位表示这个任务是
(1)
否
(0)
在就绪态。
uC/OS-II
在这里使用了一个简单的查找表快速的获取任务号,查找表的结构
OSMapTbl[n]
是第
n
位
(0
开始
)
为
1
,其它为
0
的
BYTE
。如
OSMapTbl[0] = 1
、
OSMapTbl[1] = 2
、
OSMapTbl[2] = 4…
这样的表可以方便的由数字得到特定位数为
1
的字节,相当于
1<<n
。同样,可以用一个相反功能的查找表
(OSUnMapTbl)
来得到优先级最高的任务。
OSUnMapTbl[n]
相当于
for
(i=0;(n&1)!=1 && n!=0;i++) n >>= 1;
如
OSUnMapTbl[4] = 2
,当
1
不止一位时,取最低位(因为它代表的优先级最高)。这样便可达到常数时间内查找最高优先级任务的目的。
任务切换:确认要进行任务切换以后,被挂起任务的寄存器入栈,即将运行的任务寄存器恢复。在
uC/OS-II
中,就绪任务的栈结构与刚刚发生中断一致,既寄存器在栈中,所以内核实际上只需要一个中断返回指令。有个问题就是在什么地方做任务的切换(包括判断切换),显然不是中断,因为中断中的切换有独有的函数,在什么情况下切换函数会到前台来?还是说我们直接在被挂函数中调用调度函数(这个函数在等待信号量等函数之中被调用),如果需要挂起再在这个位置保存现场,下次返回的时候,也就是调度函数返回的位置?
——
这个似乎说的过去,不过还需要确认。
空闲任务:在
uC/OS-II
中这个任务总是最低优先级,而且不会被挂起。它仅仅把一个
32
位变量不停的做
+1
操作。不过这个
+1
的过程也是关中断的,因为可能不只一条指令,时间很短,所以也没有太大的影响。另外有一个统计任务优先级稍高,它们与初始化之初的唯一一个高优先任务配合保证一个时钟节拍下空闲任务的完全运行,并由统计任务记录下这个最大的累加,以后便可根据每个时钟节拍下得到的其它累加,计算
CPU
使用率了。这也是多任务启动之初,主任务需要延时的原因。
中断引起的任务切换:进入中断服务程序时,我们在当前栈顶保存了任务的寄存器状态,在中断处理程序中,又调用了任务切换函数
(uC/OS-II
中为
OSIntCtxSw)
,
(
调用的过程就是压栈的过程
)
如果需要做任务切换,那么在这个函数中,当前任务就已经被切换到了挂起状态,为了和上面提到的切换方式保持一致,我们需要调整一下栈的指针,保证当前栈顶为进入中断之前存储的寄存器状态,如下图所示。