TI-RTOS Kernel User‘s Guide:4 Synchronization Modules

本章介绍同步访问共享资源的模块

4.1Semaphores

SYS/BIOS为基于信号量的任务间同步和通信提供了一组基本功能。信号量通常用于在一组相互竞争的任务之间协调对共享资源的访问。Semaphore模块提供Semaphore_Handle类型句柄访问的信号量对象的函数。信号量对象可以声明为计数或二进制信号量,也可以声明为简单(FIFO)或优先级感知信号量。信号量可用于任务同步和互斥。默认情况下信号量为简单的计数信号量。计数信号量保留了可用资源数量的内部计数,当count大于0时,任务在获取信号量时不会阻塞;信号量的计数值仅受16位计数器大小的限制。如果禁用断言,如果计数从最大16位值增加,则计数滚动而不通知。

semParams.mode = Semaphore_Mode_COUNTING;

二进制信号量最多协调两个任务对共享资源的访问,二进制信号量提供比计数信号量更好的性能。

semParams.mode = Semaphore_Mode_BINARY;

任务按照FIFO顺序等待简单计数和二进制信号量,而不考虑任务的优先级。创建“优先级”信号量,是在具有较低优先级的第一个任务之前将挂起的任务插入等待列表中。因此,同等优先级的任务按FIFO顺序等待,但高优先级的任务在低优先级的任务之前准备好。

semParams.mode = Semaphore_Mode_COUNTING_PRIORITY;
semParams.mode = Semaphore_Mode_BINARY_PRIORITY;

请注意,使用优先级信号量可能会增加系统中的中断延迟,因为在扫描等待信号量的任务列表以寻找适当的插入点时,中断将被禁用。一般来说,每个等待任务大约有12条指令。例如,如果有10个更高优先级的任务在等待,那么在新任务进入列表之前,所有10个任务都将被检查,并禁用中断。

函数Semaphore_create()和Semaphore_delete()分别用于创建和删除信号量对象,

Semaphore_Handle Semaphore_create( Int count, 
    Semaphore_Params *attrs,
    Error_Block *eb );
Void Semaphore_delete(Semaphore_Handle *sem);

信号量计数在创建时初始化为count。通常count设置为信号量正在同步的资源数量。Semaphore_pend()等待一个信号量。如果信号量计数大于0,Semaphore_pend()只是减少计数并返回,计数不大于0,,Semaphore_pend()将等待Semaphore_post()发送信号量。Semaphore_pend()的timeout参数,允许任务等待到超时,无限期等待(BIOS_WAIT_FOREVER),或者根本不等待(BIOS_NO_WAIT)。Semaphore_pend()的返回值用于指示信号量是否成功获取。

Bool Semaphore_pend( Semaphore_Handle sem, UInt timeout);

Semaphore_post()用于给信号量发信号。如果任务正在等待信号量,Semaphore_post()将从信号量队列中删除第一个任务,并将其放入就绪队列。如果没有任务在等待,Semaphore_post()只是增加信号量计数并返回。

Void Semaphore_post(Semaphore_Handle sem);

调用Semaphore_post()可能会导致在特定情况下重新启用硬件中断。

4.1.1Semaphore例子

例4-4提供了三个writer任务的示例代码,它们创建唯一的消息,并将它们放在一个reader任务的列表中。写入器任务调用Semaphore_post()来指示另一条消息已被放入列表中。reader任务调用Semaphore_pend()来等待消息。Semaphore_pend()仅当消息在列表中可用时返回。阅读器任务使用System_printf()函数打印消息。在这个示例程序中,三个写器任务、一个读器任务、一个信号量和一个队列是按如下方式静态创建的:

var Defaults = xdc.useModule('xdc.runtime.Defaults');
var Diags    = xdc.useModule('xdc.runtime.Diags');
var Error    = xdc.useModule('xdc.runtime.Error');
var Log      = xdc.useModule('xdc.runtime.Log');
var LoggerBuf = xdc.useModule('xdc.runtime.LoggerBuf');
var Main   = xdc.useModule('xdc.runtime.Main');
var Memory = xdc.useModule('xdc.runtime.Memory')
var SysMin = xdc.useModule('xdc.runtime.SysMin');
var System = xdc.useModule('xdc.runtime.System');
var Text   = xdc.useModule('xdc.runtime.Text');

