Con­text swit­cher

The core of the system

The switcher keeps track of processes and threads, allowing them to be created and destroyed. It also allows one thread to yield control to another, ensuring that the context of the first thread is swapped out and replaced by that of the second, if necessary.

An application does not talk to the switcher directly. One of its threads calls in to one of the subsystems, then that subsystem decides whether to talk to the switcher, usually because it knows that it cannot complete the call immediately, and is perhaps waiting for some hardware (e.g. a network interface or a disc drive) to respond.

Under the control of the subsystem which a thread has called in to, the thread offers to yield control through a Context​Switcher_​Yield call, which might block until the switcher grants control back to that thread. In the meantime, the switcher may have granted control to several other threads in turn.

Before yielding, the controlling subsystem must set up any interrupt routines and internal structures to determine the earliest moment when the switcher can return control. In particular, it must reserve a word [Edit: zero-initialised?] to be used as a release handle, and pass its address to Context​Switcher_​Yield, which the switcher will fill in. When the interrupt comes in, and the subsystem has determined that the thread's call into it can complete, it passes the release handle to Context​Switcher_​Resume, allowing the switcher to mark the thread ready to continue.

When the switcher detects that all threads are suspended, the kernel will be idle, so it should try to clear the transient-callback list. This will allow interrupts to call Context​Switcher_​Resume indirectly by setting up transient callbacks.

Here's some code to do this, taken from The Callback Hack for network filing systems:

usermode_donothing
        SUBS    ip, lr, #0          ; ip := lr; set C - sneaky
        TEQ     pc, pc
        TEQNEP  pc, #0              ; ->USR26, Z still clear, IF cleared
        MSREQ   CPSR_c, #2_00010000 ; ->USR32, I32/F32/T cleared
        MOV     r0, #0
        MOV     r1, #1
        SWI     XOS_Byte
        SWI     XOS_EnterOS
      [ {CONFIG}=26
        MOVS    pc, ip
      |
        MOV     pc, ip
      ] 

Interface

Only one SWI is defined for accessing the context switcher, and it is only to be used by subsystems as modules, not by applications, as a rendezvous point. All other subroutines are SVC calls, and are provided to the caller through the SWI routine.

Context-switcher routines
Type Routine
SWI operation Context​Switcher_​Register​Subsystem
SVC subroutine Context​Switcher_​Yield
SVC subroutine Context​Switcher_​Resume
SVC subroutine Context​Switcher_​Lookup​Context
SVC subroutine Context​Switcher_​Exit
SVC subroutine Context​Switcher_​Fork
SVC subroutine Context​Switcher_​Spawn
SVC subroutine Context​Switcher_​Finish

SWI​ ​Context​Switcher_​Register​Subsystem ​ ​​ ​

Hook a new subsystem into the context-switching architecture

On entry

  • R0=the subsystem's subcontext pointer
  • R1=the subsystem's instantiation pointer (R12 entry value for all SVC subroutines)
  • R2=the subsystem's subroutine table
  • R3=the subsystem's subroutine table size in words

On exit

  • R0=the subsystem's new subsystem index
  • R1=the switcher's instantiation pointer (R12 entry value for all SVC subroutines)
  • R2=the switcher's subroutine table
  • R3=the switcher's subroutine table size in words

Description

A subsystem is registering its interest in context switching. This operation allows the switcher and the new subsystem to exchange subroutine entry points, and the subsystem will be allocated a free subsystem index to be used in future reference with the switcher.

On entry, the first R3 words of the block pointed to by R2 should be the entry addresses of the corresponding first R3 subroutines of the following sequence:

  1. Subsystem_​Fork
  2. Subsystem_​Exit
  3. Subsystem_​Terminate
  4. Subsystem_​Switch​In
  5. Subsystem_​Switch​Out

Unused entries must contain 0, or be beyond the specified size of the table.

In return, R2 will point to a table of R3 switcher subroutines, in order:

  1. Context​Switcher_​Yield
  2. Context​Switcher_​Resume
  3. Context​Switcher_​Lookup​Context
  4. Context​Switcher_​Fork
  5. Context​Switcher_​Exit
  6. Context​Switcher_​Spawn
  7. Context​Switcher_​Finish

