TI-RTOS Kernel(SYS/BIOS)---同步模块

本章描述了可用于同步访问共享资源的模块

信号量

SYS/BIOS提供了一组基本的函数,用于基于信号量的任务间同步和通信。信号量通常用于在一组相互竞争的任务之间协调对共享资源的访问。Semsphore模块提供了一些函数,用于操作通过Semsphore_Handle类型的句柄访问的信号量对象。

信号量对象可以声明为计数信号或二进制信号量,也可以声明为简单信号量(FIFO)或优先级感知的信号量。信号量可以用于任务之间同步和互斥。计数和二进制信号量都使用相同的API。

在默认情况下,信号量是简单的计数信号量

计数信号量保持相应可用资源数量的内部计数。当count大于0时,任务在获取信号量时不会阻塞。信号量的计数值仅受16位计数器大小的限制。如果关闭了异常检测,则计数从最大16位值递增时将滚动而没有通知

在配置计数信号量时,设置的mode参数如下:

semParams.mode = Semaphore_Mode_COUNTING;

二进制信号量可用或不可用。他们的值的增量不能超过1。因此,它应该用于协调最多两个任务对共享资源的访问。二进制信号量比计数信号量提供了更好的性能

为了配置二进制信号量,设置mode参数如下:

semParams.mode = Semaphore_Mode_BINARY;

Tasks以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。通常,count被设置为信号量正在同步的资源的数量

Semaphore_pend()等待一个信号量。如果信号量计数大于0,则Semaphore_pend()简单地减少计数并返回。否则,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()来表明另一个消息已被存放到列表中。读任务调用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);
}

输出结果:

(0) writing 'a' ...
read 'a' from (0).
(0) writing 'b' ...
read 'b' from (0).
(0) writing 'c' ...
read 'c' from (0).
writer (0) done.
(1) writing 'a' ...
read 'a' from (1).
(1) writing 'b' ...
read 'b' from (1).
(1) writing 'c' ...
read 'c' from (1).
writer (1) done.
(2) writing 'a' ...
read 'a' from (2).
(2) writing 'b' ...
read 'b' from (2).
(2) writing 'c' ...
read 'c' from (2).
reader done.
writer (2) done.

Event模块

Events为线程之间的通信和同步提供了一种方法。它们类似于信号量,除了它们允许你指定在等待线程返回之前必须发生的多个条件(“事件”)

Event实例与"pend"和"post"一起调用,就像Semaphore一样。但是,对Event_pend()的调用会指定等待哪些事件,而对Event_post()的调用会指定哪些事件正在被释放

注意:一次只能在Event对象上挂起一个Task

单个Event实例最多可以管理32个events,每个事件由一个event ID表示。Events IDs仅仅是位掩码,对应于Event对象管理的唯一事件。

每个Event的行为都像一个二进制信号量

调用Event_pend()需要一个"andMask"和一个"orMask"。andMask由所有必须发生的事件的event IDs组成,orMask由只有一个必须发生的事件的event IDs组成

与Semaphores一样,对Event_pend()的调用也需要一个timeout值,如果调用time out超时则返回0。如果对Event_pend()调用成功,它将返回"已消耗"事件的掩码–即为满足对Event_pend()的调用而发生的事件。然后,该任务负责处理所使用的所有事件。

仅仅Tasks能调用Event_pend(),Hwis,Swis,和其他的Tasks调用Event_post()

Event_pend()原型如下:

UInt Event_pend(Event_Handle event, 
                UInt         andMask, 
                UInt         orMask, 
                UInt         timeout);

Event_post()原型如下:

Void Event_post(Event_Handle event, 
                UInt         eventIds);

Configuration example:下面的语句可以静态地创建一个事件。Event对象有一个名为"myEvent"的Event_Handle

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

Runtime example: 下面的C代码创建了一个名为"myEvent"的Event_Handle的对象

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");
}

Runtime example:下面的C代码阻塞了一个事件。只有当事件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);

Runtime example:下面的C程序调用Event_post()来通知哪些事件发生了。eventMask应该包含正在释放的事件的ID.

Event_post(myEvent, Event_Id_00);

Runtime example:下面的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();
        }
    }
}

隐式地释放事件

除了支持通过Event_post() API显示释放事件外,一些SYS/BIOS对象还支持隐式释放与其对象关联的事件。例如,可以将Mailbox配置为每当有消息可用时(即每当调用Mailbox_post()时)释放相关的事件,从而允许在等待Mailbox消息和/或其他事件发生阻塞任务。

Mailbox和Semaphore对象目前支持释放与资源相关的事件。要启用从Mailbox和Semaphore对象释放事件的能力,必须将Semaphore模块的supportsEvents属性设置为True。例如,配置可以包括以下语句:

Semaphore.supportsEvents = true;

支持隐式事件释放的SYS/BIOS对象在创建时必须配置一个事件对象和事件ID。可以决定将哪个事件ID与特定的资源可用性信号(即,邮箱中的可用的消息、邮箱中可用的空间或可用的信号量)关联