var BIOS  = xdc.useModule('ti.sysbios.BIOS');
var Clock = xdc.useModule('ti.sysbios.knl.Clock');
var Task  = xdc.useModule('ti.sysbios.knl.Task');
var Semaphore = xdc.useModule('ti.sysbios.knl.Semaphore');
var Hwi   = xdc.useModule('ti.sysbios.hal.Hwi');
var HeapMem   = xdc.useModule('ti.sysbios.heaps.HeapMem');

/* set heap and stack sizes */
BIOS.heapSize = 0x2000;
Program.stack = 0x1000;
SysMin.bufSize = 0x400;

/* set library type */
BIOS.libType = BIOS.LibType_Custom;

/* Set logger for the whole system */
var loggerBufParams = new LoggerBuf.Params();
loggerBufParams.numEntries = 32;
var logger0 = LoggerBuf.create(loggerBufParams);
Defaults.common$.logger = logger0;
Main.common$.diags_INFO = Diags.ALWAYS_ON;

/* Use Semaphore, and Task modules and set global properties */
var Semaphore = xdc.useModule('ti.sysbios.knl.Semaphore');
Program.global.sem = Semaphore.create(0);
var Task = xdc.useModule('ti.sysbios.knl.Task');
Task.idleTaskVitalTaskFlag = false;

/* Statically create reader and writer Tasks */
var reader = Task.create('&reader');
reader.priority = 5;
var writer0 = Task.create('&writer');
writer0.priority = 3;
writer0.arg0 = 0;
var writer1 = Task.create('&writer');
writer1.priority = 3;
writer1.arg0 = 1;
var writer2 = Task.create('&writer');
writer2.priority = 3;
writer2.arg0 = 2;

/* uses Queue module and create two instances statically */
var Queue = xdc.useModule('ti.sysbios.knl.Queue');
Program.global.msgQueue = Queue.create();
Program.global.freeQueue = Queue.create();

由于该程序使用多个任务,因此使用计数信号量来同步对列表的访问。虽然首先安排了三个写入器任务,但消息一放到队列中就会被读取,因为读取器的任务优先级高于写入器。

/* ======== semtest.c ======== */
#include <xdc/std.h>
#include <xdc/runtime/Memory.h>
#include <xdc/runtime/System.h>
#include <xdc/runtime/Error.h>
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Semaphore.h>
#include <ti/sysbios/knl/Task.h>
#include <ti/sysbios/knl/Queue.h>

#define NUMMSGS 3 /* number of messages */
#define NUMWRITERS 3 /* number of writer tasks created with */

/* Config Tool */
typedef struct MsgObj {
     Queue_Elem elem; /* first field for Queue */
     Int id; /* writer task id */
     Char val; /* message value */
} MsgObj, *Msg;

Void reader();
Void writer();

/* The following objects are created statically. */
extern Semaphore_Handle   sem;
extern Queue_Handle       msgQueue;
extern Queue_Handle       freeQueue;

/* ======== main ======== */
Int main(Int argc, Char* argv[])
{
   Int i;
   MsgObj *msg;
   Error_Block eb;
   Error_init(&eb);
 
   msg = (MsgObj *) Memory_alloc(NULL, NUMMSGS * sizeof(MsgObj), 0, &eb);
   if (msg == NULL) {
      System_abort("Memory allocation failed");
   }
 
 /* Put all messages on freeQueue */
   for (i = 0; i < NUMMSGS; msg++, i++) {
       Queue_put(freeQueue, (Queue_Elem *) msg);
   }
   BIOS_start();
   return(0);
}

/* ======== reader ======== */
Void reader()
{
   Msg msg;
   Int i;
   for (i = 0; i < NUMMSGS * NUMWRITERS; i++) {
       /* Wait for semaphore to be posted by writer(). */
       Semaphore_pend(sem, BIOS_WAIT_FOREVER);
       /* get message */
       msg = Queue_get(msgQueue);
       /* print value */
       System_printf("read '%c' from (%d).\n", msg->val, msg->id);
       /* free msg */
       Queue_put(freeQueue, (Queue_Elem *) msg);
   }
   System_printf("reader done.\n");
}

