Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Sauermann J.Realtime operating systems.Concepts and implementation of microkernels for embedded systems.1997.pdf
Скачиваний:
29
Добавлен:
23.08.2013
Размер:
1.32 Mб
Скачать

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.