PilotOS  aims to become a simple operating system for the Raspberry Pi. I use it mainly to learn the ins and outs of operating system development, starting with simple functionality all the way to operating system architecture. In no way is PilotOS meant to be a usable operating system of any description, it’s just for playing around.
Yesterday I added processes and a simple scheduler and dispatcher to the mix. A dispatcher is the functionality for switching between processes, i.e. it has to jump between the processes and save and restore the context of the process. Scheduling is more like a policy on how the next process is chosen.
There is cooperative and preemptive scheduling. With cooperating scheduling the process will yield the control over the CPU itself, either by calling a specific function or sometimes by calling any system call. Some operating systems did implement it in a way, that every system call is used for switching between processes. Preemptive scheduling uses a timer interrupt for the preemption of processes. I.e. at start of the system the timer will be programmed to deliver and interrupt after a certain amount of time. The interrupt will be caught by the CPU and handled by an interrupt service routine (ISR). The ISR will start the dispatching. This also means, that a process can be preempted at any point in time (see race conditions).
I implemented at first a cooperative switching. Well to be more precise: I implemented the framework for cooperating switching and didn’t even bother testing it yet. The code is not used yet, so there is no dispatching and there are no processes yet.
The scheduler uses a comparison-function for choosing the next process. This function returns for two process-control-block (PCB) one, that it finds useful for any criterion it is set out to compare for. The schedule-routine is only there for applying the comparison-function to every PCB in the system. A PCB contains information about a process, in this example an ID, deadline, execution-time, last-use-information and the stack-pointer. Not all of these attributes are used usefully (yet).
The yielding of the CPU works with the function yield, which is jumping directly into the function dispatch. The separation of these two function is currently not useful, but maybe later on I’ll find it useful that I split these two functions up. The dispatcher saves the Link-register (lr) onto the stack. The Link-register in ARM saves the return-address after a function call (linked branch) has occured (assembler instruction bl). In the called function there is the link register, which can be used with the instruction mov pc, lr to return to the caller-code. (PC – Program Counter, Instruction Pointer).
Then all registers from r1 to r12 will be saved onto the stack. r13, r14 and r15 will not be saved for they have special functionality in the ARM-core. r13 is the stack-pointer. Saving this onto the stack would be quite useless. r14 is used as the link register. One could argue, that it might be helping to save this register with all the others, but then I would have to know, whether push and pop can save all the registers in one instruction, or maybe the Assembler will split the instruction into several ones up, which only save one register at a time. Saving r15, the program counter, onto the stack is completely bogus. The program counter points into the dispatch-function at the moment, restoring that program-counter would lead to us not being able to leave the dispatch function ever.
After saving all these registers I can use them for calculations without risking loosing all of the values a process may have saved in them. But we still need to store the stack-pointer of the yielding process in our PCB, then we can load the addresses of the comparison-functions in r0 and r1 and call the schedule-function. Its result is stored in r0. Now I need to change the stack-pointer of the CPU, recovering the old one of the next process. Every saved register is pop’d from the stack. At last the link-register is pop’d from the stack, but it will be saved into the pc-register. So the next instruction executed will be the next one of the chosen process.