/* ======== writer ======== */
Void writer(Int id)
{
   Msg msg;
   Int i;
 
   for (i = 0; i < NUMMSGS; i++) {
   /* Get msg from the free list. Since reader is higher priority and
  *   only blocks on sem, list is never empty. */

      msg = Queue_get(freeQueue);
 
      /* fill in value */
      msg->id = id;
      msg->val = (i & 0xf) + 'a';
      System_printf("(%d) writing '%c' ...\n", id, msg->val);
      /* put message */
      Queue_put(msgQueue, (Queue_Elem *) msg);
      /* post semaphore */
      Semaphore_post(sem);
   }
 
   System_printf("writer (%d) done.\n", id);
}

4.2Event模块

事件为线程之间的通信和同步提供了一种方法,类似于信号量,不同之处在于它们允许指定多个条件(“事件”),这些条件必须在等待线程返回之前发生。调用Event_pend()会指定等待哪些事件,调用Event_post()会指定正在发布哪些事件。一次只能有一个Task挂起在Event对象上。单个Event实例最多可以管理32个事件,每个事件由一个事件ID表示。事件id只是位掩码,与Event对象管理的唯一事件相对应。每个事件的行为就像一个二进制信号量。对Event_pend()的调用接受一个andMask和一个orMask。andMask由所有必须发生的事件的事件id组成,orMask由任何事件的事件id组成,其中只有一个必须发生。

Event_pend()的调用接受一个超时值,如果调用超时则返回0。如果对Event_pend()的调用成功,它将返回“消费”事件的掩码—即为满足对Event_pend()的调用而发生的事件

UInt Event_pend(Event_Handle event,  UInt andMask,  UInt orMask,  UInt timeout);
Void Event_post(Event_Handle event,  UInt eventIds);

调用Event_post()可能会导致在特定情况下重新启用硬件中断。

//配置
var Event = xdc.useModule("ti.sysbios.knl.Event");
Program.global.myEvent = Event.create();

//运行
Event_Handle  myEvent;
Error_Block   eb;
...
Error_init(&eb);
/* Default instance configuration params */
myEvent = Event_create(NULL, &eb);
if (myEvent == NULL) {
 System_abort("Event create failed");
}
//阻塞事件,当事件0和事件6同时发生时才唤醒任务,在andMask设置Event_Id_00和Event_Id_06,
//在orMask设置为Event_Id_NONE
Event_pend(myEvent, (Event_Id_00 + Event_Id_06), Event_Id_NONE, BIOS_WAIT_FOREVER);
//发生的事件,在eventMask设置Event_Id_00正在发布的事件的id
Event_post(myEvent, Event_Id_00);

下面的C代码示例展示了一个为三个中断服务例程提供后台处理的任务:

Event_Handle myEvent;
main()
{
   ...
  /* create an Event object. All events are binary */
  myEvent = Event_create(NULL, &eb);
  if (myEvent == NULL) {
     System_abort("Event create failed");
   }
}

isr0()
{
   ...
   Event_post(myEvent, Event_Id_00);
   ...
}
isr1()
{
   ...
   Event_post(myEvent, Event_Id_01);
   ...
}
isr2()
{
   ...
   Event_post(myEvent, Event_Id_02);
   ...
}

task()
{
   UInt events;
   while (TRUE) {
        /* Wait for ANY of the ISR events to be posted *
       events = Event_pend(myEvent, Event_Id_NONE, 
                           Event_Id_00 + Event_Id_01 + Event_Id_02,
                           BIOS_WAIT_FOREVER);
       /* Process all the events that have occurred */
       if (events & Event_Id_00) {
           processISR0();
       }
      if (events & Event_Id_01) {
          processISR1();
      }
      if (events & Event_Id_02) {
         processISR2();
      }
   }
}

4.2.1隐式发布事件(Implicitly Posted Events)

