· Sending messages to full message queues can cause the sending task to block, and receiving messages from an empty message queue can cause a receiving task to block
· Tasks can send to and receive from message queues without blocking, via blocking with a timeout, or via blocking forever. An ISR can only send messages without blocking.
· The task-waiting list associated with a message-queue can release tasks (unblock them) in FIFO or priority-based order.When messages are sent from one task to another, the message is typically copied twice: once from the sending task’s memory area to the message queue’s and a second time from the message queue’s memory area to the task’s.
· The data itself can either be sent as the message or as a pointer to the data as the message. The first case is better suited for smaller messages, and the latter case is better suited for large messages.
· Common message-queue operations include creating and deleting message queues, sending to and receiving from message queues, and obtaining message queue information.
· Urgent messages are inserted at the head of the queue if urgent messages are supported by the message-queue implementation.
· Some common ways to use message queues for data based communication include non-interlocked and interlocked queues providing one-way or two-way data communication.
Chapter 8: Other Kernel Objects
In addition to the key kernel objects, such as tasks, semaphores, and message queues, kernels provide many other important objects as well. Because every kernel is different, the number of objects a given kernel supports can vary from one to another. This chapter explores additional kernel objects common to embedded systems development, although the list presented here is certainly not all-inclusive. Specifically, this chapter focuses on:
· other kernel objects, including pipes, event registers, signals, and condition variables,
· object definitions and general descriptions,
· associated operations, and
· typical applications of each.
Pipes are kernel objects that provide unstructured data exchange and facilitate synchronization among tasks. In a traditional implementation, a pipe is a unidirectional data exchange facility, as shown in Figure 8.1. Two descriptors, one for each end of the pipe (one end for reading and one for writing), are returned when the pipe is created. Data is written via one descriptor and read via the other. The data remains in the pipe as an unstructured byte stream. Data is read from the pipe in FIFO order.
Figure 8.1: A common pipe-unidirectional.
A pipe provides a simple data flow facility so that the reader becomes blocked when the pipe is empty, and the writer becomes blocked when the pipe is full. Typically, a pipe is used to exchange data between a data-producing task and a data-consuming task, as shown in Figure 8.2. It is also permissible to have several writers for the pipe with multiple readers on it.
Figure 8.2: Common pipe operation.
Note that a pipe is conceptually similar to a message queue but with significant differences. For example, unlike a message queue, a pipe does not store multiple messages. Instead, the data that it stores is not structured, but consists of a stream of bytes. Also, the data in a pipe cannot be prioritized; the data flow is strictly first-in, first-out FIFO. Finally, as is described below, pipes support the powerful select operation, and message queues do not.
8.2.1 Pipe Control Blocks
Pipes can be dynamically created or destroyed. The kernel creates and maintains pipe-specific information in an internal data structure called a pipe control block. The structure of the pipe control block varies from one implementation to another. In its general form, a pipe control block contains a kernel-allocated data buffer for the pipe’s input and output operation. The size of this buffer is maintained in the control block and is fixed when the pipe is created; it cannot be altered at run time. The current data byte count, along with the current input and output position indicators, are part of the pipe control block. The current data byte count indicates the amount of readable data in the pipe. The input position specifies where the next write operation begins in the buffer. Similarly, the output position specifies where the next read operation begins. The kernel creates two descriptors that are unique within the system I/O space and returns these descriptors to the creating task. These descriptors identify each end of the pipe uniquely.
Two task-waiting lists are associated with each pipe, as shown in Figure 8.3. One waiting list keeps track of tasks that are waiting to write into the pipe while it is full; the other keeps track of tasks that are waiting to read from the pipe while it is empty.
Figure 8.3: Pipe control block.
A pipe has a limited number of states associated with it from the time of its creation to its termination. Each state corresponds to the data transfer state between the reader and the writer of the pipe, as illustrated in Figure 8.4.
Figure 8.4: States of a pipe.
8.2.3 Named and Unnamed Pipes
A kernel typically supports two kinds of pipe objects: named pipes and unnamed pipes. A named pipe, also known as FIFO, has a name similar to a file name and appears in the file system as if it were a file or a device. Any task or ISR that needs to use the named pipe can reference it by name. The unnamed pipe does not have a name and does not appear in the file system. It must be referenced by the descriptors that the kernel returns when the pipe is created, as explained in more detail in the following sections.
8.2.4 Typical Pipe Operations
The following set of operations can be performed on a pipe:
· create and destroy a pipe,
· read from or write to a pipe,
· issue control commands on the pipe, and
· select on a pipe.
Create and Destroy
Create and destroy operations are available, as shown in Table 8.1.
Table 8.1: Create and destroy operations.
Operation |
Description |
Pipe |
Creates a pipe |
Open |
Opens a pipe |
Close |
Deletes or closes a pipe |
The pipe operation creates an unnamed pipe. This operation returns two descriptors to the calling task, and subsequent calls reference these descriptors. One descriptor is used only for writing, and the other descriptor is used only for reading.
Creating a named pipe is similar to creating a file; the specific call is implementation-dependent. Some common names for such a call are mknod and mkfifo. Because a named pipe has a recognizable name in the file system after it is created, the pipe can be opened using the open operation. The calling task must specify whether it is opening the pipe for the read operation or for the write operation; it cannot be both.
Читать дальше