Hi,
glauxosdever wrote:I decided to implement a scheduler, so I first need a mechanism to create and switch tasks. But I am wondering what is the best design for this.
Creating a process is relatively expensive (e.g. likely the single most expensive thing a micro-kernel does) because it involves many things - doing permission checks, creating a virtual address space, creating some sort of "process data structure", creating an initial thread, dealing with/starting some sort of executable loader, etc.
When a high priority thread creates a process (and the new process has a lower priority initial thread), you want to postpone as much of the work involved as possible so that it costs the least of the high priority thread's time. When a low priority thread creates a process (and the new process has a higher priority initial thread), you want to do as much of the work involved as soon as possible so that the new higher priority process is created faster.
Fortunately; this can be relatively simple to achieve: do the least work possible to create the new process' initial thread, then let the scheduler figure out when the new thread gets CPU time when (based on thread priorities, etc), then do all the remaining work after the scheduler decides to give the new process' initial thread some CPU time. Note that this has other benefits - for example, it's easier for a newly created thread to access its own virtual address space (and harder for a thread that belongs to one process to modify things in a completely different process).
Creating a new thread (for an existing process) should follow the same logic - do the least possible, then let scheduler figure out when to switch to the newly created "minimal thread" (based on thread priorities, etc), then finish creating the new thread after scheduler has given it CPU time.
With this in mind; what is the least work you can do to create a new thread before that new thread is first given CPU time (and how much of the work involved can be postponed until after the new thread is given CPU time)?
When the scheduler switches to a thread it needs to load (at least some of) that thread's state - e.g. ESP/kernel stack, and whatever is popped off a thread's kernel stack by the thread switch code (e.g. EIP, etc). This information must exist before switching to the new thread, and therefore whoever created the new thread must create this information in advance. Everything else (e.g. the thread's FPU/MMX/SSE/AVX state, its debugging and performance monitoring state, its user-space state and user-space stack, etc) can be done after the scheduler switches to the new thread; and (if it's an initial thread for a new process) this can include building the rest of the process (building the new process' virtual address space, preparing the executable loader, etc). You'd also have to tell the scheduler that the new "minimal thread" is ready to run (e.g. put it on some sort of scheduler's queue or list maybe).
With this in mind, what you'd want when creating a new thread is a function pointer that points to some function in the kernel that will do the "after thread is given CPU time" part of the new thread's initialisation (so you can have different functions - one for "new thread for existing process", one for "new thread for new process", etc); and a "void *" pointer to a structure containing whatever data that function might need. You'd also want to know the new thread's priority, and which process it belongs to.
This gives you something like:
Code: Select all
int createThread( int processID, int threadPriority, void *threadKernelStack, void (*kernelThreadFinaliseFunctionPointer)(void *), void * kernelThreadFinaliseFunctionData );
Where kernel functions that finish creating new threads end up like:
Code: Select all
void kernelThreadFinalise( void * kernelThreadFinaliseFunctionData );
Cheers,
Brendan