第十一讲 同步原语 *
*********************
2007/03/09
[email protected] www.armecos.com
ecos是多线程系统,并发执行造成了一些新问题的产生:多线程协同工作、对临界资源的竞争、线程间通信、线程间同步等等。其实,所有的多任务系统都会遇到类似问题,计算机专家们总结了很多抽象模型来应对,方法手段很多,各有特色,每种操作系统可能只实现了某个子集。ecos内核的同步机制提供了许多同步原语,包括:互斥、条件变量、信号量、信箱、事件标志和Spinlock等。
抽象出来的同步原语操作主要包括:创建、删除、等待(阻塞/超时阻塞/非阻塞)、释放、设置、广播、查询数据、查询状态。
虽然操作大同小异,但每种同步原语的含义和适用情况不同,下面详细介绍各种原语的使用方法和注意事项。
==========
* 互斥体 *
==========
互斥体用于实现线程对资源的安全共享。适用于线程间或线程与滞后中断服务程序DSR访问同一临界资源时的安全保护。
考虑下面的例子:
static volatile int counter = 0;
void test(void)
{
......
counter++;
}
假设在某个时候counter的值为16,此时有两个同一优先级的线程A和B,它们都调用上面的test函数。线程A读取counter的值,并将其值加1,此时,counter为17。线程B也做同样的操作,counter的值为18。但是如果线程A在读取counter的值为16后,在将其值加1之前调度器调度运行线程B,此时,线程B读取的仍然是counter原来的值16,操作完成后counter变为17。这样,counter的值只增加了1,而不是2,因此最后counter的值只是17,而不是18。这足以说明该应用程序的运行是不可靠的。
使用互斥体就可以安全地操作counter全局变量:
static volatile int counter = 0;
static cyg_mutex_t lock;
void test(void)
{
......
cyg_mutex_lock(&lock);
counter++;
cyg_mutex_unlock(&lock);
}
互斥体的使用可能会引起优先级倒置问题的出现。假设有三个不同优先级的线程A、B、C,优先级A>B>C。A和B由于等待事件而处于阻塞状态,C得以运行。线程C在进入临界区时锁定了一个互斥体。当线程A、B被唤醒时,线程A要等待同一个互斥体,但它在线程C离开临界区并释放互斥体之前不得不等待。与此同时,线程B却可以毫无问题地正常运行。由于线程C比线程B优先级低,它在B被阻塞前将没有机会运行。这样线程A就不能运行。其结果就是高优先级的线程A由于优先级比它低的线程B的原因而无法运行,这就发生了优先级倒置。
解决优先级倒置问题普遍使用的技术是:优先级置顶协议(Priority Ceiling Protocol)和优先级继承协议(Priority Inheritance Protocol)。
优先级置顶意味着占有互斥体的线程在运行时的优先级比任何其他可以获取该互斥体的线程的优先级都要高。ecos组件包通常无法知道系统中各种线程的详细信息,因此无法对组件包内部使用的互斥体设置合适的置顶优先级。设置高了会影响调度操作。
优先级继承将占有互斥体的线程优先级提升到所有正在等待该互斥体的线程优先级的最高值。当一个线程等待正被另一优先级较低的线程占有的互斥体时,拥有该互斥体的线程优先级被提升到正在等待该互斥体的线程优先级,优先级继承比优先级置顶效率高,不过增加了同步调用开销,而且实现起来比优先级置顶复杂。
初始化 cyg_mutex_init
删除 cyg_mutex_destroy
锁定 cyg_mutex_lock
尝试锁定 cyg_mutex_trylock
解锁 cyg_mutex_unlock
释放 cyg_mutex_release
设置置顶优先级 cyg_mutex_set_ceiling
设置协议 cyg_mutex_set_protocol
============
* 条件变量 *
============
条件变量是允许线程同时给多个线程发信号的一个同步机制。当线程等待一个条件变量时,它在进入等待状态之前将释放互斥体,在被唤醒后又重新拥有互斥体。这种操作是原子操作。
举例说明见例1。
初始化 cyg_cond_init
删除 cyg_cond_destroy
等待 cyg_cond_wait
唤醒 cyg_cond_signal
广播 cyg_cond_broadcast
带超时等待 cyg_cond_timed_wait
==========
* 信号量 *
==========
信号量是一个允许线程等待直到事件发生的同步原语。每个信号量都有一个整数计数器,如果计数器为0,那么等待该信号量的线程将被阻塞。如果计数器大于0,那么等待的线程将消耗一个事件,即计数器减1。唤醒信号量将对计数器加1。即使事件连续快速发生多次,信号量也不会丢失信息。
信号量的另一个用途是对资源的管理。计数器的值与当前可用资源的数目相对应。实际上,条件变量更适合于这种操作。
初始化 cyg_semaphore_init
删除 cyg_semaphore_destroy
等待 cyg_semaphore_wait
带超时等待 cyg_semaphore_timed_wait
非阻塞等待 cyg_semaphore_trywait
唤醒 cyg_semaphore_post
获取信息 cyg_semaphore_peek
========
* 信箱 *
========
信箱是一个类似于信号量的同步原语,还可以在事件发生时传递一些数据。有些系统称之为消息队列。被称为消息的数据通常是数据结构的指针。信箱只具有有限的容量,缺省配置为10个槽位,有可能溢出。因此,信箱通常不能被DSR用来唤醒线程。
创建 cyg_mbox_create
删除 cyg_mbox_delete
获得消息 cyg_mbox_get
带超时获得消息 cyg_mbox_timed_get
非阻塞获得消息 cyg_mbox_tryget
非删除获得消息 cyg_mbox_peek_item
发送消息 cyg_mbox_put
带超时发送消息 cyg_mbox_timed_put
非阻塞发送消息 cyg_mbox_tryput
读取消息数 cyg_mbox_peek
判断是否有消息 cyg_mbox_waiting_to_get
发新消息前判断 cyg_mbox_waiting_to_put
============
* 事件标志 *
============
事件标志允许线程等待一个或几个不同类型的事件发生。还可以用于等待某些事件组合的发生。事件标志不存在溢出问题。
事件标志可以指定函数调用者阻塞(1)直到所有指定事件发生为止;(2)直到至少一个指定事件发生为止;(3)直到所有指定事件发生为止并清除事件标志;(2)直到至少一个指定事件发生为止并清除事件标志。
初始化 cyg_flag_init
删除 cyg_flag_destroy
设置标志位 cyg_flag_setbits
清除标志位 cyg_flag_maskbits
等待事件发生 cyg_flag_wait
超时等待事件 cyg_flag_timed_wait
探查事件是否发生 cyg_flag_poll
返回事件标志当前值 cyg_flag_peek
报告是否有线程等待 cyg_flag_waiting
============
* Spinlock *
============
Spinlock是为SMP系统中的应用线程提供的一个同步原语。Spinlock运行级别要低于其他同步原语(如互斥体)。特别在对中断进行处理以及线程需要共享硬件资源的情况下需要使用Spinlock。在SMP系统中,内核自身的实现也需要使用Spinlock。
必须强调的是,对Spinlock的拥有时间必须很短,一般为几十条指令。在单处理器系统中,不应该使用Spinlock。
初始化 cyg_spinlock_init
删除 cyg_spinlock_destroy
声称 cyg_spinlock_spin
释放 cyg_spinlock_clear
非阻塞声称 cyg_spinlock_try
检查是否有等待 cyg_spinlock_test
安全声称 cyg_spinlock_spin_intsave
安全释放 cyg_spinlock_clear_intsave