• Ben Avison's avatar
    Improve behaviour when used with RTSupport threads · 28fdd6df
    Ben Avison authored
    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.
    28fdd6df
mutex 2.91 KB