The kernel code was designed from the beginning with multiple CPUs in mind. It was written so as to minimize the amount of code that would be discarded in case of MP. There is yet a great deal more code that is needed for MP however.
I describe here the high level strategy that we adopted for MP, as it solved some related problems that were already crucial for single processor versions.
These ideas are much like schemes for transactions where an attempt is made that may turn out to be infeasible and must then leave no side effects. This is commonly called commit-abort logic. We were vaguely aware of this discipline and I am sure that it was not original with us. Still some of the details of our implementation may be of interest.
Perhaps ideally a CPU spends most of its time executing user code in user mode. This code will occasionally invoke the kernel to change some part of the world that is not represented in the memory space of any domain. Most such kernel invocations are feasible and the code must handle the success case quickly, but be prepared for possible obstacles. There are several forms of obstacles: