Sub­sys­tems

A common abstraction for all I/O modules and other context-retaining modules

A module should consider itself to be a subsystem if it has either of the following requirements:

A module becomes a subsystem by making a Context​Switcher_​Register​Subsystem call.

Implementation of blocking calls

The application enters SVC mode from USR mode via an interface defined by the subsystem, e.g. a SWI call.  The subsystem decides that the call will block for an indefinite period (or may so block, but the subsystem doesn't bother to check, either through laziness, an unreasonable overhead due to checking, or simply to allow a thread to yield for the sake of general concurrency), so it does the following:

  1. Agree with the kernel to receive an interrupt or event indicating that the call can unblock, setting up a structure to be ready for this event.

  2. Call Context​Switcher_​Yield, which may not return straight away.  The subsystem passes a reference to its internal structure representing the blocking call, and part of the structure will be filled by the switcher as its reference.

  3. When the switcher call does return, transmit the information payload of the call between the process and the kernel.

  4. Delete the structure.

  5. Return to the calling process.

When the kernel reports that the operation is complete (or immediately completable), the subsystem should do the following:

  1. Use any context that the subsystem established with the kernel to locate the blocked structure.

  2. Call Context​Switcher_​Resume passing the switcher's reference, and specifying an agreed priority level.

  3. Return from the interrupt.

Question: Should the subsystem call Context​Switcher_​Resume from IRQ/FIQ mode?

Answer:

Hmm…


Question: Is there a race condition here? The switcher must provide its reference before the kernel gets back to the subsystem.

Answer:

Arse.

Ah, perhaps if the subsystem arranges for the kernel to call it only in SVC mode, then the other problem goes away (for a start), and the subsystem won't try to access the switcher reference until the switcher has put it there.

Another possibility is to have a separate switcher call to set up the reference, and return as soon as that is done.  Then the thread is yielded.


(Under special conditions, the subsystem may decide that it has prematurely decided to resume a thread, or may wish to alter its priority.  It can call Context​Switcher_​Resume as often as necessary until the yield call has returned, and can even de-queue the thread entirely.)

Alternatively, the calling thread may be terminated, either by another thread in the same process, or by the process exiting.  The subsystem will receive a Subsystem_​Terminate, and should proceed as follows:

  1. Use the reference it originally provided to locate the structure pertaining to this blocked thread.

  2. Delete the structure.

  3. Return to the switcher.


Uniform Subsystem Interface

This is the interface that a subsystem must provide to the context switcher when registering with it via Context​Switcher_​Register​Subsystem.  This call exchanges this interface with the SVC interface of the switcher, so that they can then invoke each other without inefficient SWI calls.

Subsystem routines
Type Routine
SVC subroutine Subsystem_​Fork
SVC subroutine Subsystem_​Switch​Out
SVC subroutine Subsystem_​Switch​In
SVC subroutine Subsystem_​Exit
SVC subroutine Subsystem_​Terminate

SVC subroutine​ ​Subsystem_​Fork​ ​​ ​

Instruct subsystem to duplicate subcontext

On entry

  • R0=the subsystem's subcontext pointer
  • R12=the subsystem's instantiation pointer

On exit

  • R0=the subsystem's new subcontext pointer

Description

A context is being forked.  The subsystem must duplicate the specified subcontext, and return a handle to the new one.

SVC subroutine​ ​Subsystem_​Switch​Out​ ​​ ​

Notify subsystem of process being switched out

On entry

  • R0=the subsystem's subcontext pointer
  • R12=the subsystem's instantiation pointer

Description

A context is being switched out.

Question: Are these two operations really necessary?  Why not just one?

Answer:

Hmm, not sure.  Ah, it allows us to completely switch out a process with respect to all subsystems, before completely switching the next one in; although, I expect switch-out will be mainly just set a flag for the process, or be completely ignored.


SVC subroutine​ ​Subsystem_​Switch​In​ ​​ ​

Notify subsystem of process being switched in

On entry

  • R0=the subsystem's subcontext pointer
  • R12=the subsystem's instantiation pointer

Description

A context is being switched in.

SVC subroutine​ ​Subsystem_​Exit​ ​​ ​

Instruct subsystem to destroy a subcontext

On entry

  • R0=the subsystem's subcontext pointer
  • R12=the subsystem's instantiation pointer

Description

A context is exiting.  The subsystem must release the specified subcontext.

SVC subroutine​ ​Subsystem_​Terminate​ ​​ ​

Inform subsystem of terminating thread

On entry

  • R0=the subsystem's subcontext pointer
  • R1=the subsystem's termination reference for the thread
  • R12=the subsystem's instantiation pointer

Description

The switcher is informing a subsystem, which currently has a blocked thread, that the thread has been terminated.  The termination reference is the R2 value provided when the thread was blocked by Context​Switcher_​Yield.

This call is intended to allow a subsystem to de-allocate any resources it holds about a thread with which it can no longer interact.  If the thread is destroyed as part of an exiting process, the Subsystem_​Exit call will appear later.

The call has a second purpose, when R1 may be null.  In this case, the subsystem is keeping per-thread information, and needs to know when the thread is terminated in order to release that information.  This is primarily to support the event multiplexer subsystem, which will call Context​Switcher_​Yield with the SW_PERM_NOTIFY flag to inform the switcher that it permanently needs to be notified of the thread terminating.