μC/OS-II 源码阅读笔记 —— 内核深度剖析

 

一个程序猿郁结十年的青苹果

Bush 2014-4-24

前言

此文发表在此,由于正吃菜的我才疏学浅,文中难免有错误的地方,欢迎看官和过客指正批评,痛骂也无妨,我虚心接受所有的鄙视。

目录

概述

缩略语

01  何谓任务?

02  任务与中断有啥异同?

03  何谓原子性操作?

04  任务栈是怎么回事?

05  何谓现场?

06  临界保护对子中C语言的变量跟汇编子函数中的寄存器是怎样联系起来的?

07  任务切换时具体做些什么?

08  任务切换在什么时候发生?

09  任务被切后是个什么样子?

10  关于就绪表中高效的调度算法

11  系统启动时运行的第一个任务,第一步是怎么运行的?

12  空闲任务 OS_TaskIdle 在什么时候运行?

13  μC/OS-II中的中断同单片机程序的中断有什么异同?

14  什么是函数的可重入性?其意义何在?

15  同步信号量的内部机理是什么?

16  互斥信号量的内部机理是什么?

17  邮箱的内部机理是什么?

18  消息队列的内部机理是什么?

19  标志组的内部机理是什么?

20  μC/OS-II的内存管理机制是什么?

21  什么是堆?mallocfree是怎么运作的?

22  操作系统到底是什么东东?

后记

概述:

该笔记对应μC/OS-II的源码版本是V252,对照阅读的参考书是北京航空航天大学出版社 20035月第一版《嵌入式实时操作系统 μC/OS-II(第二版)》(English nameMicroC/OS-II The Real-Time Kernel Second EdionAuthorJean J.Labrosse,邵贝贝翻译)。

该笔记对应的硬件平台是STM32F107,编译器是安装了 ARM-MDK 453  Keil μVision4,代码阅读工具是SourceInsight3.5

该笔记并非源代码的详细讲解,亦非μC/OS-II的使用说明,而是汇总了阅读源码过程中产生的疑问及解答,进而从中归纳总结出μC/OS-II系统的内在机理,对于想从本质和源头探索操作系统的程序猿或许有点参考帮助,或许能够启发更优质的使用μC/OS-II的方法,甚者若能就实际情况来优化μC/OS-II的内核以提高软件的质量则更当令此文欣慰了。所谓学然后知不足,教然后知困。本文并非教学总结,无能面面俱到,假如看官正为类似问题而纠结,那么若能知遇此文,就算是缘分了。

缩略语:

缩写代号 含义

OS 操作系统

μC/OS-II μC/OS-II内核

TCB TASK CONTROL BLOCK(任务控制块)

ECB EVENT CONTROL BLOCK(事件控制块)

SP CPU中的堆栈指针寄存器

CM3 ARM公司 Cortex M3单片机内核

MSP CM3中的主堆栈指针寄存器,是SP的一个化身

PSP CM3中的进程堆栈指针寄存器,是SP的一个化身

PendSVHandler PendSV中断服务函数

TickHandler CM3SysTick中断服务函数

系统 主要指μC/OS-II,有时指一般意义上的系统,有时指操作系统,根据上下文来确定

任务 除了一般意义上的任务外,有些地方还包括中断,需根据上下文来确定,请看完01

上台 任务占据CPU

下台 任务退出CPU

被切 任务在任务切换中退出CPU

绿色的文字 都是源代码的摘录

01 何谓任务?

在此打算辨清任务、进程、线程这些概念。

