RTX51 Tiny是应用于8051系列单片机的小型多任务实时操作系统,它完全集成在Keil C51编译器中,体积小,简洁高效,能够实现独立不相关的多任务并发执行,具体相关伙伴组任务之间的同步机制。 被广泛应用在8051系列单片机的软件开发项目中。
当Tiny 系统打开时间片(timeshare≠0)时,系统中多任务由内核按时间片轮转调度,并发运行。但这时只有独立不相关任务能够按时间片并发推进;而对于有共享资源关联的(非伙伴组之间的)相关任务,Tiny却缺少对共享资源争用的管理工具。信号量是一种有效的、对共享资源进行保护的管理工具;管程是具有同等表达能力的另一种高级同步管理工具。
信号量实现两种管理机制:互斥和同步,两者完全由程序员负责。
管程相应实现两种管理机制:互斥锁和条件变量。理论上编译器负责互斥自动加锁部分,条件变量部分仍需程序员负责,这使程序员出错几率减少一半。管程机制的目标是为了更易于编写正确的程序,管程相对信号量更易于检错和控制。
信号量设计方法见参考文献[1]。信号量的优点是通用性,可以做成库函数,一次设计,长久应用。而管程,理论上是编译器依赖,与条件变量一起,随具体项目设计而不同。
Keil C51没有管程支持,影响管程C语言应用的原因大多是一种习惯概念:“管程是语言概念,C语言并不支持它。……要使用管程,就需要一种带有管程的语言。”实际上,对Tiny这样的小系统,富有好奇心和探索欲的程序员可以自己代劳,替代编译器完成管程结构成分的功能。理论上进入管程时的互斥由编译器负责,而通常的做法是用一个互斥锁或二元信号量。当一个任务调用管程过程时。该过程中的前几条指令将检查在管程中是否有其他的活动任务。这是程序员替代编译器完成管程语言成分时所需做的少量工作。
管程结构如图1所示,管程把所有共享资源以及一组针对这些共享资源的操作过程集中在一个软件模块Monitor 内部。并发争用管程内部共享资源的外部多任务,只能通过调用管程内部过程来间接使用管程内部共享资源。管程的互斥特性保证了外部无序并发任务只能以串行化方式顺序通过管程(临界区),以协作方式顺序使用共享资源。管程结构(见图1)的右半部实现互斥机制;左半部实现条件变量同步机制。
图1 管程结构
下面举实际应用中的例子简单说明管程功能及应用。
例1无条件临界区 (实现图1右半部互斥机制)
假定有三个RTX51TNY任务,task1~task3,并发使用单一串口资源分别输出数据s1~s3,这里串口是共享资源。
编者注:例1源程序略。
三任务借助回调函数方法进入管程Monitor_f((*f)(void *),void*),而管程函数Monitor_f( )在入口处添加了一把互斥锁mutex,使管程函数具有互斥特征,每次只允许一个申请使用管程内共享资源的外部活动任务进入管程。
申请进入管程的任务,必须先拿到互斥锁mutex ,
获得使用权,使用完资源后,退出管程前又必须先归还锁,释放使用权。获取锁成功的任务,必须按照“以关锁操作开始,以开锁操作结束”的规则进行。
互斥锁是信号量锁的简化应用,差别在于:当取锁失败时,管程互斥锁调用os_switch_task( ) 调度程序,主动放弃CPU给下一任务,并不阻塞当前任务,仍为就绪态,因此无需等待其他任务唤醒,也不像自旋锁那样忙等待;在该任务下次被调度时,它再一次对锁进行测试。
当取锁成功时,互斥锁能够自动关锁以防止其他任务进入;这避免了程序员“以关锁操作开始”的疏忽。
例2条件临界区
例1中,管程提供了一种实现互斥的简便途径;如果仅仅替代编译器的互斥加锁功能,这好像已经够用了。但管程还需要有同步机制,还需要一种方法使进入管程的任务能够像使用信号量一样实现同步机制。实现前驱或资源条件临界区,即进入管程的任务在发现继续运行条件不满足时,能够阻塞自己。这是应该由程序员主要负责的部分,编译器无能为力。
解决方法就是引人条件变量以及利用RTX51 Tiny已有的相关的两个操作:os_wait( )和os_send_signal( )。当一个管程过程发现它无法继续运行时(例如前驱条件或者资源不满足时),它会在某个条件变量上执行os_wait( )操作。该操作导致调用任务自身阻塞,并且还将另一个以前等在管程之外的任务调入管程。一个进入管程无法继续运行,被迫进入自身阻塞(等待条件满足)的任务,必须按下列规则进行:
◆ 释放互斥锁给管程入口;
◆ 进入条件等待队列;
◆ 睡眠。等待伙伴唤醒。
假定例1中三个任务要求按图2所示逻辑规则顺序轮流使用串口资源输出,管程内部就需要有条件变量支持。条件变量同步机制理论上是由程序员负责的部分,编译器(替代)仍只负责互斥锁部分。
图2 三任务轮流执行逻辑规则
编者注:例2源程序略。
这个例子当然有更简单的解法。测试流程如图3所示,图中写成完整if嵌套形式,是为了突出条件变量的逻辑表达式概念。退出管程前的任务测试程序,代码已经裁剪掉一半(在等待队列初始化全0条件下)。任务1和任务2按照逻辑表达式规则判定(predicate),从不会进入条件变量等待队列,只有3以上的任务才会进入条件变量队列等待。
图3 管程内活动进程退出管程前对伙伴组条件变量队列的测试
从例1看到,C51带有Test and Set Lock语句(由8051硬件功能确定),这是C51能够简单实现管程入口互斥锁的首要条件,也即管程(模块)实现的互斥机制部分(理论上编译器负责的加锁部分)。
从例2看到,RTX51 Tiny具有wait/signal函数,自身已能完成伙伴组间同步机制,因而能够方便实现条件变量机制。这是管程(模块)实现的同步机制部分。
例3经典同步/互斥问题(生产者与消费者问题)
操作系统文化中有一些经典的进程同步问题被广为讨论和分析,它们是从各种类型的同步问题中归纳、抽象出来的一般性模型;每个新的同步/互斥机制的发明都希望通过这些经典问题来展示自己的精妙之处。生产者与消费者问题被普遍用来检验同步/互斥机制的正确性。
两个任务:生产者/消费者任务通过共享一个公共的固定大小的缓冲区(例3中为8字节。)而关联。生产者将信息(产品)存入缓冲区供消费;消费者从缓冲区取出信息(产品)以消费。如果生产速度大于消费,供大于求,缓冲区将很快填满,生产者必须睡眠以停止生产,等待消费者从缓冲区中取走一些数据项消费掉后再唤醒生产者。同样,如果消费速度大于生产,供不应求,缓冲区很快为空,消费者必须睡眠以等待生产者生产一些数据项后再唤醒消费者。例3源程序略——编者注。