除了通过Event_post() API显式发布事件之外,一些SYS/BIOS对象还支持隐式发布与其对象相关的事件。支持隐式事件发布的SYS/BIOS对象必须在创建时配置一个事件对象和事件ID。您可以决定将哪个事件ID与特定的资源可用性信号(即邮箱中可用的消息、邮箱中可用的房间或可用的信号量)相关联。配置为隐式事件发布的SYS/BIOS对象一次只能由一个Task等待.当Event_pend()用于从隐式发布对象获取资源时,应该使用BIOS_NO_WAIT超时参数随后从对象检索资源。

邮箱和信号量对象目前支持发布与其可用资源相关联的事件。要启用从Mailbox和Semaphore对象发布事件,必须将Semaphore模块的supportsEvents属性设置为True。

Semaphore.supportsEvents = true;

但是,如果在调用Event_pend()之前在相应的信号量上调用Semaphore_pend(sem, BIOS_NO_WAIT),则在从Semaphore_pend()返回之前,Event对象中匹配的事件ID会在内部刷新,以匹配新更新的信号量“count”。如果调用Semaphore_pend()后信号量计数非零,则在Event对象中设置相应的事件ID。如果调用Semaphore_pend()后计数为零,则清除事件对象中相应的事件ID。通过这种方式,Event对象与隐式调用Event_post()的Semaphore对象保持同步。但是应用程序必须调用Semaphore_pend(sem, BIOS_NO_WAIT)才能进行同步

由于邮箱是建立在信号量基础上的,因此同样的逻辑也适用于在邮箱中注册的Event对象。在由Mailbox_post()或Mailbox_pend()隐式发布事件的Event_pend()返回之后,相应的事件对象通过对Mailbox_pend(mbx, mbuf, BIOS_NO_WAIT)或Mailbox_post(mbx, mbuf, BIOS_NO_WAIT)的调用进行刷新。

该任务处理发送到Mailbox消息的消息,并执行ISR的后处理要求。

Event_Handle myEvent;
Mailbox_Handle mbox;
typedef struct msg {
 UInt id;
 Char buf[10];
}
main()
{
 Mailbox_Params mboxParams;
 Error_Block eb;
 Error_init(&eb);
 myEvent = Event_create(NULL, &eb);
 if (myEvent == NULL) {
 System_abort("Event create failed");
 }
Mailbox_Params_init(&mboxParams);
 mboxParams.readerEvent = myEvent;
 /* Assign Event_Id_00 to Mailbox "not empty" event */
 mboxParams.readerEventId = Event_Id_00; 
 mbox = Mailbox_create(sizeof(msg), 50, &mboxParams, &eb);
 if (mbox == NULL) {
 System_abort("Mailbox create failed");
 }
 /* Mailbox_create() sets Mailbox's readerEvent to 
 * counting mode and initial count = 50 */
}
writerTask()
{
 ...
 Mailbox_post(mbox, &msgA, BIOS_WAIT_FOREVER); 
 /* implicitly posts Event_Id_00 to myEvent */
 ...
}
isr()
{
 Event_post(myEvent, Event_Id_01);
}
readerTask()
{
 while (TRUE) {/* Wait for either ISR or Mailbox message */
 events = Event_pend(myEvent, 
 Event_Id_NONE, /* andMask = 0 */
 Event_Id_00 + Event_Id_01, /* orMask */
 BIOS_WAIT_FOREVER); /* timeout */
 if (events & Event_Id_00) {
 /* Get the posted message.
 * Mailbox_pend() will not block since Event_pend() 
 * has guaranteed that a message is available.
 * Notice that the special BIOS_NO_WAIT 
 * parameter tells Mailbox that Event_pend()
 * was used to acquire the available message.
 */
 Mailbox_pend(mbox, &msgB, BIOS_NO_WAIT); 
 processMsg(&msgB);
 }
 if (events & Event_Id_01) {
 processISR();
 }
 }
}

4.3Gates

门是防止并发访问关键代码区域的设备。各种Gate实现在试图锁定关键区域的方式上有所不同.Gates通常通过禁用某些级别的抢占(例如禁用任务切换甚至硬件中断)或使用二进制信号量来工作。所有Gate实现都通过使用“key”来支持嵌套。对于禁用抢占的Gates,可能会有多个线程调用Gate_enter(),但是在所有线程调用Gate_leave()之前,不应该恢复抢占。此功能是通过使用键来提供的。对Gate_enter()的调用返回一个密钥,然后必须将该密钥传递回Gate_leave()。只有对Gate_enter()的最外层调用返回恢复抢占的正确键。

