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:
It performs some form of potentially time-consuming I/O, during which it can, and wishes to, yield CPU control back to other parts of the system.
It maintains per-process state, possibly needing to know when control enters or leaves a process.
A module becomes a subsystem by making a ContextSwitcher_RegisterSubsystem call.
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:
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.
Call ContextSwitcher_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.
When the switcher call does return, transmit the information payload of the call between the process and the kernel.
Delete the structure.
Return to the calling process.
When the kernel reports that the operation is complete (or immediately completable), the subsystem should do the following:
Use any context that the subsystem established with the kernel to locate the blocked structure.
Call ContextSwitcher_Resume passing the switcher's reference, and specifying an agreed priority level.
Return from the interrupt.
Question: Is there a race condition here? The switcher must provide its reference before the kernel gets back to the subsystem.
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 ContextSwitcher_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:
Use the reference it originally provided to locate the structure pertaining to this blocked thread.
Delete the structure.
Return to the switcher.
This is the interface that a subsystem must provide to the context switcher when registering with it via ContextSwitcher_RegisterSubsystem. This call exchanges this interface with the SVC interface of the switcher, so that they can then invoke each other without inefficient SWI calls.
| Type | Routine |
|---|---|
| SVC subroutine | Subsystem_Fork |
| SVC subroutine | Subsystem_SwitchOut |
| SVC subroutine | Subsystem_SwitchIn |
| SVC subroutine | Subsystem_Exit |
| SVC subroutine | Subsystem_Terminate |
Instruct subsystem to duplicate subcontext
A context is being forked. The subsystem must duplicate the specified subcontext, and return a handle to the new one.
Notify subsystem of process being switched out
A context is being switched out.
Question: Are these two operations really necessary? Why not just one?
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.
Notify subsystem of process being switched in
A context is being switched in.
Instruct subsystem to destroy a subcontext
A context is exiting. The subsystem must release the specified subcontext.
Inform subsystem of terminating thread
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 ContextSwitcher_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
ContextSwitcher_Yield
with the SW_PERM_NOTIFY flag to inform the switcher
that it permanently needs to be notified of the thread
terminating.