Advertisement

Queues: introduction and basic services

August 15, 2018

Colin Walls-August 15, 2018

Queues were introduced in an earlier article. They provide a more flexible means of passing simple messages between tasks than mailboxes.

Using Queues

In Nucleus SE, queues are configured at build time. There may be a maximum of 16 queues configured for an application. If no queues are configured, no data structures or service call code appertaining to queues are included in the application.

A queue is simply a set of storage locations, each big enough to hold a single data item of type ADDR, access to which is controlled so that it may be safely utilized by multiple tasks. Tasks can write to a queue repeatedly until all the locations are full. Tasks can read from a queue and data is normally received on a first in, first out (FIFO) basis. Trying to send to a full queue or read from an empty one may result in an error or task suspension, depending upon options selected in the API call and the Nucleus SE configuration.

Queues and Pipes

Nucleus SE also supports pipes, which were also introduced in an earlier article and are covered in detail in a future article. The main difference between queues and pipes is the message size. Queues carry messages comprising a single ADDR – these would commonly be pointers. A pipe carries messages which are an arbitrary number of bytes long; the size is fixed for each pipe in the application and set at configuration time.

Configuring Queues

Number of Queues

As with most aspects of Nucleus SE, the configuration of queues is primarily controlled by #define statements in nuse_config.h. The key setting is NUSE_QUEUE_NUMBER, which determines how many queues are configured for the application. The default setting is 0 (i.e. no queues are in use) and you can set it to any value up to 16. An erroneous value will result in a compile time error, which is generated by a test in nuse_config_check.h (this is included into nuse_config.c and hence compiled with this module) resulting in a #error statement being compiled.

Choosing a non-zero value is the “master enable” for queues. This results in some data structures being defined and sized accordingly, of which more in the next article. It also activates the API enabling settings.

API Enables

Every API function (service call) in Nucleus SE has an enabling #define symbol in nuse_config.h. For queues, these are:

NUSE_QUEUE_SEND
NUSE_QUEUE_RECEIVE
NUSE_QUEUE_JAM
NUSE_QUEUE_RESET
NUSE_QUEUE_INFORMATION
NUSE_QUEUE_COUNT

By default, all of these are set to FALSE, thus disabling each service call and inhibiting the inclusion of any implementation code. To configure queues for an application, you need to select the API calls that you want to use and set their enabling symbols to TRUE.

Here is an extract from the default nuse_config.h file.

#define NUSE_QUEUE_NUMBER    0  /* Number of queues in the
                                   system - 0-16 */
                                /* Service call enablers */
#define NUSE_QUEUE_SEND         FALSE
#define NUSE_QUEUE_RECEIVE      FALSE
#define NUSE_QUEUE_JAM          FALSE
#define NUSE_QUEUE_RESET        FALSE
#define NUSE_QUEUE_INFORMATION  FALSE
#define NUSE_QUEUE_COUNT        FALSE

A compile time error will result if a queue API function is enabled and no queues are configured (except for NUSE_Queue_Count() which is always permitted). If your code uses an API call, which has not been enabled, a link time error will result, as no implementation code will have been included in the application.

Queue Service Calls

Nucleus RTOS supports ten service calls that appertain to queues, which provide the following functionality:

  • Send a message to a queue. Implemented by NUSE_Queue_Send() in Nucleus SE.

  • Receive a message from a queue. Implemented by NUSE_Queue_Receive() in Nucleus SE.

  • Send a message to the front of a queue. Implemented by NUSE_Queue_Jam() in Nucleus SE.

  • Restore a queue to the unused state, with no tasks suspended (reset). Implemented by NUSE_Queue_Reset() in Nucleus SE.

  • Provide information about a specified queue. Implemented by NUSE_Queue_Information() in Nucleus SE.

  • Return a count of how many queues are (currently) configured for the application. Implemented by NUSE_Queue_Count() in Nucleus SE.

  • Add a new queue to the application (create). Not implemented in Nucleus SE.

  • Remove a queue from the application (delete). Not implemented in Nucleus SE.

  • Return pointers to all the queues (currently) in the application. Not implemented in Nucleus SE.

  • Send a message to all the tasks that are suspended on a queue (broadcast). Not implemented in Nucleus SE.

The implementation of each of these service calls is examined in detail.

Queue Write and Read Services

The fundamental operations, which can be performed on a queue, are writing data to it – which is sometimes termed sending – and reading data from it – which is also termed receiving. It is also possible to write data to the front of a queue – which is also termed jamming. Nucleus RTOS and Nucleus SE each provide three basic API calls for these operations, which will be discussed here.

Writing to a Queue

The Nucleus RTOS API call for writing to a queue is very flexible, enabling you to suspend indefinitely, or with a timeout, if the operation cannot be completed immediately; i.e. you try to write to a full queue. Nucleus SE provides the same service, except task suspend is optional and timeout is not implemented.

Nucleus RTOS also offers a facility to broadcast to a queue, but this is not supported by Nucleus SE. It is described under Unimplemented APIs in the next article.

Nucleus RTOS API Call for Sending to a Queue

Service call prototype:

STATUS NU_Send_To_Queue(NU_QUEUE *queue, VOID  *message,
                        UNSIGNED size, UNSIGNED suspend);

Parameters:

queue – pointer to the user-supplied queue control block

message – a pointer to the message to be sent

size – the number of UNSIGNED data elements in the message. If the queue supports variable-length messages, this parameter must be equal to or less than the message size supported by the queue. If the queue supports fixed-size messages, this parameter must be exactly the same as the message size supported by the queue.