下面的C代码用门保护一个关键区域。本例使用GateHwi,它禁用和启用中断作为锁定机制。

UInt gateKey;
GateHwi_Handle gateHwi;
GateHwi_Params prms;
Error_Block eb;
...
Error_init(&eb);
GateHwi_Params_init(&prms);
gateHwi = GateHwi_create(&prms, &eb);
if (gateHwi == NULL) {
   System_abort("Gate create failed");
}
/* Simultaneous operations on a global variable by multiple threads could cause problems,
 * so modifications to the global variable are protected with a Gate. */
gateKey = GateHwi_enter(gateHwi);
myGlobalVar = 7;
GateHwi_leave(gateHwi, gateKey);

4.3.1基于抢占的门实现

下面的门的实现使用某种形式的抢占禁用:

• ti.sysbios.gates.GateHwi
• ti.sysbios.gates.GateSwi
• ti.sysbios.gates.GateTask

GateHwi:GateHwi禁用和启用中断作为锁定机制。这样的门保证了对CPU的独占访问。当关键区域被Task、Swi或Hwi线程共享时,可以使用此门。进入和离开之间的时间应尽可能短,以减少Hwi延迟。

GateSwi:GateSwi禁用和启用软件中断作为锁定机制。当关键区域被Swi或Task线程共享时,可以使用此门。此门不能被Hwi线程使用。进入和离开之间的持续时间应该尽可能短,以减少Swi延迟。

 GateTask:GateTask禁用和启用任务作为锁定机制。当关键区域被Task线程共享时,可以使用此门。此门不能被Hwi或Swi线程使用。进入和离开之间的持续时间应该尽可能短,以最小化任务延迟

4.3.2基于信号量的门实现

下面的门的实现使用信号量:

• ti.sysbios.gates.GateMutex
• ti.sysbios.gates.GateMutexPri

GateMutex:GateMutex使用二进制信号量作为锁定机制。每个GateMutex实例都有自己唯一的信号量。因为这个门可能会阻塞,所以它不应该被Swi或Hwi线程使用,而应该只被Task线程使用

GateMutexPri:GateMutexPri是一个互斥锁Gate(一次只能由一个线程持有),它实现了“优先级继承”,以防止优先级反转。当高优先级任务的优先级被有效地“反转”,因为它正在等待由低优先级任务持有的Gate时,就会发生优先级反转。优先级反转的问题和解决方案将在4.3.3节中描述。

配置示例:配置HeapMem使用的网关类型。(参见第7.8.2节,HeapMem进行进一步讨论。)

var GateMutexPri = xdc.useModule('ti.sysbios.gates.GateMutexPri');
var HeapMem = xdc.useModule('ti.sysbios.heaps.HeapMem');
HeapMem.common$.gate = GateMutexPri.create();

4.3.3优先级翻转

下面的例子展示了优先级反转的问题。一个系统有三个任务—Low, Med, 和High,每个任务的优先级都由它的名字来表示。Task Low首先运行并获取。高任务被调度并抢占低任务,高任务试图获得,并等待。然后Med任务被调度抢占Low任务。High任务必须等待Med任务和Low任务完成后才能继续。在这种情况下,Low任务实际上降低了High任务的优先级。

解决方法:优先级继承。为了防止优先级反转,GateMutexPri实现了优先级继承。当High任务试图获取Low任务拥有的门时,Low任务的优先级暂时提升到High任务的优先级,只要High任务在等待门。因此High任务将其优先级赠给Low任务。当多个任务等待门时,门所有者将获得等待门的所有任务的最高优先级。

说明:优先级继承并不能完全防止优先级反转。任务只在进入门的调用中捐赠它们的优先级,因此,如果任务在等待门时提高了优先级,则该优先级不会传递给门的所有者