This table is persistent, and shared by other systems. It must not be altered.

SVC subroutine​ ​Context​Switcher_​Yield​ ​​ ​

Indicate that the current thread is waiting for I/O

On entry

  • R0=subsystem index of caller
  • R1=address of word to hold release handle
  • R2=subsystem reference
  • R3=flags

Description

The calling subsystem is yielding control on behalf of the process that called it. The call may therefore not return immediately.

When it does return, the release handle can be considered invalid.

The subsystem reference is returned by Subsystem_​Terminate, so that subsystem can locate its state pertaining to a blocked thread that has been aborted.

[Edit:

Another possibility is that the switcher will simply allow the terminated thread to return from this call with some indication that it has been terminated. The subsystem would detect this, and release its resources, but not return to the application. Instead, it would make a non-returning call back to the switcher, which could then delete the thread's stack and select another thread.

]

SVC subroutine​ ​Context​Switcher_​Resume​ ​​ ​

Place a waiting thread in a scheduling queue

On entry

  • R0=subsystem index of caller
  • R1=address of word holding release handle
  • R2=priority level

Description

The calling subsystem wants to resume one of its callers' threads. The thread is placed on a queue identified by the priority level. This call may be used to alter the priority of a pending thread, or set it back to the blocked state (priority -1).

Like other SVC subroutines, this routine must not be called in an interrupt mode. The interrupt routine should arrange for an SVC call to be made to a subsystem routine as soon as possible after the interrupt is finished, e.g. by OS_AddCallBack. This ensures that the Context​Switcher_​Yield call will have had time to write its release handle into the supplied word — the callback will not occur between the word being allocated and the handle being written.

SVC subroutine​ ​Context​Switcher_​Lookup​Context ​ ​​ ​

Identify the subcontext for the current thread

On entry

  • R0=subsystem index of caller

On exit

  • R1=subsystem subcontext pointer

Description

The calling subsystem wants to locate its subcontext within the current context.

SVC subroutine​ ​Context​Switcher_​Fork​ ​​ ​

Duplicate the current process

On entry

  • R0=subsystem index of caller

On exit

  • R1=process identifier

Description

The calling subsystem wants to cause the calling context to fork.

The call will return twice, once to the original thread with the process identifier of the new thread, and once to the new thread with a null identifier.

SVC subroutine​ ​Context​Switcher_​Exit​ ​​ ​

Terminate the current process

On entry

  • R0=subsystem index of caller
  • R1=exit code

On exit

  • This call does not return.

Description

The calling subsystem wants to cause the calling context to exit.

SVC subroutine​ ​Context​Switcher_​Spawn​ ​​ ​

Start new thread in current process

On entry

  • R0=subsystem index of caller
  • R1=pointer to register block
  • R2=priority level
  • R3=subsystem termination reference

On exit

  • R1=thread identifier

Description

The calling subsystem wants to start a new thread in the current process. A block of initial register values must be provided, including the entry address in R15. The code will be entered in USR mode, and should return according to the managing subsystem's specification, e.g. with Thread​Manager_​Finish, which will then call Context​Switcher_​Finish on the thread's behalf.

SVC subroutine​ ​Context​Switcher_​Finish​ ​​ ​

Terminate the current thread

On entry

  • R0=subsystem index of caller
  • R1=thread identifier, or 0 for calling thread

On exit

  • This call does not return if the calling thread is terminating itself.

Description

The current thread is terminated. If this is the last thread in the process, Context​Switcher_​Exit is called with an exit code of zero.

[Edit:

Presumably, if a thread is terminating itself, it can call the appropriate SWI after releasing its stack.

]

[Edit:

If a thread tries to terminate itself using a non-zero identifier, the attempt could be ignored. That would allow the exec code in the thread manager to easily terminate all threads except the one running, so that it can be used to initiate the new process image.

]

Question: What about terminating a particular thread?

Answer:

Okay, yes. This call should take an argument giving the process-scoped thread identifier. [Edit: Now added.]

We don't want to do something similar for processes, though, do we? Process termination comes from within the process, while thread termination can come from anywhere in the controlling process, i.e. another thread.

But we should still have a way to force a process to abort, like kill -9.