欲说任务,必先说任务控制块(TCB)TCB是一个全局变量,它们在μC/OS-II中的组织形式是一个链表化了的数组:OS_TCB OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS],每个数组元素是一个TCB。它与任务一一对应,不管任务存在与否,TCB这个空间一直存在,只不过是个空的TCB而已。这个TCB数组由μC/OS-II负责管理,通过OSTaskCreate函数分配给任务,通过OSTaskDel函数收回到全局指针OSTCBFreeList所串的链表中。欲说任务,还必须说任务函数。任务函数是一个死循环,类似单片机程序中的主循环。单片机中的主循环,除了中断耽搁了一会儿,一直在主循环中转圈。任务函数的死循环也是这样,除了中断和其它任务耽搁了一段时间,也是一直在死循环里转圈。欲说任务,也必须说任务栈。任务栈是一段全局的内存,由程序猿创建,只限程序猿指定的任务使用,μC/OS-II负责这段全局内存的管理。把任务比作人,TCB相当于人的骨骼,任务函数相当于人的肌肉,程序猿给任务设定的功能相当于人的性格,任务函数的所有运行,相当于人的行为,任务栈相当于人的家,μC/OS-II相当于国王,程序猿相当于天。任务函数一圈圈的周而运行,好似人一年年的轮回。任务占据了CPU好似人睡醒的白天,任务退出CPU好似人睡觉的夜晚。这就是任务,其又名曰进程(还有线程基本也是这个意思)。什么是属于任务私有的呢?TCBμC/OS-II的,由任务使用,任务栈和任务函数貌似是任务的私有物,其它的,任务函数调用的函数,包括系统函数和程序猿设计的应用函数,是与其它任务共有的。任务函数调用的函数,好似过眼云烟,只是那时那刻,以那个任务的名义,经过了CPU,来无影去无踪。其实那就是任务的精神,精神只在当下有意义,也就是经过CPU16个寄存器中时才有实际意义,而且必须依托于一个具体的任务才有意义,除此之外,它们只不过是陈旧的记忆或者遥远的空想。可见,在OS的世界里,也跳不出王阳明参悟的真理——“知行合一

TCB的具体定义如下,这是精简化了的定义,只是为了理解TCB的目的,以删其繁。其成员共分五组,以空格间隔。第一组,OSTCBStkPtr是任务栈的台账;第二组,是μC/OS-II的户口稽查员,用来查找这个TCB;第三组,是任务通信的台账;第四组是任务睡眠、任务状态、任务级别的台账;第五组,是μC/OS-II的小兵,专用于任务切换的快速计算。这个TCB在任务创建时由OS_TCBInit函数初始化。

/*

*********************************************************************************************************

*                                          TASK CONTROL BLOCK

*********************************************************************************************************

*/

typedef struct os_tcb {

    OS_STK        *OSTCBStkPtr;        /* Pointer to current top of stack                                */

    struct os_tcb *OSTCBNext;          /* Pointer to next     TCB in the TCB list                      */

    struct os_tcb *OSTCBPrev;          /* Pointer to previous TCB in the TCB list                   */

    OS_EVENT      *OSTCBEventPtr;      /* Pointer to event control block                               */

    void          *OSTCBMsg;           /* Message received from OSMboxPost() or OSQPost()   */

    OS_FLAG_NODE  *OSTCBFlagNode;      /* Pointer to event flag node                            */

    OS_FLAGS       OSTCBFlagsRdy;      /* Event flags that made task ready to run             */

    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                  */

} OS_TCB;

02 任务与中断有啥异同?

相同点:任务和中断都有独立性,都是主动运行,即任务函数不是因为其它函数的调用才运行。任务函数和中断服务函数都不准return。两者都可以用Post类通信机制以及OSTaskResume

相异点:任务受μC/OS-II制约,中断不受μC/OS-II制约,甚至μC/OS-II还要让着中断,因为PendSV中断的优先级最低,而且μC/OS-II还得祈求中断给μC/OS-II打招呼。任务可以做到很大,而中断则要求短小精悍。任务有自己的栈空间,中断只是共用中断们的主堆栈。任务主要是内部的行为,而中断主要是外部事件。任务可以用所有通信机制,中断不可以用Pend类通信。

