| .. _microkernel_pipes: |
| |
| Pipes |
| ##### |
| |
| Concepts |
| ******** |
| |
| The microkernel's :dfn:`pipe` object type is an implementation of a traditional |
| anonymous pipe. |
| |
| A pipe allows a task to send a byte stream to another task. The pipe can be |
| configured with a ring buffer which holds data that has been sent |
| but not yet received; alternatively, the pipe may have no ring buffer. |
| Pipes can be used to transfer chunks of data in whole or in part, and either |
| synchronously or asynchronously. |
| |
| Any number of pipes can be defined in a microkernel system. Each pipe |
| needs |
| |
| * A **name** to uniquely identify it. |
| |
| * A **size**, in bytes, of the ring buffer. Note that a size of zero defines |
| a pipe with no ring buffer. |
| |
| Sending Data |
| ============ |
| |
| A task sends data to a pipe by specifying a pointer to the data bytes |
| to be sent. It also specifies both the number of data bytes available |
| and a :dfn:`pipe option` that indicates the minimum number of data bytes |
| the pipe must accept. The following pipe option values are supported: |
| |
| ``_ALL_N`` |
| |
| Specifies that **all** available data bytes must be accepted by the pipe. |
| When this requirement is not fulfilled, the send request either fails or |
| performs a partial send. |
| |
| ``_1_TO_N`` |
| |
| Specifies that **at least one** data byte must be accepted by the pipe. |
| When this requirement is not fulfilled, the send request fails. |
| |
| ``_0_TO_N`` |
| |
| Specifies that **any number** of data bytes, including zero, may be accepted |
| by the pipe; the send request never fails. |
| |
| The pipe accepts data bytes from the sending task if they can be delivered |
| by copying them directly to the receiving task. If the sending task is unable |
| to wait, or has waited as long as it can, the pipe can also accept data bytes |
| by copying them to its ring buffer for later delivery. The ring buffer is used |
| only when necessary to minimize copying of data bytes. |
| |
| Upon the completion of a send operation, a :dfn:`return code` is provided to |
| indicate whether the send request was satisfied. The sending task can also read |
| the ``bytes written`` argument attribute to determine how many data bytes were |
| accepted by the pipe, and subsequently allowing it to deal with any unsent data |
| bytes. |
| |
| Data sent to a pipe that does not have a ring buffer is sent synchronously; |
| that is, when the send operation is complete, the sending task knows that the |
| receiving task has received the data that was sent. Data sent to a pipe |
| that has a ring buffer is sent asynchronously; that is, when the send operation |
| is complete, some or all of the data that was sent may still be in the pipe |
| waiting for the receiving task to receive it. |
| |
| Incomplete Send Requests |
| ------------------------ |
| |
| Although a pipe endeavors to accept all available data bytes when the |
| ``_ALL_N`` pipe option is specified, it does not guarantee that the |
| data bytes will be accepted in an "all or nothing" manner. When the pipe |
| is able to accept at least one data byte, it returns :c:macro:`RC_INCOMPLETE` |
| to notify the sending task that its request was not fully satisfied. When |
| the pipe is unable to accept any data bytes, it returns :c:macro:`RC_FAIL`. |
| |
| One example of a situation that can result in an incomplete send is a |
| time-limited send request through an unbuffered pipe. If the receiving task |
| chooses to receive only a subset of the sender's data bytes, and the send |
| operation times out before the receiving task attempts to receive the |
| remainder, an incomplete send occurs. |
| |
| Sending using a Memory Pool Block |
| --------------------------------- |
| |
| A task that sends large chunks of data through a pipe may be able to improve |
| its efficiency by placing the data into a memory pool block and sending |
| the block. The pipe treats the memory block as a temporary addition to |
| its ring buffer, allowing it to immediately accept the data bytes without |
| copying them. Once all of the data bytes in the block have been delivered |
| to the receiving task, the pipe automatically frees the block back to the |
| memory pool. |
| |
| Data sent using a memory pool block is always sent asynchronously, even for |
| a pipe with no ring buffer of its own. Likewise, the pipe always accepts all |
| of the available data in the block -- a partial transfer never occurs. |
| |
| Receiving Data |
| ============== |
| |
| A task receives from a pipe by specifying a pointer to an area to receive |
| the data bytes that were sent. It also specifies both the desired number |
| of data bytes and a :dfn:`pipe option` that indicates the minimum number of |
| data bytes the pipe must deliver. The following pipe option values |
| are supported: |
| |
| ``_ALL_N`` |
| |
| Specifies that all desired number of data bytes must be received. |
| When this requirement is not fulfilled, the receive request either fails or |
| performs a partial receive. |
| |
| ``_1_TO_N`` |
| |
| Specifies that at least one data byte must be received. When this requirement |
| is not fulfilled, the receive request fails. |
| |
| ``_0_TO_N`` |
| |
| Specifies that any number of data bytes (including zero) may be |
| received; the receive request never fails. |
| |
| The pipe delivers data bytes by copying them directly from the sending task |
| or from the pipe's ring buffer. Data bytes taken from the ring buffer are |
| delivered in a first in, first out manner. |
| |
| When a pipe is unable to deliver the specified minimum number of data bytes, |
| the receiving task may choose to wait until they can be delivered. |
| |
| Upon completion of a receive operation, a :dfn:`return code` is provided to |
| indicate whether the receive request was satisfied. The receiving task also |
| can read the ``bytes read`` argument attribute to determine how many |
| data bytes were delivered by the pipe. |
| |
| Incomplete Receive Requests |
| --------------------------- |
| |
| Although a pipe endeavors to deliver all desired data bytes when the |
| ``_ALL_N`` pipe option is specified, it does not guarantee that the |
| data bytes will be delivered in an "all or nothing" manner. When the pipe |
| is able to deliver at least one data byte, it returns :c:macro:`RC_INCOMPLETE` |
| to notify the receiving task that its request was not fully satisfied. When |
| the pipe is unable to deliver any data bytes, it returns :c:macro:`RC_FAIL`. |
| |
| An example of a situation that can result in an incomplete receive is a |
| time-limited receive request through an unbuffered pipe. If the sending task |
| sends fewer than the desired number of data bytes, and the receive |
| operation times out before the sending task attempts to send the remainder, |
| an incomplete receive occurs. |
| |
| Receiving using a Memory Pool Block |
| ----------------------------------- |
| |
| A task can achieve the effect of receiving data from a pipe into a memory pool |
| block by pre-allocating a block and then receiving the data into it. |
| |
| Sharing a Pipe |
| ============== |
| |
| A pipe is typically used by a single sending task and a single receiving |
| task; however, it is possible for a pipe to be shared by multiple sending |
| tasks or multiple receiving tasks. |
| |
| Care must be taken when a pipe is shared by multiple sending tasks to |
| ensure the data bytes they send do not become interleaved unexpectedly; |
| using the ``_ALL_N`` pipe option helps to ensure that each data chunk is |
| transferred in a single operation. The same is true when multiple receiving |
| tasks are reading from the same pipe. |
| |
| Purpose |
| ******* |
| |
| Use a pipe to transfer data when the receiving task needs the ability |
| to split or merge the data items generated by the sending task. |
| |
| Usage |
| ***** |
| |
| Defining a Pipe |
| =============== |
| |
| The following parameters must be defined: |
| |
| *name* |
| This specifies a unique name for the pipe. |
| |
| *buffer_size* |
| This specifies the size in bytes of the pipe's ring buffer. |
| If no ring buffer is to be used specify zero. |
| |
| Public Pipe |
| ----------- |
| |
| Define the pipe in the application's MDEF using the following syntax: |
| |
| .. code-block:: console |
| |
| PIPE name buffer_size |
| |
| For example, the file :file:`projName.mdef` defines a pipe with a 1 KB ring |
| buffer as follows: |
| |
| .. code-block:: console |
| |
| % PIPE NAME BUFFERSIZE |
| % =============================== |
| PIPE DATA_PIPE 1024 |
| |
| A public pipe can be referenced by name from any source file that includes |
| the file :file:`zephyr.h`. |
| |
| Private Pipe |
| ------------ |
| |
| Define the pipe in a source file using the following syntax: |
| |
| .. code-block:: c |
| |
| DEFINE_PIPE(name, size); |
| |
| For example, the following code defines a private pipe named ``PRIV_PIPE``. |
| |
| .. code-block:: c |
| |
| DEFINE_PIPE(PRIV_PIPE, 1024); |
| |
| To use this pipe from a different source file use the following syntax: |
| |
| .. code-block:: c |
| |
| extern const kpipe_t PRIV_PIPE; |
| |
| Example: Writing Fixed-Size Data Items to a Pipe |
| ================================================ |
| |
| This code uses a pipe to send a series of fixed-size data items |
| to a consuming task. |
| |
| .. code-block:: c |
| |
| void producer_task(void) |
| { |
| struct item_type data_item; |
| int amount_written; |
| |
| while (1) { |
| /* generate a data item to send */ |
| data_item = ... ; |
| |
| /* write the entire data item to the pipe */ |
| task_pipe_put(DATA_PIPE, &data_item, sizeof(data_item), |
| &amount_written, _ALL_N, TICKS_UNLIMITED); |
| |
| } |
| } |
| |
| Example: Reading Fixed-Size Data Items from a Pipe |
| ================================================== |
| |
| This code uses a pipe to receive a series of fixed-size data items |
| from a producing task. To improve performance, the consuming task |
| waits until 20 data items are available then reads them as a group, |
| rather than reading them individually. |
| |
| .. code-block:: c |
| |
| void consumer_task(void) |
| { |
| struct item_type data_items[20]; |
| int amount_read; |
| int i; |
| |
| while (1) { |
| /* read 20 complete data items at once */ |
| task_pipe_get(DATA_PIPE, &data_items, sizeof(data_items), |
| &amount_read, _ALL_N, TICKS_UNLIMITED); |
| |
| /* process the data items one at a time */ |
| for (i = 0; i < 20; i++) { |
| ... = data_items[i]; |
| ... |
| } |
| } |
| } |
| |
| Example: Reading a Stream of Data Bytes from a Pipe |
| =================================================== |
| |
| This code uses a pipe to process a stream of data bytes from a |
| producing task. The pipe is read in a non-blocking manner to allow |
| the consuming task to perform other work when there are no |
| unprocessed data bytes in the pipe. |
| |
| .. code-block:: c |
| |
| void consumer_task(void) |
| { |
| char data_area[20]; |
| int amount_read; |
| int i; |
| |
| while (1) { |
| /* consume any data bytes currently in the pipe */ |
| while (task_pipe_get(DATA_PIPE, &data_area, sizeof(data_area), |
| &amount_read, _1_TO_N, TICKS_NONE) == RC_OK) { |
| /* now have from 1 to 20 data bytes */ |
| for (i = 0; i < amount_read; i++) { |
| ... = data_area[i]; |
| ... |
| } |
| } |
| |
| /* do other processing */ |
| ... |
| } |
| } |
| |
| APIs |
| **** |
| |
| Pipe APIs provided by :file:`microkernel.h` |
| =========================================== |
| |
| :cpp:func:`task_pipe_put()` |
| Write data to a pipe, with time limited waiting. |
| |
| :c:func:`task_pipe_block_put()` |
| Write data to a pipe from a memory pool block. |
| |
| :cpp:func:`task_pipe_get()` |
| Read data from a pipe, or fails and continues if data isn't there. |