这可能发生在涉及多个门的情况下。例如,一个系统有四个任务:VeryLow、Low、Med和High,每个任务的优先级由其名称表示。VeryLow任务首先运行并获取门A。Low任务接下来运行并获取门B,等待门A。High任务运行并等待门B。High任务已经将其优先级捐赠给Low任务,但Low在VeryLow上被阻塞,因此尽管使用门优先级反转仍然发生。解决这个问题的方法是围绕它进行设计。如果一个高优先级、时间紧迫的任务可能需要门A,那么它应该是一个设计规则,即没有任务长时间持有该门或在持有该门时阻塞。当多个任务等待此门时,它们按优先级顺序接收门(高优先级的任务首先接收门)。这是因为等待GateMutexPri的任务列表是按优先级排序的,而不是FIFO.

对GateMutexPri_enter()的调用可能会阻塞,因此此门只能在任务上下文中使用。GateMutexPri具有不确定性调用,因为它保持按优先级排序的等待任务列表。

4.3.4配置SYS/BIOS的门类型

应用程序设置在TI RTS库中调用使用的门的类型(第6章)。选择的门的类型用于保证RTS  APIs的可重入性。BIOS.rtsGateType配置属性控制此行为。在XGCONF中,这个属性被标记为“C标准库锁”。门的类型取决于将要调用RTS库函数的线程的类型。例如,如果Swi和Task线程都要调用RTS库的System_printf()函数,则应该使用GateSwi。在这种情况下,在Swi或Task线程调用System_printf()时不会禁用Hwi线程。

使用 NoLocking, RTS 锁未插入,不能保证TI RTS库调用的可重入性。如果需要的话应用程序可以直接插入RTS锁。

GateTask不支持作为SYS/BIOS RTS门类型。

GateHwi: 中断被禁用和恢复以保持可重入性。使用它从Hwi进行任何RTS呼叫

GateSwi::Swi被禁用和恢复以保持重新进入。使用如果不从任何Hwis进行RTS呼叫,但从Swi进行此类呼叫。

GateMutex:单个互斥锁用于维护可重入性。使用它只从任务进行RTS调用。只阻塞那些试图执行RTS库关键区域的任务

GateMutexPri:优先级继承互斥锁用于维护可重入性。只阻塞那些试图执行RTS库关键区域的任务。将正在执行RTS库中关键区域的任务的优先级提升到被互斥锁阻塞的最高优先级任务的级别

4.4邮箱

 ti.sysbios.knl.Mailbox 模块提供了一组管理邮箱的函数,在同一处理器上邮箱可将从一个任务的缓冲区传递到另一个任务的缓冲区。邮箱实例可以进行多个读取和写入。邮箱模块将缓冲区复制到固定大小的内部缓冲区。这些缓冲区的大小和数量在创建(或构造)Mailbox实例时指定。当通过Mailbox_post()发送缓冲区时,副本就完成了。当通过Mailbox_pend()检索缓冲区时,会发生另一次复制。Mailbox_create()和Mailbox_delete()分别用于创建和删除邮箱,还可以静态地创建邮箱对象。可以使用邮箱来确保传入缓冲区的流量不会超过系统处理这些缓冲区的能力。使用邮箱实例发送和接收的所有缓冲区都必须具有相同的大小。

Mailbox_Handle  Mailbox_create(SizeT bufsize, UInt numBufs,       
                      Mailbox_Params *params,Error_Block *eb)
Void Mailbox_delete(Mailbox_Handle *handle);

Mailbox_pend()用于从邮箱读取缓冲区,如果没有可用的缓冲区(即邮箱为空),Mailbox_pend()阻塞。timeout参数允许任务等待到超时,无限期等待(BIOS_WAIT_FOREVER),或者根本不等待(BIOS_NO_WAIT),时间的单位是系统时钟的刻度。

Bool Mailbox_pend(Mailbox_Handle handle, Ptr buf,  UInt timeout);

Mailbox_post()用于向邮箱发送缓冲区。如果没有可用的缓冲槽(即邮箱已满),Mailbox_post()阻塞。timeout参数允许任务等待到超时,无限期等待(BIOS_WAIT_FOREVER),或者根本不等待(BIOS_NO_WAIT)。

Bool Mailbox_post(Mailbox_Handle handle,  Ptr buf, UInt timeout);

