This project has two parts.
CPU Scheduling. As you might have already noticed, the default CPU scheduling algorithm in GeekOS is priority-based preemptive round-robin. You will add your own CPU scheduling algrorithm. You will add a system call to choose between them. To see the difference between them, you will add a system call to get the system's current time.
Semaphores. You will implement semaphores, a simple synchronization construct for threads. You will provide system calls to create, use and terminate semaphores.
The choice of scheduler should be implemented within the function Get_Next_Runnable() (see kthread.c). The choice should not show up in the arguments of this function. A call to Get_Next_Runnable() should return without indicating the scheduling algorithm being used; the caller should learn only that some thread has been selected.
You are free to create a new scheduling algorithm that is optimized for anything you want. However, there are two constraints. First, you need to explain its goals, i.e., what it is intended to optimize. When you turn in your code, put your explanation (justified with measured elasped times) in a REAME.scheduler file in the top level of the class project directory. Second, make sure that your algorithm is no worse than the default algorithm in the time it takes to complete user programs. Otherwise the tester may timeout and give up on your submission.
You will provide the following two system calls so that you can run user programs to compare your scheduler against the default.
int Set_Scheduling_Policy(int policy):
|
unsigned long Get_Time_Of_Day():
|
You can obtain the elapsed time of a user program's execution by invoking the system call at the beginning and at the end of the program (in the user code). Use such measurements to compare your scheduler versus the default scheduler; include them in your justification in README.scheduler.
Note: This elapsed time is known as wall clock time. It includes the time when the system is not doing anything on behalf of the program's process, e.g., when the process is switched out but runnable, when the process is waiting on an IO device that is serving another process. The time during which the process is executing on the CPU is referred to as process time (or sometimes virtual time); this project is not concerned with obtaining that.
You will augment GeekOS with semaphores so that user programs can open them, use them for thread synchronization, and close them.
For specification purposes, it is convenient to view each semaphore as having the following attributes while it exists. ("For specification purposes" means that these attributes need not be explicitly present in your implementation.)
The name is supplied by user programs. The SID is supplied by the kernel; it points to a particular semaphore data structure (which you will implement) in the kernel. The SIDs come from a fixed pool of 20 SIDs, initially all available.
You will add the following four system calls to manipulate semaphores. Each is described in more detail below.
int Open_Semaphore(const char *name, int ival):
|
int Close_Semaphore(int sem):
|
int P(int sem):
|
int V(int sem):
|
Each semaphore data structure should contain a thread queue for its blocked threads. To block a thread (in a "P" operation), you can use the Wait function in the kernel. To wake up a thread (in a "V" operation), you can use the Wake_Up_One function. (If you want to wake up all the threads in a thread queue, you can use the Wake_Up function. Look at kthread.h and kthread.c to see how these functions are declared and used.
You have some options regarding the implementation of V(sem) when processes are blocked on semaphore sem. One option is to select one or more blocked processes and restore each to redo the P(sem) in which it was blocked. Another option is to select one (and only one) blocked process and restore it to just after the P(sem) in which it was blocked. (Here, the V operation is also simultaneously executing the blocked thread's P operation. Think about how the semaphore's value should be updated.)
If a thread exits while it has one or more semaphores open, the kernel should close this thread from every such semaphore. (So it makes sense to have a semaphore-closing function in the kernel which can be invoked by both the Sys_Close_Semaphore() function and some function involved in terminating user threads.)
In order not to clobber syscall.c with too much functionality, put your semaphore implementation in sem.h and sem.c. Semaphore operations must be executed atomically, i.e., implemented within a critical section.
Since you need to have multiple processes running concurrently to test the functionality you will implement, your shell should be able to launch processes in the background.
In this and other projects, you will rely heavily upon a list data structure. An implementation has been provided to you in list.h file. Familiarize yourself with its syntax and functionality. It can be a little tricky to understand the syntax because functions are written using #define. Naturally you are always free to extend, modify, or write your own implementation that would better suit your needs.
Identifier | Kernel Function | User Function |
SYS_SETSCHEDULINGPOLICY | int Sys_SetSchedulingPolicy (struct Interrupt_State* state) | int Set_Scheduling_Policy (int policy) |
SYS_GETTIMEOFDAY | unsigned long Sys_GetTimeOfDay (struct Interrupt_State* state) | unsigned long Get_Time_Of_Day() |
SYS_OPEN_SEMAPHORE | int Sys_Open_Semaphore (struct Interrupt_State* state) | int Open_Semaphore (const char *name, int ival) |
SYS_P | int Sys_P (struct Interrupt_State* state) | int P (int sem) |
SYS_V | int Sys_V (struct Interrupt_State* state) | int V (int sem) |
SYS_CLOSE_SEMAPHORE | int Sys_Close_Semaphore (struct Interrupt_State* state) | int Close_Semaphore (int sem) |
The files we provided can be used to test your semaphores or scheduling algorithm:
% /c/workload.exe [algorithm]
where algorithm can take any of the values rr or mys
.
% /c/sem-ping.exe &
% /c/sem-pong.exe