- •List of Figures
- •List of Tables
- •Preface
- •1 Requirements
- •1.1 General Requirements
- •1.2 Memory Requirements
- •1.3 Performance
- •1.4 Portability
- •2 Concepts
- •2.1.1 Compiling and Linking
- •2.2 Loading and Execution of Programs
- •2.3 Preemptive Multitasking
- •2.3.1 Duplication of Hardware
- •2.3.2 Task Switch
- •2.3.3 Task Control Blocks
- •2.3.4 De-Scheduling
- •2.4 Semaphores
- •2.5 Queues
- •2.5.1 Ring Buffers
- •2.5.2 Ring Buffer with Get Semaphore
- •2.5.3 Ring Buffer with Put Semaphore
- •2.5.4 Ring Buffer with Get and Put Semaphores
- •3 Kernel Implementation
- •3.1 Kernel Architecture
- •3.2 Hardware Model
- •3.2.1 Processor
- •3.2.2 Memory Map
- •3.2.3 Peripherals
- •3.2.4 Interrupt Assignment
- •3.2.5 Data Bus Usage
- •3.3 Task Switching
- •3.4 Semaphores
- •3.4.1 Semaphore Constructors
- •3.4.2 Semaphore Destructor
- •3.4.3 Semaphore P()
- •3.4.4 Semaphore Poll()
- •3.4.5 Semaphore V()
- •3.5 Queues
- •3.5.1 Ring Buffer Constructor and Destructor
- •3.5.2 RingBuffer Member Functions
- •3.5.3 Queue Put and Get Functions
- •3.5.4 Queue Put and Get Without Disabling Interrupts
- •3.6 Interprocess Communication
- •3.7 Serial Input and Output
- •3.7.1 Channel Numbers
- •3.7.2 SerialIn and SerialOut Classes and Constructors/Destructors
- •3.7.3 Public SerialOut Member Functions
- •3.7.4 Public SerialIn Member Functions
- •3.8 Interrupt Processing
- •3.8.1 Hardware Initialization
- •3.8.2 Interrupt Service Routine
- •3.9 Memory Management
- •3.10 Miscellaneous Functions
- •4 Bootstrap
- •4.1 Introduction
- •4.3.1 Task Parameters
- •4.3.2 Task Creation
- •4.3.3 Task Activation
- •4.3.4 Task Deletion
- •5 An Application
- •5.1 Introduction
- •5.2 Using the Monitor
- •5.3 A Monitor Session
- •5.4 Monitor Implementation
- •6 Development Environment
- •6.1 General
- •6.2 Terminology
- •6.3 Prerequisites
- •6.3.1 Scenario 1: UNIX or Linux Host
- •6.3.2 Scenario 2: DOS Host
- •6.3.3 Scenario 3: Other Host or Scenarios 1 and 2 Failed
- •6.4 Building the Cross-Environment
- •6.4.1 Building the GNU cross-binutils package
- •6.4.2 Building the GNU cross-gcc package
- •6.4.3 The libgcc.a library
- •6.5 The Target Environment
- •6.5.2 The skip_aout Utility
- •7 Miscellaneous
- •7.1 General
- •7.2 Porting to different Processors
- •7.2.1 Porting to MC68000 or MC68008 Processors
- •7.2.2 Porting to Other Processor families
- •7.3 Saving Registers in Interrupt Service Routines
- •A Appendices
- •A.1 Startup Code (crt0.S)
- •A.3 Task.cc
- •A.6 Semaphore.hh
- •A.7 Queue.hh
- •A.8 Queue.cc
- •A.9 Message.hh
- •A.10 Channels.hh
- •A.11 SerialOut.hh
- •A.12 SerialOut.cc
- •A.13 SerialIn.hh
- •A.14 SerialIn.cc
- •A.15 TaskId.hh
- •A.18 ApplicationStart.cc
- •A.19 Monitor.hh
- •A.20 Monitor.cc
- •A.22 SRcat.cc
- •Index
3. Kernel Implementation |
59 |
|
|
3.7Serial Input and Output
The basic model for serial input and output has already been discussed in Section 2.5.3 and presented in Figure 2.14. In principle, the input and output directions are completely independent of each other, except for the software flow control (e.g. XON/XOFF protocol) at the hardware side of the receive buffer, and possibly line editing functions (e.g. echoing of characters) at the task side of the receive buffer.
This section deals with the task side of both the receive and transmit buffers; the hardware side is discussed in Section 3.8. Strictly speaking, the aspects of serial input and output discussed here are not part of the operating system itself. But they are so commonly used that it is appropriate to include them in the kernel.
Several tasks sharing one serial input or output channel is a common source of trouble. A typical example is a router that receives data packets on several serial ports and transmits them (after possibly modifying them) on other serial ports.
??? What is the trouble ???An implementation with three serial ports could be as shown in Figure 3.5.
Rx Buf 0 |
Rx T0 |
Packet |
Tx T0 |
Tx Buf 0 |
|
Handler |
|||||
|
|
|
|
||
Rx Buf 1 |
Rx T1 |
|
Tx T1 |
Tx Buf 1 |
|
Rx Buf 2 |
Rx T2 |
Packet |
Tx T2 |
Tx Buf 2 |
|
Handler |
|||||
|
|
|
|
||
|
Queue of idle Packet Handlers |
|
|
Packet |
Packet |
Packet |
Packet |
Handler |
Handler |
Handler |
Handler |
FIGURE 3.5 Serial Router (Version A)
60 |
3.7 Serial Input and Output |
|
|
For each serial port, there is a receive task (RX T0..2) that receives characters from its serial port. If a complete packet is received, the receive task fetches a pointer to an idle packet handler task and sends a message containing the packet to that task. The packet handler task processes the packet and may create other packets that are sent as messages to some of the transmit tasks (Tx T0..2). When a packet handler has finished processing a packet, it puts itself back into the queue of idle packet handlers. The transmit tasks merely send the packets out on their respective serial outputs. In this implementation, each serial input is handled by one task Rx Ti, and each serial output is handled by a task Tx Ti dedicated to that port. The main purpose of these tasks is to maintain atomicity at packet level. That is, these tasks are responsible for assembling and de-assembling sequences of characters into packets and vice versa. Since the receive and transmit tasks are statically bound to their serial ports, there is no conflict between tasks with regard to ports.
Now assume there is some mechanism by which a task can temporarily claim a serial input and output port for a period of time so that no other task can use that port at the same time. Then the number of tasks involved could be reduced as shown in Figure 3.6.
Rx Buf 0 |
Packet |
|
Tx Buf 0 |
|
Handler |
|
|||
|
|
|
||
Rx Buf 1 |
Packet |
|
Tx Buf 1 |
|
Handler |
|
|||
|
|
|
||
Rx Buf 2 |
Packet |
Packet |
Tx Buf 2 |
|
Handler |
Handler |
|||
|
|
Queue of unserved input ports
Packet |
Packet |
Packet |
Packet |
Handler |
Handler |
Handler |
Handler |
FIGURE 3.6 Serial Router (Version B)
3. Kernel Implementation |
61 |
|
|
At the output side, a packet handler merely claims a serial output port when it needs to transmit a packet. The queue of idle packet handlers has been replaced by a queue of input ports that have no packet handlers assigned; this queue initially contains all serial input ports. A packet handler first gets an unserved input port, so that shortly after start-up of the system each input port is served by a packet handler; the other packet handlers are blocked at the queue for unserved inputs. A packet handler serving an input first claims that input port and starts collecting the characters of the next packet. When a complete packet is received, the packet handler releases the input port (which causes the next idle packet server to take over that port), puts it back into the queue of unserved input ports, and continues processing of the packet. Like in router version A, this scheme schedules the packet handlers between the ports in a fair way. Sometimes, in particular if the serial ports need to have different priorities (e.g. due to different communication speeds), a scheduling on a per-port basis is required. This leads to an even simpler implementation shown in Figure 3.7.
Rx Buf 0 |
Packet |
|
Tx Buf 0 |
|
Handler |
|
|||
|
|
|
||
Rx Buf 1 |
Packet |
|
Tx Buf 1 |
|
Handler |
|
|||
|
|
|
||
Rx Buf 2 |
Packet |
Packet |
Tx Buf 2 |
|
Handler |
Handler |
|||
|
|
FIGURE 3.7 Serial Router (Version C)
With this implementation, one can e.g. assign different priorities to each input port and use different numbers of packet servers. The packet servers queue themselves by claiming the input port, so that the queue of unserved input ports used in version B becomes obsolete. As a consequence, no initialization of that queue is required. The code for the packet handler becomes as simple as that:
Semaphore Port_0_Input, Port_0_Output;
Semaphore Port_1_Input, Port_1_Output;
Semaphore Port_2_Input, Port_2_Output;
void packet_handler_main(Semaphore & Port_i_Input)
{
for (;;)
{
Port_i_Input.P();
62 |
3.7 Serial Input and Output |
|
|
char * Packet = getPacket(port);
Port_i_Input.V(); |
|
handlePacket(Packet); |
// deletes Packet |
}
}
The semaphores control the claiming and releasing of the serial input and output ports. Using semaphores explicitly is not very elegant though. First, it must be assured that any task using a serial port is claiming and releasing the corresponding semaphore. Also it is often desirable to have a “dummy” port (such as /dev/nul in UNIX) that behaves like a real serial port. Such a dummy port could be used e.g. to turn logging information on and off. But claiming and releasing dummy ports makes little sense. In general, the actual implementation of a port should be hidden from the interface using the port. Thus for a clean objectoriented design, the semaphores should be maintained by the port rather than by an application using the port. This leads to the kernel implementation of serial input and output described in the following sections.
3.7.1 Channel Numbers
It is convenient to refer to serial ports by channel numbers. In our hardware model, we assumed one DUART with two serial ports, which we call SERIAL_0 and SERIAL_1. These are normally operated in an interrupt-driven manner. Sometimes however, it is required to have a polled operation available, in particular before the interrupt system has been initialized, and in the case of fatal system errors. For achieving this polled operation, the channels
SERIAL_0_POLLED and SERIAL_1_POLLED are provided. Finally, the
DUMMY_SERIAL channel is used when the actual serial output needs to be suppressed.
1 |
// Channels.hh |
|
... |
|
|
5 |
enum Channel { |
|
6 |
SERIAL_0 |
= 0, |
7 |
SERIAL_1 |
= 1, |
8 |
SERIAL_0_POLLED |
= 4, |
9 |
SERIAL_1_POLLED |
= 5, |
10 |
DUMMY_SERIAL |
= 8, |
11 |
}; |
|
Often, one would like to turn the serial output on and off, e.g. for debugging purposes. Therefore, channel variables rather than explicit channels are used:
1 // Channels.hh
...
13extern Channel MonitorIn;
14extern Channel MonitorOut;
15extern Channel ErrorOut;