邮箱提供配置参数允许将事件与邮箱关联,允许同时等待邮箱消息和另一个事件。Mailbox提供了两个配置参数来支持邮箱阅读事件,readerevent和readerEventId,允许邮箱阅读使用事件对象来等待邮箱消息。邮箱写入提供了两个配置参数,writerevent和writerEventId,允许邮箱编写者使用事件对象来等待邮箱中的空间。

这些事件句柄的名称可能会引起误解。readerEvent是邮箱阅读器应该挂起的事件,但它是由邮箱编写器在Mailbox_post()调用中发布的。writerEvent是邮箱写入器应该挂起等待邮箱未满的事件,以便它可以成功执行Mailbox_post(),而不会因为邮箱已满而挂起。然而,只要邮箱读取成功(即mailbox_pende()返回TRUE),就会由邮箱读取器发送writerEvent。

当使用事件时,线程调用Event_pend()并等待多个事件。从Event_pend()返回后,线程必须调用Mailbox_pend()或Mailbox_post()——取决于它是一个读线程还是一个写线程——超时值为BIOS_NO_WAIT。

4.5队列

 ti.sysbios.knl.Queue 模块提供创建对象列表,Queue是作为双链表实现的,因此可以在列表中的任何位置插入或删除元素,并且Queue没有最大大小限制。

4.5.1队列的基本FIFO操作

要向Queue添加一个结构,它的第一个字段需要为Queue_Elem类型。下面的示例显示了可以添加到Queue的结构。Queue_enqueue()将元素添加到列表的后面,Queue_dequeue()删除并返回列表头部的元素。总之,这些函数支持一个自然的FIFO队列。

下面的示例演示了基本的Queue操作——Queue_enqueue()和Queue_dequeue()。它还使用Queue_empty()函数,该函数在Queue中没有更多元素时返回true。

/* This structure can be added to a Queue because the first field is a Queue_Elem. */
typedef struct Rec {
           Queue_Elem elem;
           Int data;
} Rec;
Queue_Handle  myQ;
Rec r1, r2;
Rec* rp;
r1.data = 100;
r2.data = 200;

// No parameters or Error block are needed to create a Queue.
myQ = Queue_create(NULL, NULL);

// Add r1 and r2 to the back of myQ.
Queue_enqueue(myQ, &(r1.elem));
Queue_enqueue(myQ, &(r2.elem));

// Dequeue the records and print their data
while (!Queue_empty(myQ)) {
     // Implicit cast from (Queue_Elem *) to (Rec *)
    rp = Queue_dequeue(myQ);
    System_printf("rec: %d\n", rp->data);
}

4.5.2在队列上迭代

Queue模块还提供了几个用于在Queue上循环的 APIs。Queue_head()返回Queue前面的元素(不删除它),Queue_next()和Queue_prev()分别返回Queue中的下一个和上一个元素。

下面的示例演示了从头到尾遍历Queue一次的一种方法。在这个例子中,"myQ"是一个Queue_Handle。

Queue_Elem *elem;
for (elem = Queue_head(myQ); elem != (Queue_Elem *)myQ; 
    elem = Queue_next(elem)) {
     ...
}

4.5.3插入和删除队列元素

还可以使用Queue_insert()和Queue_remove()从Queue中间的任何位置插入或删除元素。Queue_insert()在指定元素的前面插入一个元素,Queue_remove()从它所在的Queue中删除指定元素。注意,Queue没有提供任何 APIs来插入或删除Queue中给定索引处的元素

Queue_enqueue(myQ, &(r1.elem));
/* Insert r2 in front of r1 in the Queue. */
Queue_insert(&(r1.elem), &(r2.elem));
/* Remove r1 from the Queue. Note that Queue_remove() does not 
 * require a handle to myQ. */
Queue_remove(&(r1.elem));

4.5.4原子队列操作

队列通常在系统中的多个线程之间共享,这可能导致不同线程并发地修改队列,从而损坏队列。上面讨论的Queue api不能防止这种情况。但是,Queue提供了两个“原子”api,它们在对Queue进行操作之前禁用中断。这些api是Queue_get()和Queue_put(),前者是Queue_dequeue()的原子版本,后者是Queue_enqueue()的原子版本。

  • 16
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值