I had forgotten about this when I wrote the stuff below. It is better in some ways and perhaps I will unify them sometime.

The kernel keeps state that is logically in pages and nodes, ‘units’ here, but actually in various data structures known only to the kernel. The index to this section mentions several manners in which the logical and physical states differ. Any physical states corresponds to some particular logical state and the kernel is always able to promptly put state back into the logical units without action by domain (‘user mode’) code or IO. This latitude simplifies some otherwise complex corner cases. The kernel acts on this state when a program running in user mode:

These are ‘traps’ endogenous to the user mode program that the domain is obeying. In addition there are exogenous interrupts due to:

Endogenous kernel entries necessitate a transaction wherein certain units must be accessed—some read accesses and some write accesses. Kernel code that is special to the transaction, acquires shared locks for units that it must read, and exclusive locks for units that it must write. Kernel semantics is arranged so that there is a modest limit on the number of such locks. Sometimes, domain code behavior causes aliasing where a transaction requires modifying the same unit under two guises. Aliasing is discovered by lock collisions and some such events are valid and others are invalid and rejected. For valid cases, special processing is required where the kernel may unprepare nodes, which it is always free to do.

Sometimes a necessary unit is not in RAM and some unit frame is commandeered and IO is initiated to bring the unit into RAM. During this IO all locks are released and the domain whose act triggered the transaction remains in the same state where the act occurred. The domain is placed on an IO stall queue. During lock acquisition no global state is modified in either units or the information derived from them. We look on the ready queue for another domain to run.

When all necessary locks are held we have finished the dry run and we then complete the transaction generally reading the units with shared locks and possibly writing the exclusively locked units. Concomitantly with this we unlock all the locks as we finish using them.

This logic is mostly unmodified for symmetric multiprocessing with shared memory. More code is then necessary as when a domain action is on a node that defines a domain currently running on another CPU. Such code has not been written. Also we must tolerate collisions arising when two CPUs are in the kernel and try to lock the same unit.