概而言之,在μC/OS-II的世界里,中断是比任务地位更高的存在。若把程序猿比作天,任务居于人道,μC/OS-II是国王,而中断显然不属于人道,也不服从μC/OS-II的管理。那中断算是哪一道的呢?鬼道?阿修罗道?中断比人道的任务有点高明,但毕竟有缺陷,因此还是逃不出宿命,那是程序猿给安排的使命。

03 何谓原子性操作?

所谓原子性操作就是一个在操作期间不能被打断能连续运行的操作。操作系统中经常用的一个术语——原语,就是指原子性操作。实际中的原子性操作基本有两种情况:第一种是不能被中断的汇编指令;第二种是用关中断开中断对子保护起来的代码,无论代码有多长,只要在开始处关了中断,那么它必然是原子性的。

04 任务栈是怎么回事?

任务栈是程序猿定义的全局内存,要么是静态数组,要么是动态数组,在任务创建时,它作为参数由程序猿分配给任务。任务在创建时,在任务栈中初始化一个CPU现场,其中除了xPSRPCLRR0外,其它的都是假的,并保存栈顶至TCB的第一个成员OSTCBStkPtr中。任务栈通过任务切换正式启用,这句话的意思是任务栈在任务上台时,把栈顶指针恢复到CM3中的SP中。任务被切后会把任务的现场保存到任务栈中,并更新栈顶至OSTCBStkPtr。任务上台时,μC/OS-II通过OSTCBStkPtr找到任务栈,并恢复现场。因此可以说,OSTCBStkPtr是任务栈(程序猿定义的全局内存)与任务的纽带。形象的说,任务栈就是任务的一亩三分地,就是程序猿给任务安排的居所。

这儿需要针对性说明的是,CM3中的堆栈指针寄存器有两个:MSPPSPCM3上的μC/OS-II对这两个堆栈指针寄存器的分工是,中断用MSP,任务用PSP,因此任务和中断的栈互不干涉。假若都用MSP,则中断会像寄生虫一样借用任务的栈,哪个任务运行期间发生的中断,就借用那个任务的栈。并且任务切换都会是在上一个任务栈的空间里运行切换操作(因为切换操作也是中断)。这样是很不可取的,不仅有可能导致任务栈的溢出,而且还会导致bootload时创建的Stack闲置不用,是浪费。bootload的中的Stack是否在运行到main函数时就撤除了?这个思考题留待以后验证。回答:不会撤除。这个问题同问题21一样,涉及到了运行库的问题。

05 何谓现场?

这个问题其实不属于OS的范畴,这是微机原理的知识,但却是理解OS的基础。现场就是CPU的寄存器们,它们是离ALU最近的存在,在CM3中,它们是R0-R15xPSR,共1632位寄存器。任何函数,不管是任务函数还是中断服务函数,必须经过现场寄存器才能达到ALU。函数的运行,都是经流过这些现场寄存器们来完成的。每个时刻,现场寄存器们都对应了函数的最微小的一步。通过现场,能得到正在执行的函数的当下状态。把ALU比作皇帝,现场寄存器们就是皇宫里干活的人,太监及朝臣之类。相比之下,代码区的函数就是地方上的外臣了。每个时刻,现场都对应了一个缘分,这就是OS世界里的当下。

06 临界保护对子中C语言的变量跟汇编子函数中的寄存器是怎样联系起来的?

这是一个特殊问题,在ARM中有明确规定,由文档《ARM Architecture Procedure Call Standard(AAPCS, Ref5)》给出。当主调函数传递实参时使用R0-R3R0传递第一个,R1传递第二个......。子函数的返回值写到R0中,见《Cortex-M3权威指南》中文版 P152。实际测试中,发现在KEIL STM32F107环境下,当参数小于等于4个时是如此,当参数大于4个时(N=10个做实验),是先R0传递第10个,R1传递第9个,R2传递第8个,R3传递第7个,然后把R0-R3依次压入栈中,然后,R0传递第6个,R1传递第5个,R2传递第4个,R3传递第3个,然后再把

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值