suspend – specification for task suspend; may be NU_NO_SUSPEND or NU_SUSPEND or a timeout value

Returns:

NU_SUCCESS – the call was completed successfully

NU_INVALID_QUEUE – the queue pointer is invalid

NU_INVALID_POINTER – the message pointer is NULL

NU_INVALID_SIZE – the message size is incompatible with the message size supported by the queue

NU_INVALID_SUSPEND – suspend was attempted from a non-task thread

NU_QUEUE_FULL – the queue is full and suspend was not specified

NU_TIMEOUT – the queue is still full even after suspending for the specified timeout value

NU_QUEUE_DELETED – the queue was deleted while the task was suspended

NU_QUEUE_RESET – the queue was reset while the task was suspended

Nucleus SE API Call for Sending to a Queue

This API call supports the key functionality of the Nucleus RTOS API.

Service call prototype:

STATUS NUSE_Queue_Send(NUSE_QUEUE queue, ADDR *message,
                       U8 suspend);

Parameters:

queue – the index (ID) of the queue to be utilized

message – a pointer to the message to be sent, which is a single variable of type ADDR

suspend – specification for task suspend; may be NUSE_NO_SUSPEND or NUSE_SUSPEND

Returns:

NUSE_SUCCESS – the call was completed successfully

NUSE_INVALID_QUEUE – the queue index is invalid

NUSE_INVALID_POINTER – the message pointer is NULL

NUSE_INVALID_SUSPEND – suspend was attempted from a non-task thread or when blocking API calls were not enabled

NUSE_QUEUE_FULL – the queue is full and suspend was not specified

NUSE_QUEUE_WAS_RESET – the queue was reset while the task was suspended

Nucleus SE Implementation of Queue ASend

The bulk of the code of the NUSE_Queue_Send() API function – after parameter checking – is selected by conditional compilation, dependent on whether support for blocking (task suspend) API calls is enabled. We will look at the two variants separately here.

If blocking is not enabled, the code for this API call is quite simple:

if (NUSE_Queue_Items[queue] == NUSE_Queue_Size[queue])   /* queue
                                                         full */
{
   return_value = NUSE_QUEUE_FULL;
}
else                                 /* queue element available */
{
   NUSE_Queue_Data[queue][NUSE_Queue_Head[queue]++] = *message;
   if (NUSE_Queue_Head[queue] == NUSE_Queue_Size[queue])
   {
      NUSE_Queue_Head[queue] = 0;
   }
   NUSE_Queue_Items[queue]++;
   return_value = NUSE_SUCCESS;
}


The function simply checks that there is room in the queue and uses the NUSE_Queue_Head[] index to store the message in the queue’s data area.

When blocking is enabled, the code becomes more complex:

do
{
   if (NUSE_Queue_Items[queue] == NUSE_Queue_Size[queue])   /*
                                                     queue full */
   {
      if (suspend == NUSE_NO_SUSPEND)
      {
         return_value = NUSE_QUEUE_FULL;
      }
      else
      {                                           /* block task */
         NUSE_Queue_Blocking_Count[queue]++;
         NUSE_Suspend_Task(NUSE_Task_Active,
                          (queue << 4) | NUSE_QUEUE_SUSPEND);
         return_value =
         NUSE_Task_Blocking_Return[NUSE_Task_Active];
         if (return_value != NUSE_SUCCESS)
         {
            suspend = NUSE_NO_SUSPEND;
         }
      }
   }
   else
   {                                /* queue element available */
      NUSE_Queue_Data[queue][NUSE_Queue_Head[queue]++] = *message;
      if (NUSE_Queue_Head[queue] == NUSE_Queue_Size[queue])
      {
         NUSE_Queue_Head[queue] = 0;
      }
      NUSE_Queue_Items[queue]++;
      if (NUSE_Queue_Blocking_Count[queue] != 0)
      {
         U8 index;          /* check whether a task is blocked
                               on this queue */
         NUSE_Queue_Blocking_Count[queue]--;
         for (index=0; index<NUSE_TASK_NUMBER; index++)
         {
             if ((LONIB(NUSE_Task_Status[index]) ==
                  NUSE_QUEUE_SUSPEND)
                  && (HINIB(NUSE_Task_Status[index]) == queue))
             {
                NUSE_Task_Blocking_Return[index] = NUSE_SUCCESS;
                NUSE_Wake_Task(index);
                break;
             }
          }
       }
       return_value = NUSE_SUCCESS;
       suspend = NUSE_NO_SUSPEND;
    }
} while (suspend == NUSE_SUSPEND);

Some explanation of the code may be useful:

The code is enclosed in a do...while loop, which continues while the parameter suspend has the value NUSE_SUSPEND.

If the queue is full and suspend is set to NUSE_NO_SUSPEND, the API call exits with NUSE_QUEUE_FULL. If suspend was set to NUSE_SUSPEND, the task is suspended. On return (i.e. when the task is woken up), if the return value is NUSE_SUCCESS, indicating that the task was woken because a message had been read (as opposed to a reset of the queue) the code loops back to the top.

If the queue is not full, the supplied message is stored using the NUSE_Queue_Head[] index to store the message in the queue’s data area. A check is made on whether any tasks are suspended (waiting to receive) on the queue. If there are any tasks waiting, the first one is woken. The suspend variable is set to NUSE_NO_SUSPEND and the API call exits with NUSE_SUCCESS.

Continue reading on page two >>

 

< Previous
Page 1 of 2
Next >

Loading comments...

Most Commented