当Event_pend()用于从隐式释放对象中获取资源时,应使用BIOS_NO_WAIT超时参数来随后从对象中检索资源

回忆以下,Events是二进制的,所以当从Event_pend返回时,所有匹配的事件被消耗掉。如果任务在一个从信号量隐式释放的事件上被阻塞,则会消耗相应的事件ID。在同一个事件ID上再次立即调用Event_pend()将会阻塞。

但是,如果在调用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对象。在Event_pend()返回由Mailbox_post()或Mailbox_pend()隐式释放的事件之后,相应的Event对象会通过调用Mailbox_pend(mbx, mbuf, BIOS_NO_WAIT)或 Mailbox_post(mbx, mbuf, BIOS_NO_WAIT)来刷新

Runtime example:下面的C代码显示了一个任务,该任务处理发送到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();
      }
   }
}

Gates

Gates是用来防止并发访问关键代码区域的设备。不同的Gate实现在试图锁定关键区域的方式有所不同

线程可以被其他优先级更高的线程抢占,一些代码段需要由一个线程完成,然后才能由另一个线程执行。修改全局变量的代码是可能需要Gate保护的关键区域的常见示例。

Gates通常通过禁用某些级别的抢占(如禁用任务切换或硬件中断)或使用二进制信号量来工作

所有Gate实现都通过使用“键”来支持嵌套

对于禁用抢占功能的Gates,可能会有多个线程调用Gate_enter(),但是只有所有线程都调用了Gate_leave(),抢占才会恢复。此功能是通过使用键来提供的。对Gate_enter()的调用返回一个键,然后必须将该键传递回Gate_leave()。只有最外层的Gate_enter()调用返回恢复抢占的正确值

在下面的例子中,函数名中使用的是实现的实际模块名,而不是"Gate"

Runtime example: 下面的C代码用Gate来保护一个关键区域。本例使用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);

基于抢占的Gate实现

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

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

GateHwi

GateHwi禁用和启用中断作为锁定机制。这样的Gate保证了对CPU的独占访问。当关键区域由Task、Swi或Hwi线程共享式,使用此Gate

enter和leave之间的时间应该尽可能短,以减少Hwi延迟

GateSwi

GateSwi禁用和启用软件中断作为锁定机制。当关键区域由Swi或Task线程共享时,可以使用此Gate。这个Gate不能被Hwi线程使用

enter和leave之间的时间应该尽可能短,以减少Swi延迟

GateTask

GateTask禁用和启用Task作为锁定机制。当关键区域由Task共享时,可以使用此Gate。这个Gate不能被Hwi或Swi线程使用

enter和leave之间的时间应该尽可能短,以减少Task延迟

基于信号量的Gate实现

下面Gates的实现使用了一个信号量:

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

GateMutex

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

GateMutexPri

GateMutexPri是一个互斥Gate(一次只能由一个线程持有),它实现了"优先级继承",以防止优先级倒置。优先级倒置发生在高优先级Task的优先级被"倒置"时,因为它正在等待低优先级Task持有的Gate

Configuration example: 下面的示例指定要由HeapMem使用的GateType

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

优先级反转

下面的例子说明了优先级反转的问题。一个系统有三个Task—Low,Med和High—每个Task的优先级由其名称所建议。Task Low首先运行并获得gate。Task High被调度并抢占Low。Task High尝试得到gate,并且等待它。接下来,task Med被调度并抢占task Low。现在task High必须等待task Med和task Low都完成后才能继续。在这种情况下,task Low实际上降低了task High的优先级。

解决方案:优先级继承

为了防止优先级反转,GateMutexPri实现了优先级继承。当task High试图获得task Low拥有的gate时,只要High在等待gate,task Low的优先级就会暂时提升到High的优先级之上。因此,task High捐赠它的优先级给task Low。

当有多个tasks在等待gate时,gate所有者会收到所有正在等待gate的任务的最高优先级

说明
优先级的继承并不能完全防止优先级反转。Task只要求在调用中提供进入gate的优先级,所以如果一个Task在等待gate时提高了它的优先级,该优先级不会传递给gate的所有者。

在这种情境下,这能包含多个gates。例如,一个系统有四个task:VeryLow,Low,Med和High,每个task的优先级由其名称所建议。Task VeryLow首先运行并且得到gate A。Task Low接下来运行并且得到gate B,然后等待gate A。Task High运行并且等待gate B。Task High已经将它的优先级捐赠给task Low,但是Low在VeryLow上被阻塞,所以尽管使用了gate,优先级还是发生了反转。解决这个问题的方法是围绕它进行设计。如果一个高优先级、时间紧迫的task需要gate A,那么设计规则应该是,没有task会长时间占据这个gate,也没有task会阻塞这个gate。

当多个tasks在等待这个gate时,它们按照优先级的顺序接收这个gate(高优先级的task首先接收gate)。这是因为等待GateMutexPri的task列表是按优先级排序的,而不是FIFO。

对GateMutexPri_enter()的调用可能会阻塞,因此这个gate只能在task上下文中使用。

GateMutexPri具有非确定性的调用,因为它按优先级对等的任务列表进行排序

