No more waiting ! For here comes the programmatic description of the kernel’s thread manager and scheduler component ! *insert stupid jingle here*
Scheduler (class name : Scheduler, instance name : scheduler)
This kernel component manages multiple processor cores and the main clock interrupt source on the current architecture (such as the legacy x86 PIT or the newer APIC). It uses them to provide timer functionality and thread management at the CPU level. Since the thread abstraction as a notion of currently executing task goes beyond the kernel’s scope of CPU management, this component also provides a notification interface for other shared hardware resources (disk drives, GPUs…) to keep in touch about the status of currently executing threads. Conversely, the kernel scheduler also needs input from higher-level OS components, such as the user interface (to know what is in the background and what is in the foreground) and the power management module (to shut down background tasks when power is a critical resource) in order to do its task properly, so an interface for this is also provided.
The scheduler manages a variable number of threads, so memory allocation must be initialized for it to work. It manages a part of the process abstraction (threads), so the process manager must be already started for it to work. To communicate with the hardware it manages, the scheduler may also need the help of bidirectional IO mechanisms in an architecture-dependent fashion : those must be initialized.
- bool init_rpc(RPCManager&) : To be run once the RPC subsystem is ready. Sets up all RPC-based functionality of the scheduler, which essentially concern communication with user-mode processes. Returns true if successful, false otherwise.
- bool init_interrupt(InterruptManager&) : Same thing with interrupts. They are needed for multiple processor management and preemptive multitasking operation.
- PID add_process(PID, ProcessProperties) : Part of the standard insulator interface, called by the process manager when a process is created. The first parameter is the process’ PID, the second is a set of properties that define a process from the scheduler’s point of view, such as the maximal amount of threads that it may run simultaneously, the priority layer which threads from the process start in, informations about thread stack size… Returns the process’ PID if successful, PID_INVALID otherwise.
- void remove_process(PID) : Called by the process manager when a process is destroyed, allows the scheduler to get rid of its internal entries about that PID (including all active threads).
- PID update_process(PID, PID) : Called during live updating of a process, makes the old and new process swap PIDs and transfers resources which are not mission-critical from the old process to the new process. First parameter is the PID of the old process, second parameter is the PID of the new process. Returns the old process’ new PID if successful, PID_INVALID otherwise.
- TID add_thread(PID, PID, ThreadDescriptor) : Creates a thread, given its description (requested priority layer, etc…) and the specification of its two parents. One is the scheduling parent, which determines how the thread will be scheduled, and isolation parent on the other side, which determines in the boundaries of which process the thread will execute. Allowing these two parents to be different processes is necessary in order to be able to prioritize tasks which originate from a given user process even when they spread across process boundaries. Of course, a thread is killed if either of its parents dies.
- void remove_thread(PID, TID) : Destroys a thread, given one of its parents.
- void save_cpu_state(PID, TID) : Called during a context switch after a small assembly wrapper has given enough control back to the kernel for code to execute (typically by pushing the value of all the general-purpose registers used by a thread on the kernel stack). Saves the state of a previously running thread in its dedicated storage area. This function will probably be private to the Scheduler class.
- void load_cpu_state(PID, TID) : Resumes the execution of a saved thread. Same as before.
- bool switch_process_priority_layer(PID, PLID) : Moves a process between priority layers, as an example because it has fallen from the foreground to the background of the user interface, or to intermittently freeze a background task for power saving purposes. Parameters are the process’ PID and a numerical identifier designating the target priority layer. Threads for which this PID is scheduling parent will see their own priority layer be adjusted if their current priority layer is higher than the process’ target priority layer. Returns true if the operation is successful, false otherwise.
- bool switch_thread_preferred_priority_layer(PID, TID, PLID) : Same thing, but with a thread. Changes the thread’s preferred priority layer, that is, the priority layer in which it stays unless its scheduling parent is dropped to a lower-level priority layer in which case the thread is forced to follow until the process sees its priority go up again.
- CID monitor_priority_layer_changes(PID, SID) : Asks the scheduler to make a notification RPC call each time a process’ priority changes. Is typically used to synchronize the multiple schedulers of a system (GPU, disk…), as explained earlier. The RPC call could typically take one parameter, the PID of the process whose priority has changed. Returns the RPC connection’s CID if successful, CID_INVALID otherwise.
- CID setup_timer(PID, SID) : Prepares an RPC call for timer purposes. All temporizations work by firing an RPC call after a certain time. Returns the RPC connection’s CID if successful, CID_INVALID otherwise.
- void remove_timer(PID, CID) : Stops and removes a previously set up timer RPC call.
- void start_oneshot_timer(Time, PID, CID) : Fires the specified RPC call after the specified time has elapsed.
- void start_periodic_timer(Time, PID, CID) : Fires the specified RPC call periodically, using the specified time as a period.
- void stop_all_timers(PID, CID) : Stop all timers associated to a specific connection ID.
Questions and answers
- I see no way to create and remove priority layers or adjust their properties in the interface, is it fixed ? In the current design yes, in sense that the priority layer stack is defined at kernel boot and requires a kernel restart to be modified. I am not against allowing it to be adjusted at run time, but whoever suggests it will have to provide a valid use case for this operation (and risk a proliferation of “custom” priority layers that bring little as compared to the system ones). Also, an important thing to note is that the PLIDs will not be defined as a regular linear scale (0 is halted, 1 is background, 2 is foreground, 3 is RT), but rather with space between them so that new priority layers may be added in the future without losing the attractive property that the PLID of a low-priority layer is lower than that of a high-priority layer. Example : 10 for halted tasks, 20 for background tasks, 30 for foreground tasks, 60 for RT tasks, or even something more widespread.
This was a tough one to write, and I’m two weeks late. Still, I think that defining the interface to this component (or, at least, its general appearance) is an important step forward for the evolution of this kernel. With a scheduler, this OS gets a sense of time and the ability to run tasks simultaneously or quasi-simultaneously, which is very important in a modern operating system. What’s still left to define is the interrupt manager (which made a quick appearance around here) and the IO port manager. After that, I’ll have to decide whether I redesign the memory manager’s interface to make it more consistent with the recent developments. Then it’ll be a big run of implementation to get all of this running, and after that it will be time for the kernel-user mode interface, the test of the first user-mode code, and after that many more fun things including the celebration of a complete kernel.