This post will detail the LμKOS Process Manager’s (PM) subsystem and what makes it tick. The format of this post will roughly follow the previous post for consistency’s sake. The terms here will refer to the terms in the previous post, so hop back there for cross reference.

Definitions

TermAcronymDefinition
ThreadA single execution context in one Address Space, and contains associated properties (discussed below)
ProcessA collection of Threads which all reside in the same Address Space, and contains associated properties (discussed below)
AffinityIn this context, Affinity is referring to the property of a Thread which causes it to be restricted to run on a single, specific core. Often called CPU Affinity
Process ManagerPMThe LμKOS Process Manager handles the creation/deletion/scheduling of Processes and Threads in the system
Table 1. Definitions

Why do we need a Process Manager?

The Process Manager (referred to by its acronym, PM, from here on out) provides a useful abstraction for us to manage multiple threads of execution across multiple cores, some of which may be running in the same Address Space. This abstraction also allows a common interface to do most of the OS-level Thread and Process initialization and only call into architecture specific code when we need to do something truly architecture specific, like packing initial arguments onto the call stack.

The LμKOS Process Manager

The LμKOS Process Manager is comprised of a few data structures, and a handful of functions as shown below. In the following sections I will detail some of the key functions the PM provides which are not self-explanatory.

One key thing to note about LμKOS is that there is no such concept as a kernel thread. Unlike most OSes, the core kernel only does things in its own context at startup, and following when requested by an application. Due to this, there are no kernel threads to distinguish from user threads — every thread is a user thread.

Another key aspect of the way LμKOS schedules is that uses a variation on two-level scheduling. Processes to be run are selected based on a priority basis, and each process can then in turn select which of its threads should be run in that process’s time slice.

struct process;
struct ipc_pipe;
typedef struct
{

	char *name;
	size_t stack_size;
	size_t priority;
	void *stack_base;
	void *stack_pointer;
	void *entry;
	void *arg;
	size_t flags;
	size_t affinity;
	struct process *parent;
	
	struct ipc_pipe *blocked_by;
	size_t sleep_ticks;
	uint32_t lock;
	
	
}thread_t;


typedef struct thread_list_node
{
	struct thread_list_node*next;
	thread_t *thread;

}thread_list_node_t;

typedef enum
{
	PM_SCHEDULER_ROUND_ROBIN,
	PM_SCHEDULER_PRIORITY
}process_scheduler_t;


typedef struct process
{
	char *name;
	address_space_t *as;
	process_scheduler_t scheduler_type;
	size_t priority;
	thread_list_node_t* threads;
	uint32_t lock;

}process_t;

/**
 * initialize the Process Manager
 * @param maxcpus 		the maximum number of CPUs expected in this systems
 */
void pm_init(size_t maxcpus);

/**
 * create a process with the given parameters but no threads and does not ready for scheduling
 * @param name		the process name
 * @param as		the address space
 * @param scheduler	the desired thread scheduler
 * @param priority 	the process priority
 */
process_t *pm_process_create(char *name, address_space_t *as, process_scheduler_t scheduler, size_t priority);


/**
 * add a process to the scheduler
 * @param prc		the process
 */
void pm_process_schedule(process_t *prc);



/**
 * create a thread and add it to the specified process
 * note: if this process is scheduled, this thread will be immediately schedulable 
 * @param name			this thred's name
 * @param prc			the parent process
 * @param entry			the entry point
 * @param arg			the argument
 * @param stack_size	the thread stack size
 * @param priority		the thread priority (unused in some process schedulers )
 */
thread_t* pm_thread_create(char *name, process_t *prc, void *entry, void*arg, size_t stack_size, size_t priority);


/**
 * get the next schedulable thread for this cpu
 * @param cpuno		the cpu number
 * @return a pointer to the next thread
 */
thread_t *pm_thread_next_get(size_t cpuno);



/**
 * get current thread for this CPU
 * @param cpuno		the cpu noumber
 * @return a pointer to the current thread
 */
thread_t *pm_thread_current_get(size_t cpuno);

/**
 * set the core affinity of this thread
 */
void pm_thread_affinity_set(thread_t *thr, size_t aff);

The process_t and thread_t structures

These two structures are the core scheduling structures in the OS. A Process simply holds a collection of Threads, and a Thread contains all the useful execution information.

The pm_init Function

This function, unlike the Virtual Memory Manager’s init function, does quite a lot. This function creates the idle threads (which are always ready, never blocked) which will run when nothing else is ready. It also loops through a Platform Data structure which holds binary data about what applications to launch at startup. This function loads up and sets up the Threads by calling into the ELF loader library and the architecture specific code. For the idle Threads, each core on the machine gets an idle thread “assigned” to it via setting the Affinity.

The pm_process_create Function

This function creates the “container” for future threads to be contained in. At Process creation you decide by which method the process should schedule its Threads.

The pm_thread_create Function

This function takes in parameters which all relate to the Address Space in which the Thread should run. For example, the entry is the address, in the destination Address Space, of the first function to be called (could be main or anything else). This function creates a stack of the given size in the destination address space (which we can glean from the Process structure), populates it by call into architecture code, sets up all the associated meta-data, and then returns it. This Thread is immediately schedulable in the given Process.

The pm_thread_next_get Function

This is the workhorse of the PM. The scheduling interrupt code in the architecture library calls this function to find out which Thread should run next on a given core. See the diagram below.

Figure 1. Scheduling.

Architecture Specific Code

There is only one architecture specific call for this manager and it is shown below.

/**
 * architecture specific call to populate the task stack with the arguments and set the entry point
 */
void pm_arch_thread_stack_populate(process_t *prc, thread_t *thr);

The function it provides is to do the architecture specific thread stack initialization using the information in the process and thread structures passed to it.