配置SYS/BIOS Gate类型

应用程序设置TI RTS库中调用所使用的gate类型。选择的gate的类型用于保证RTS API的重入。BIOS.rtsGateType配置属性控制这个行为。在XGCONG中,这个属性被标记为“C Standard Library Lock”。

gate的类型却决于将要调用RTS库函数的贤臣类型。例如,如果Swi和Task线程都将要调用RTS库的System_printf()函数,则应该使用GateSwi。在这种情况下,在Swi或Task线程调用System_printf()期间不会禁用Hwi线程

如果使用了NoLocking,则RTS锁不会被插入,并且TI RTS库调用的重入不能得到保证。如果需要,应用程序可以直接插入RTS锁。

注意,GateTask不支持作为一个SYS/BIOS RTS gate类型

  • GateHwi: 中断被禁用并恢复以保证可重入性。如果从Hwi进行任何RTS调用,则调用
  • GateSwi: Swis被禁用并恢复以保持可重入性。如果不是从任何Hwis进行RTS调用,而是从Swis进行此类调用,则使用
  • GateMutex: 使用一个互斥锁来维护重入。如果只从Tasks RTS调用,则使用。只有Task的区块试图执行RTS库的关键区域
  • GateMutexPri: 优先级继承互斥锁用于维护重入。只有Task的区块也试图执行RTS库的关键区域。将正在执行RTS库中的关键区域的Task的优先级提高到被互斥锁阻塞的最高优先级Task的级别。

默认的RTS Gate Type取决于其他配置参数启用的线程模型类型。如果BIOS.taskEnabled为true时,使用GateMutex。如果BIOS.swiEnabled为true和BIOS.taskEnabled为false,表示使用GateSwi。如果BIOS.swiEnabled和BIOS.taskEnabled为false,则使用xdc.runtime.GateNull。

如果BIOS.taskEnabled为false时,用户不应该选择GateMutex(或其他Task等级的gates)。类似地,如果BIOS.taskEnabled为false,则用户不应该选择GateSwi或Task等级的gates

邮箱

ti.sysbios.knl.Mailbox模块提供了一组管理邮箱的函数。在同一个处理器上,可以使用邮箱将缓冲区从一个task传递到另一个task。

Mailbox实例可以被多个读写器和写入器使用。

Mailbox模块将缓冲区复制到固定大小的内部缓冲区。这些缓冲区的大小和数量在创建(或构造)Mailbox实例时指定。当一个缓冲区通过Mailbox_post()被发送时,就完成了复制。当缓存区通过Mailbox_pend()来检索时,另一个复制就完成了。

Mailbox_create()和Mailbox_delete()分别用于创建和删除邮箱。还可以静态地创建邮箱对象。

邮箱可用于确保传入缓冲区的流不会超过系统处理这些缓冲区的能力。本节后面给出的示例就说明了这种模式

可以在创建邮箱时指定内部邮箱缓冲区的数量和每个缓冲区的大小。由于大小是在创建Mailbox时指定的,因此使用Mailbox实例发送和接收的所有缓冲区必须具有相同的大小。

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参数允task等待超时、无限期等待(BIOS_WAIT_FOREVER)或根本不等待(BIOS_NO_WAIT)。时间单元是系统时钟的滴答声。

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

Mailbox_post()用于向邮箱释放缓冲区。如果没有可用的缓冲区槽(即邮箱已满),Mailbox_post()将阻塞。timeout参数允task等待超时、无限期等待(BIOS_WAIT_FOREVER)或根本不等待(BIOS_NO_WAIT)。

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

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

注意,这些事件的句柄的名称可能会引起误解。readerEvent是Mailbox阅读器应该挂起的事件,但它是由Mailbox写入器再Mailbox_post()调用中提交的。writerEvent是邮箱写入器在等待邮箱未满时应该挂起的事件,这样他就可以成功执行Mailbox_post(),而不会因为邮箱已满而挂起。然而,只要成功地从Mailbox读取(即Mailbox_pend()返回TRUE),就会由Mailbox阅读器释放

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

队列

ti.sysbios.knl.Queue模块支持创建对象列表。Queue是作为一个双链表实现的,因此可以从列表中任何位置插入或删除元素,因此Queue没有最大的值

队列基本的FIFO操作

要向Queue添加一个结构体,它的第一个字段需要是Queue_Elem类型。下面的例子显示了一个结构体能够被添加到队列中。

队列中有一个"头",也就是列表的前面Queue_enqueue()将元素添加到列表的后面,Queue_dequeue()删除并返回位于表头的元素。这些函数一起支持自然的FIFO队列

Run-time example: 下面的示例演示了基本的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);
}

例子打印:

rec: 100
rec: 200

队列迭代

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

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

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

插入和移除队列元素

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

**Run-time example:**下面的示例演示了Queue_insert()和Queue_remove()

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));

原子队列操作

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

参考文献:

  1. 《TI-RTOS Kernel (SYS/BIOS) User’s Guide》

猜你喜欢

转载自blog.csdn.net/Xiao_Jie123/article/details/120589206