Commit 28fdd6df authored by Ben Avison's avatar Ben Avison
Browse files

Improve behaviour when used with RTSupport threads

Two of the locking data types - mutex_t and spinrw_t - offer a
lock-with-sleep function. These can cause the current thread to sleep until
another thread releases the lock. Sleeping is achieved using OS_UpCall 6, and
so supports thread suspension by either TaskWindow or RTSupport.

This is usually fine, but RTSupport, unlike TaskWindow, permits threads to
be assigned differing priority levels. Thus, if a lock is held by a
low-priority thread when a high-priority thread attempts to lock it, the
low-priority thread continues to run (this is a common feature of
priority-based threading systems known as priority inversion). This is
unavoidable in some situations, but ideally the high-priority thread should
be resumed as soon as the shared resource (in this case the lock) is
released by the low-priority thread. Previously, there was no mechanism to
achieve this with either of these two lock types.

One challenge is to achieve this without making the unlock functions
unnecessarily slow in the common case where there is no higher-priority
thread to resume. This is achieved in two ways:

1) Yielding is performed using RT_Yield to explicitly rescan pollwords for
   RTSupport threads only. Since TaskWindow threads are all implicitly of
   identical priority, we avoid using an OS_UpCall 6 yield, which would
   cause all TaskWindow pollwords to be rescanned as well.

2) An internal flag inside the lock struct is used to remember whether any
   other threads attempted to lock it when it was already locked, and a yield
   is only performed when unlocking if this indicates there is (or might be)
   another thread waiting.

