- •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
7. Miscellaneous |
125 |
|
|
7.3Saving Registers in Interrupt Service Routines
An interrupt service routine must not alter any registers. For a simple interrupt service routine, this can be achieved by saving those registers that the interrupt service routine uses and by restoring them after completion.
|
1 | crt0.S |
|
|
... |
|
|
|
133 |
_duart_isr: |
|
| |
134 |
MOVE.B |
#LED_YELLOW, wLED_ON |
| yellow LED on |
135 |
MOVEM.L |
D0-D7/A0-A6, -(SP) |
| save all registers |
... |
|
|
|
216 |
MOVEM.L (SP)+, D0-D7/A0-A6 |
| restore all registers |
|
... |
|
|
|
This is a safe way, but not the most efficient one. Considering the code between line 135 and 216, only registers D0, D1, D7, and A0 are modified by the interrupt service routine. So it would be sufficient to save and restore only these registers. However, the interrupt service routine calls other functions which may alter other registers, and these need to be saved as well. In order to save only those registers changed by the interrupt service routine and the functions it calls, one needs to know which registers are altered by the functions generated by the compiler. For some compilers, there is a convention such as “any function generated by the compiler may alter registers D0 through D3 and A0 through A3 and leaves all other registers intact”. The register preserving convention is usually documented for a compiler in a chapter like “function calling conventions”. In case of gcc, there is a file config/<machine>/<machine>.h in the directory where the compiler sources are installed, where <machine> stands for the target for which the compiler was configured. In our case, this would be the file config/m68k/m68k.h. In this file, a macro CALL_USED_REGISTERS is defined, which marks those registers with 1 that are changed by a function call. The first line refers to data registers, the next line to address registers and the third line to floating point registers.
// config/m68k/m68k.h
...
#define CALL_USED_REGISTERS \
{1, 1, 0, |
0, 0, 0, 0, 0, |
\ |
1, 1, 0, |
0, 0, 0, 0, 1, |
\ |
1, 1, 0, |
0, 0, 0, 0, 0 } |
|
That is, if the compiler is configured to use the file m68k.h, then registers D0, D1, A0, A1, A7, and floating point registers FP0 and FP1 may be altered by function calls generated by the compiler. If the compiler uses other registers, it saves and restores them automatically. Although A7 (i.e. the SP) is altered, it is restored by the function call mechanism. With this knowledge, one could safely write
|
1 | crt0.S |
|
... |
|
|
133 |
_duart_isr: |
| |
126 |
7.3 Saving Registers in Interrupt Service Routines |
|
|
134 |
MOVE.B #LED_YELLOW, wLED_ON |
| yellow LED on |
135 |
MOVEM.L D0/D1/D7/A0/A1, -(SP) |
| save registers used later on |
... |
|
|
216 |
MOVEM.L (SP)+, D0/D1/D7/A0/A1 |
| restore registers |
... |
|
|
This causes only 5 instead of 15 registers to be saved and restored. Since compilers tend to choose lower register numbers (D0, D1, A0, A1, FP0, and FP1) for registers that they may destroy, we chose a high register (D7) for the interrupt status so that it does not need to be saved before C++ function calls.
7. Miscellaneous |
127 |
|
|
7.4Semaphores with time-out
So far, the state machine shown in Figure 7.1 is used for the state of a task.
|
STARTED |
||
|
Start() |
||
Terminate() |
|
P() |
|
RUN |
BLKD |
||
Error |
|||
|
V() |
||
|
|
||
|
Sleep() |
Time-out |
|
SLEEP
FAILED,
TERMINATED
FIGURE 7.1 Task State Machine
Sometimes a combination of the states SLEEP and BLKD is required. One example is waiting for a character, but indicating a failure if the character is not received within a certain period of time. With the present state machine, there are several possibilities to achieve this, but none is perfect. We could, for instance, first Sleep() for the period and then Poll() to check if a character has arrived during Sleep(). This would lead to bad performance, in particular if the period is long and if time-out rarely occurs. One could increase the performance by performing Sleep() and Poll() in a loop with smaller intervals, but this would cost extra processing time. Another alternative would be to use two additional tasks: one that is responsible for receiving characters, and the other for sleeping. Any of these additional tasks would send an event to the task that is actually waiting for a character or time-out, indicating that the character has been received or that timeout has occurred. All this is significant effort for an otherwise simple problem. The best solution is to extend the task state machine by a new state S_BLKD, as shown in Figure 7.2.
128 |
7.4 Semaphores with time-out |
|
|
|
STARTED |
|
|
|
|
Start() |
|
|
|
Terminate() |
|
P() |
|
|
RUN |
|
BLKD |
||
Error |
V() |
|||
|
|
|||
|
|
|
||
|
Sleep() |
P_Timeout() |
||
|
|
|
||
|
Time-out |
|
||
|
|
V() or |
|
|
|
SLEEP |
Time-out |
S_BLKD |
|
|
|
|||
FAILED,
TERMINATED
FIGURE 7.2 Task State Machine with new State S_BLKD
The new state S_BLKD combines the properties of states SLEEP and BLKD by returning the task to state RUN if either the resource represented by a semaphore is available (the character is received in our example) or the time-out provided with the call Semaphore::P_Timeout(unsigned int time) has expired. The task calling P_Timeout() must of course be able to determine whether the resource is available or time-out has occurred. That is, P_Timeout() will return e.g. an int indicating the result rather than Semaphore::P(), which returns void. The new state can be implemented as follows, where the details are left as an exercise to the reader. ??? willst Du die Lösung nicht verraten ???
•The class Task gets two new data members int P_Timeout_Result and
Semaphore * P_Timeout_Semaphore.
•The class Semaphore is extended by a new member function int P_Timeout(unsigned long time). This function is similar to P() with the following differences: If a resource is available, P_Timeout() returns 0 indicating no time-out. Otherwise it sets the current task’s member
P_Timeout_Semaphore to the semaphore on which P_Timeout is performed, sets the current task’s TaskSleep to time, and blocks the task by setting both the BLKD and the SLEEP bits in the current task’s TaskStatus. After the task has been unblocked by either a V() call or timeout, it returns P_Timeout_Result of the current task.
7. Miscellaneous |
129 |
|
|
•Semaphore::V() is modified so that it sets the P_Timeout_Result of a task that is unblocked to 0, indicating no time-out. That task will then return 0 as the result of its P_Timeout() function call. It also clears the SLEEP bit of the task that is unblocked.
•If the sleep period of a task has expired (after label L_SLEEP_LP in crt0.S), then the BLKD bit is examined besides clearing the SLEEP bit of the task. If it is set, i.e. if the task is in state S_BLKD, then this bit is cleared as well, the task is removed from the semaphore waiting chain (using the P_Timeout_Semaphore member of the task) and P_Timeout_Result is set to nonzero, indicating time-out.
After the semaphore class has been extended this way, the queue classes are extended accordingly, implementing member functions like Get_Timeout() and Put_Timeout(). Since all these changes require considerable effort, they should only be implemented when needed. As a matter of fact, we have implemented quite complex applications without the need for time-outs in semaphores.
