看完UC/OS操作系统,mark一下。该系统的主要目的是对任务的管理,对内存的管理。该系统对任务的管理及实时性见UC/OS框架部分介绍。该系统大多采用C来写,少部分使用了汇编(系统启动时开启中断并触发中断部分,任务调度中触发中断部分,进入退出临界段)。
US/OS框架
UC/OS主要使用了任务控制块(TCB),事件控制块,信号量集,内存控制块来实现任务及内存的管理。如下将简说各个快的形成。
1、UC/OS中重要模块
1.1 简说任务控制块的形成(TCB)
要达到实时的作用,就会出现在运行一个任务时会跳到其他的任务中去运行。 若回到原来的任务,需要在原来的位置处开始运行。要完成该任务,就得需开辟一个任务堆栈区,用于保存各寄存器组的信息,当回到原任务时,再从堆栈中恢复各寄存器的内容。由于任务有很多个,为了快速查找目前需要执行的任务,因此引入了优先级,又因操作系统要求任务切换时间要与任务的多少没关系,因此引入位图相关的量。一个任务的执行,可能需要另外的一个或多个任务执行到某时才执行,或者另外的任务会发送一段原任务需要使用的东东,因此引入了事件(包含信号量、互斥信号量、消息邮箱、消息队列)及信号量集;UC/OS是可剥夺行内核,且不能让一个任务一直占用CPU,因此出现了一个延时参数,当延时时间是时钟节拍的整数倍时,就可把该任务,以让其他的任务运行。具体可以对照TCB控制块看一下。
1.2 事件控制块的形成
事件包含了信号量、互斥信号量、消息邮箱、消息队列。该控制块的指针保存在TCB中。我觉得放在TCB中,主要是因为,哪个任务要使用,就在其任务执行函数中申请,因此就可通过当前任务TCB的指针操作时间控制块。
个人觉得事件,和平裸跑时使用的标志量类似,只是一个条件。只是操作系统把其统一起来,并可以自由选择类型。
由上面介绍可知,事件包含几种类型,因此时间控制块的成员中有表明事件类型的量;任务运行需要信号量,因此就需要信号表示的相关量,又因信号量可能是一个数值,也可能是一段内容,因此事件控制块内有变量及指针(主要指向消息的指针)。既然有消息,那么消息可能有多个,因此需引入了消息队列(使用消息队列发送消息时,需要提前申请空间装消息的地址。消息队列中包含指向消息的起始地址,插入及出队列的指针,消息的大小等与消息有关的信息,具体查看其结构);当申请事件时,需要查看申请任务(等待任务)中那个的优先级最高,因此依据位图,又引入了一个变量(位与等待任务的第几位元素对应,方便快速查找且不因等待任务的多少而不可估计查找时间)及一个任务等待表。
1.3 信号量集
信号量集是在信号量的思想上引入的(任务运行需要条件)。当然,任务运行可能需要多个条件满足才运行,因此,运入了信号量集。信号量集没有放在TCB中。个人觉得主要是因为任务对信号量集中的条件有选择地,不同的任务对其条件选择不一样,可能发送一次可以应对多个任务的申请或多个任务发送才能满足一个申请,而不像事件那样,只针对一个任务。也是因为这个原因,因此信号量集中需要有等待任务链表,用于当发送信号量集时遍历查询那些满足,满足的则去除等待,进入就绪。此部分中各变量的组成不再多少,直接使用其结构来说。
typedef struct os_flag_grp { /* Event Flag Group */
INT8U OSFlagType; /* 类型。用于校验或者以后扩展其他类似信号 */
void *OSFlagWaitList; /* 指向信号量集等待任务链表。发送信号量集时要遍历查询那些等待的满足进入就绪的条件 */
OS_FLAGS OSFlagFlags; /* 接收到的信号 */
#if OS_FLAG_NAME_EN > 0u
INT8U *OSFlagName;
#endif
} OS_FLAG_GRP;
typedef struct os_flag_node { /* Event Flag Wait List Node */
void *OSFlagNodeNext; /* Pointer to next NODE in wait list */
void *OSFlagNodePrev; /* Pointer to previous NODE in wait list */
void *OSFlagNodeTCB; /* 等待该信号的任务TCB指针。任务就绪时可以使用该指针把其任务进入就绪 */
void *OSFlagNodeFlagGrp; /* Pointer to Event Flag Group。感觉用处不是很大,只在把节点脱离等待任务表时用到,不适用该量,一样可以操作*/
OS_FLAGS OSFlagNodeFlags; /* Event flag to wait on.等待的信号选择 */
INT8U OSFlagNodeWaitType; /* Type of wait: 运算关系。用于任务运行时的条件筛选 */
/* OS_FLAG_WAIT_AND */
/* OS_FLAG_WAIT_ALL */
/* OS_FLAG_WAIT_OR */
/* OS_FLAG_WAIT_ANY */
} OS_FLAG_NODE;
在后期的版本中,在TCB中引入了指向信号量集的指针。个人觉得主要是为了方便删除任务时删除其该任务对于的信号量集。
1.4 内存管理
内存管理主要是把零碎的空间通过指针连接起来,方便管理。为了方便,每个内存块定义为若干个内存块,且每个内存块的大小一致。如果是每个内存块的大大小不一致,在申请内存时也不知申请多少合适。除非每个内存块包含个各自的大小,但这在操作上有增加了复杂度。目前UC/OS中使用的是每个内存块大小一致的情况。
1.5 软件定时器模块
定时器模块是在后期的版本中引入的,功能类似于通过寄存器设置定时器功能。
在UC/OS中实现的软件定时器,也是把其当做一个任务。因要把功能作为类似于硬件定时器一样,因此该模块在初始化的时候便把最高优先级分配给其任务。(最开始是以申请一个信号的方式,该信号在UC/OS中的时钟节拍中发送)
该功能的实现是:以UC/OS的时钟节拍为基础,通过计数x次后发送一个信号,此时定时器任务才可以执行。
当然,定时器任务不可能才有一个,应该有多个。而定时器运行的母体是在最高优先级中运行,为达到使用过个定时器的目的,可以通过一个函数指针数组的形式让任务在母体中遍历那些软定时任务可以执行了。
对于定时器,当然有定时周期了,因此软定时结构中必有与时间相关的量及运行时时间对比的量。其他的量看一下软定时的结构,对照程序看一下就明白
了。
2、简说UC/OS的运行
(1) 启动UC/OS,查找当前就绪的最高优先级任务,并引发一个中断,使其保存当前寄存器的内容,并把任务堆栈的内容压入各寄存器;(待恢复任务时再恢复灰数据);
(2) 运行任务中若出现延时(当延时大于时钟节拍时,便会挂起当前任务),或者有高优先级的任务就绪时就会引发一次任务调度(调度主要就是把当前寄存器内容保存,并把将要运行的任务的堆栈中的内容(xpsr,PC,SP,LR等寄存器)推入栈中);(具体实现方式见后续文章中介绍)
(3) 若开启了其他硬件中断,会自然切换到其中断中运行;(这情况下r4-r11是否需要保存我还不清楚。但我猜测可能硬件中断中不会改变r4-r11的内容。ucos中需要保存,是因为运行切换到其他任务中时,可能会改变r4-r11的内容。此处在书上及网络上暂时没有查到相关的说明)