The situation is complicated by the fact that (despite what the SWI docs say)
RT_Yield can cause IRQs to trigger even if the caller has them disabled. This
can however be inferred from the entry conditions of RTSupport callback
routines, which usually have IRQs enabled, so if any IRQ-enabled routine is
pending when we call RT_Yield, IRQs will become enabled. This is a problem
because if the calling thread also holds non-re-entrant locks (e.g. a
spinlock) then we defeat those locks by pre-empting the thread in this way.
To deal with this, we check that IRQs are enabled prior to calling RT_Yield,
even though this will also discard many cases where a yield would have been
desirable. A future enhancement would be to maintain a thread-local flag, and
trigger RT_Yield when the outermost IRQ-disabling lock is released (even if
that lock type wouldn't ordinarily cause other threads to sleep) but this
presumes thread-local storage, which is not yet implemented for RTSupport.

More sophisticated schemes are possible, such as by comparing the relative
priorities of the lock holder and waiter, but these introduce additional
overheads into the lock and unlock functions, so unless and until it becomes
clear that they are required, they have not been implemented.
parent 72e3f8f3
Pipeline #4081 passed with stages
in 3 minutes and 9 seconds
......@@ -32,36 +32,53 @@
* the mutex from the background (e.g. in an interrupt handler).
*/
#include "kernel.h"
#include "swis.h"
#include "Interface/RTSupport.h"
#include "SyncLib/synclib.h"
bool mutex_try_lock(mutex_t *mutex)
{
if (atomic_update(MUTEX_LOCKED, (uint32_t *) mutex) == MUTEX_LOCKED)
if (atomic_update_byte(false, (uint8_t *) &mutex->is_unlocked) == false)
{
mutex->thread_waiting = true;
return false;
}
barrier();
return true;
}
void mutex_lock(mutex_t *mutex)
{
while (atomic_update(MUTEX_LOCKED, (uint32_t *) mutex) == MUTEX_LOCKED)
while (atomic_update_byte(false, (uint8_t *) &mutex->is_unlocked) == false)
{
mutex->thread_waiting = true;
cpuevent_wait();
}
barrier();
}
void mutex_sleep_lock(mutex_t *mutex)
{
while (atomic_update(MUTEX_LOCKED, (uint32_t *) mutex) == MUTEX_LOCKED)
_swix(OS_UpCall, _INR(0,1), 6, mutex);
while (atomic_update_byte(false, (uint8_t *) &mutex->is_unlocked) == false)
{
mutex->thread_waiting = true;
_swix(OS_UpCall, _INR(0,1), 6, &mutex->is_unlocked);
}
barrier();
}
void mutex_unlock(mutex_t *mutex)
{
barrier();
*mutex = MUTEX_UNLOCKED;
mutex->is_unlocked = true;
bool thread_waiting = atomic_update_byte(false, (uint8_t *) &mutex->thread_waiting);
barrier_sync();
cpuevent_send();
if (thread_waiting && !_kernel_irqs_disabled())
{
static const intptr_t pollword = 1;
_swix(RT_Yield, _IN(1), &pollword);
}
}
......@@ -31,6 +31,11 @@
#include <stdbool.h>
#include <stdint.h>
/* For backwards compatibility, MUTEX_LOCKED and MUTEX_UNLOCKED must expand
* to an expression suitable for assigning to a mutex_t */
#define MUTEX_LOCKED (mutex_t){ 0, 0 }
#define MUTEX_UNLOCKED (mutex_t){ 1, 0 }
/** \file mutex.h
* An implementation of a simple mutex. Does not disable interrupts while the
* mutex is held, so is suitable for mutexes that are held for an extended
......@@ -39,10 +44,10 @@
*/
/** The data block used to hold the state of a mutex. */
typedef enum
typedef struct
{
MUTEX_LOCKED,
MUTEX_UNLOCKED
intptr_t is_unlocked; /**< Lock state. Sense and size are enforced by OS_UpCall 6 semantics */
bool thread_waiting; /**< Performance hint: a second lock has (probably) been attempted */
} mutex_t;
/** Attempt to lock a mutex.
......
......@@ -30,6 +30,9 @@
GET System
GET CPU/Arch
GET APCS/$APCS
GET FSNumbers
GET NewErrors
GET RTSupport
GET ./barrier.hdr
GET ./cpuevent.hdr
GET ./init.hdr
......@@ -46,13 +49,16 @@ Pollword # 4 ; so we can support UpCall 6
Mutex_Locked * 0
Mutex_Unlocked * 1
; The counter word is treated as 2 fields:
; The counter word is treated as 3 fields:
; bit 0 set => write lock is held
; bits 1-31 = - number of read locks held (this is so moving to/from 0 locks
; bit 1 set => at least one other attempt to perform a read or write lock has
; been attempted since it became locked (i.e. another thread is waiting)
; bits 2-31 = - number of read locks held (this is so moving to/from 0 locks
; can be detected using the C flag)
; If the lock is not held at all, the word has value 0
Counter_Write * 1:SHL:0
Counter_Read * 1:SHL:1
Counter_Write * 1:SHL:0
Counter_ThreadWaiting * 1:SHL:1
Counter_Read * 1:SHL:2
Strex_Succeeded * 0
Strex_Failed * 1
......@@ -252,6 +258,8 @@ spinrw_write_try_lock_$variant ROUT
MOV a1, #Bool_True
Return , LinkNotStacked
50
ORR ip, ip, #Counter_ThreadWaiting
STR ip, Counter
MetaUnlock a2, a3
MOV a1, #Bool_False
Return , LinkNotStacked
......@@ -273,6 +281,8 @@ spinrw_write_lock_$variant ROUT
MetaUnlock , a3
Return , LinkNotStacked
50
ORR ip, ip, #Counter_ThreadWaiting
STR ip, Counter
MetaUnlock a2, a3
Pause
B %BA10
......@@ -295,6 +305,8 @@ spinrw_write_sleep_lock_$variant ROUT
MetaUnlock , a3
Return
50
ORR ip, ip, #Counter_ThreadWaiting
STR ip, Counter
MetaUnlock a2, a3
Push "r0"
ADR r1, Pollword
......@@ -304,9 +316,11 @@ spinrw_write_sleep_lock_$variant ROUT
B %BA10
spinrw_write_unlock_$variant ROUT
FunctionEntry
MOV a3, #Mutex_Unlocked
MOV a4, #Mutex_Locked
MetaLock , a4, ip
LDR a2, Counter
LDR ip, SavedCPSR
ASSERT Mutex_Locked = 0
STR a4, Counter
......@@ -316,7 +330,12 @@ spinrw_write_unlock_$variant ROUT
BarrierSync
CPUEventSend
]
Return , LinkNotStacked
TST ip, #I32_bit
Return ,, NE
TST a2, #Counter_ThreadWaiting
MOVNE a2, pc ; point r1 at a non-zero value
SWINE XRT_Yield
Return
spinrw_read_lock_$variant ROUT
MOV a3, #Mutex_Unlocked
......@@ -331,31 +350,42 @@ spinrw_read_lock_$variant ROUT
MetaUnlock a2, a3
Return , LinkNotStacked
50
ORR ip, ip, #Counter_ThreadWaiting
STR ip, Counter
MetaUnlock a2, a3
Pause
B %BA10
spinrw_read_unlock_$variant ROUT
FunctionEntry
MOV a3, #Mutex_Unlocked
MOV a4, #Mutex_Locked
MetaLock a2, a4, ip
LDR ip, Counter
ADDS ip, ip, #Counter_Read ; C set means is now unlocked
STR ip, Counter
ADDS a4, ip, #Counter_Read ; C set means is now unlocked
BICCS a4, a4, #Counter_ThreadWaiting ; should now =0 if so
STR a4, Counter
STRCS a3, Pollword
MetaUnlock a2, a3 ; preserves flags
Return ,, CC
[ variant = "smp"
BarrierSyncCS ; preserves flags
CPUEventSendCS
BarrierSync ; preserves flags
CPUEventSend
]
Return , LinkNotStacked
TST a2, #I32_bit
Return ,, NE
TST ip, #Counter_ThreadWaiting
MOVNE a2, pc ; point r1 at a non-zero value
SWINE XRT_Yield
Return
spinrw_write_to_read_$variant ROUT
MOV a3, #Mutex_Unlocked
MOV a4, #Mutex_Locked
MetaLock , a4, ip
LDR ip, SavedCPSR
MOV a4, #-Counter_Read
LDR a4, Counter
SUB a4, a4, #Counter_Write + Counter_Read ; preserve Counter_ThreadWaiting
STR a4, Counter
; N.B. pollword remains unchanged (zero), since write lock can't be claimed yet
MetaUnlock ip, a3
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment