Advertisement

Pipes: introduction and basic services

October 16, 2018

Colin Walls-October 16, 2018

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

Using Pipes

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

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

Pipes and Queues

Nucleus SE also supports queues, which were covered in detail in a previous article. The main difference between pipes and queues 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 Pipes

Number of Pipes

As with most aspects of Nucleus SE, the configuration of pipes is primarily controlled by #define statements in nuse_config.h. The key setting is NUSE_PIPE_NUMBER, which determines how many pipes are configured for the application. The default setting is 0 (i.e. no pipes 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 pipes. 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 pipes, these are:

NUSE_PIPE_SEND
NUSE_PIPE_RECEIVE
NUSE_PIPE_JAM
NUSE_PIPE_RESET
NUSE_PIPE_INFORMATION
NUSE_PIPE_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 pipes 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_PIPE_NUMBER    0  /* Number of pipes in the
                                   system - 0-16 */
                                /* Service call enablers */
#define NUSE_PIPE_SEND         FALSE 
#define NUSE_PIPE_RECEIVE      FALSE
#define NUSE_PIPE_JAM          FALSE
#define NUSE_PIPE_RESET        FALSE
#define NUSE_PIPE_INFORMATION  FALSE
#define NUSE_PIPE_COUNT        FALSE

A compile time error will result if a pipe API function is enabled and no pipes are configured (except for NUSE_Pipe_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.

Pipe Service Calls

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

  • Send a message to a pipe. Implemented by NUSE_Pipe_Send() in Nucleus SE.

  • Receive a message from a pipe. Implemented by NUSE_Pipe_Receive() in Nucleus SE.

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

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

  • Provide information about a specified pipe. Implemented by NUSE_Pipe_Information() in Nucleus SE.

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

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

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

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

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

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

Pipe Write and Read Services

The fundamental operations, which can be performed on a pipe, 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 pipe – 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 Pipe

The Nucleus RTOS API call for writing to a pipe 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 pipe. 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 pipe, but this is not supported by Nucleus SE. It will be described under Unimplemented APIs in the next article.

Nucleus RTOS API Call for Sending to a Pipe

Service call prototype:

  STATUS NU_Send_To_Pipe(NU_PIPE *pipe, VOID  *message,
                          UNSIGNED size, UNSIGNED suspend);

Parameters:

pipe – pointer to the user-supplied pipe control block
message – a pointer to the message to be sent
size – the number of bytes in the message. If the pipe supports variable-length messages, this parameter must be equal to or less than the message size supported by the pipe. If the pipe supports fixed-size messages, this parameter must be exactly the same as the message size supported by the pipe
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_PIPE – the pipe 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 pipe
NU_INVALID_SUSPEND – suspend was attempted from a non-task thread
NU_PIPEE_FULL – the pipe is full and suspend was not specified
NU_TIMEOUT – the pipe is still full even after suspending for the specified timeout value
NU_PIPE_DELETED – the pipe was deleted while the task was suspended
NU_PIPE_RESET – the pipe was reset while the task was suspended

Nucleus SE API Call for Sending to a Pipe

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

Service call prototype:

STATUS NUSE_Pipe_Send(NUSE_PIPE pipe, U8 *message,
                      U8 suspend);

Parameters:

pipe – the index (ID) of the pipe to be utilized
message – a pointer to the message to be sent, which is a sequence of bytes as long as the configured message size of the pipe
suspend – specification for task suspend; may be NUSE_NO_SUSPEND or NUSE_SUSPEND

Returns:

NUSE_SUCCESS – the call was completed successfully
NUSE_INVALID_PIPE – the pipe 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_PIPE_FULL – the pipe is full and suspend was not specified
NUSE_PIPE_WAS_RESET – the pipe was reset while the task was suspended

Nucleus SE Implementation of Pipe Send

The bulk of the code of the NUSE_Pipe_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_Pipe_Items[pipe] == NUSE_Pipe_Size[pipe])  /* pipe
                                                       full */
{
   return_value = NUSE_PIPE_FULL;
}
else                              /* pipe element available */
{
   data = &NUSE_Pipe_Data[pipe][NUSE_Pipe_Head[pipe]];
   for (i=0; i<msgsize; i++)
   {
      *data++ = *message++;
   }
   NUSE_Pipe_Head[pipe] += msgsize;
   if (NUSE_Pipe_Head[pipe] == (NUSE_Pipe_Size[pipe] * msgsize))
   {
      NUSE_Pipe_Head[pipe] = 0;
   }
   NUSE_Pipe_Items[pipe]++;
   return_value = NUSE_SUCCESS;
}


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

When blocking is enabled, the code becomes more complex:

do
{
   if (NUSE_Pipe_Items[pipe] == NUSE_Pipe_Size[pipe]) /* pipe
                                                         full */
   {
      if (suspend == NUSE_NO_SUSPEND)
      {
         return_value = NUSE_PIPE_FULL;
      }
      else
      {                                           /* block task */
         NUSE_Pipe_Blocking_Count[pipe]++;
         NUSE_Suspend_Task(NUSE_Task_Active, (pipe << 4) |
                           NUSE_PIPE_SUSPEND);
         return_value =
            NUSE_Task_Blocking_Return[NUSE_Task_Active];
         if (return_value != NUSE_SUCCESS)
         {
            suspend = NUSE_NO_SUSPEND;
         }
      }
   }
   else                             /* pipe element available */
   {  
      data = &NUSE_Pipe_Data[pipe][NUSE_Pipe_Head[pipe]];
      for (i=0; i<msgsize; i++)
      {
         *data++ = *message++;
      }
      NUSE_Pipe_Head[pipe] += msgsize;
      if (NUSE_Pipe_Head[pipe] == (NUSE_Pipe_Size[pipe] *
                                   msgsize))
      {
         NUSE_Pipe_Head[pipe] = 0;
      }
      NUSE_Pipe_Items[pipe]++;
      if (NUSE_Pipe_Blocking_Count[pipe] != 0)
      {
         U8 index;         /* check whether a task is blocked
                              on this pipe */
         NUSE_Pipe_Blocking_Count[pipe]--;
         for (index=0; index<NUSE_TASK_NUMBER; index++)
         {
            if ((LONIB(NUSE_Task_Status[index]) ==
                  NUSE_PIPE_SUSPEND)
                  && (HINIB(NUSE_Task_Status[index]) == pipe))
            {
               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 pipe is full and suspend is set to NUSE_NO_SUSPEND, the API call exits with NUSE_PIPE_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 pipe) the code loops back to the top.

If the pipe is not full, the supplied message is stored using the NUSE_Pipe_Head[] index to store the message in the pipe’s data area. A check is made on whether any tasks are suspended (waiting to receive) on the pipe. 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