$24
In this project, you will implement a multiprocessor operating system simulator using a popular threading library for Linux called pthreads. The framework for the multiprocessor OS simulator is nearly complete, but missing one critical component: the process scheduler! Your task is to implement the process scheduler and three different scheduling algorithms.
The simulated operating system supports only one thread per process making it similar to the systems that we discussed in Chapter 6. However, the simulator itself will use a thread to represent each of the CPUs in the simulated hardware. This means that the CPUs in the simulator will appear to operate concurrently.
Note: Multiple CPU cores need to be enabled for this to work correctly. The CS 2200 Docker container should already be configured to run with 4 cores. Please see a TA if you are running into any problems.
We have provided you with source files that constitute the framework for your simulator. You will only need to modify answers.txt and student.c. However, it is in your best interest to read and understand the other files, and it will make it much easier to implement the functions instudent.c.
We have provided you the following files:
Reminder: The only files that you need to edit arestudent.c and answers.txt. If you edit any other files, your code may fail the autograder!
1.1 Scheduling Algorithms
For your simulator, you will implement the following three CPU scheduling algorithms:
Project 4 CS 2200 - Systems and Networks Spring 2024
moved to the tail of the ready queue. It is highly recommended to read the textbook section on Round Robin before starting.
1.2 Process States
In our OS simulation, there are five possible states for a process. These states are listed in the process state t enum in os-sim.h:
There is a field named state in the PCB, which must be updated with the current state of the process. The simulator will use this field to collect statistics.
Figure 1: Process States
1.3 The Ready Queue
On most systems, there are a large number of processes that need to share the resources of a small number of CPUs. When there are more processes ready to execute than CPUs, processes must wait in the READY state until a CPU becomes available. To keep track of the processes waiting to execute, we keep a ready queue of the processes in the READY state.
Since the ready queue is accessed by multiple processors, which may add and remove processes from the ready queue, the ready queue must be protected by some form of synchronization. For this project, you will use a mutex lock that we have provided called ready mutex.
1.4 Scheduling Processes
Project 4 CS 2200 - Systems and Networks Spring 2024
Note that in a multiprocessor environment, we cannot mandate that the currently running process be at the head of the ready queue. There is an array (one entry for each CPU) that will hold the pointer to the PCB currently running on that CPU.
There is a special process, the “idle” process, which is scheduled whenever there are no processes in the READY state. This process simply waits for something new to be added to the ready queue and then calls schedule().
1.5 CPU Scheduler Invocation
There are five events which will cause the simulator to invoke schedule():
1.6 The Simulator
We will use pthreads to simulate an operating system on a multiprocessor computer. We will use one thread per CPU and one thread as a ‘supervisor’ for our simulation. The supervisor thread will spawn new processes (as if a user started a process). The CPU threads will simulate the currently-running processes on each CPU, and the supervisor thread will print output.
Since the code you write will be called from multiple threads, the CPU scheduler you write must be thread-safe! This means that all data structures you use, including your ready queue, must be protected using mutexes.
The number of CPUs is specified as a command-line parameter to the simulator. For this project, you will be performing experiments with 1, 2, and 4 CPU simulations.
Also, for demonstration purposes, the simulator executes much slower than a real system would. In the real world, a CPU burst might range from one to a few hundred milliseconds, whereas in this simulator, they range from 0.2 to 2.0 seconds.
Project 4 CS 2200 - Systems and Networks Spring 2024
Figure 2: Simulator Function Calls
The above diagram should give you a good overview of how the system works in terms of the functions being called and PCBs moving around.
Below is a second diagram that shows the entire system overview and the code that you need to write is inside of the green cloud at the bottom. All of the items outside of the green cloud are part of the simulator and will not need to be modified by you.
Project 4 CS 2200 - Systems and Networks Spring 2024
Figure 3: System overview
Project 4 CS 2200 - Systems and Networks Spring 2024
Compile and run the simulator with ./os-sim 2. After a few seconds, hit Control-C to exit. You will see the output below:
Figure 4: Sample Output
The simulator generates a Gantt Chart, showing the current state of the OS at every 100ms interval. The leftmost column shows the current time, in seconds. The next three columns show the number of Running, Ready, and Waiting processes, respectively. The next two columns show the process currently running on each CPU. The rightmost column shows the processes which are currently in the I/O queue, with the head of the queue on the left and the tail of the queue on the right.
As you can see, nothing is executing. This is because we have no CPU scheduler to select processes to execute! Once you complete Problem 1 and implement a basic FCFS scheduler, you will see the processes executing on the CPUs.
Project 4 CS 2200 - Systems and Networks Spring 2024
We have provided simple implementations of queue t, enqueue(), dequeue(), and is empty() in student.c. The struct you have to implement will serve as your ready queue, and you should be using these helper func-tions to add and remove processes from the ready queue in the problems to follow.
2.1 Provided Queue
Project 4 CS 2200 - Systems and Networks Spring 2024
Implement the CPU scheduler using the FCFS scheduling algorithm. You may do this however you like, however, we suggest the following:
Checkout the hints in section 3.3, and note that preempt() is not necessary for this stage of the project.
idle() must wait on a condition variable that is signalled whenever a process is added to the ready queue.
schedule() should extract the next process in the ready queue, then call context switch() to select the process to execute. If there are no runnable processes, schedule() should call context switch() with a NULL pointer as the PCB to execute the idle process.
3.1 Hints
Project 4 CS 2200 - Systems and Networks Spring 2024
Add Round-Robin scheduling functionality to your code. Alter the provided enqueue() and dequeue() to support round robin. You should modify main() to add a command line option, -r, which selects the Round-Robin scheduling algorithm, and accepts a parameter, the length of the timeslice. For this project, timeslices are measured in tenths of seconds. E.g.:
./os-sim <# CPUs> -r 5
should run a Round-Robin scheduler with timeslices of 500 ms. While:
./os-sim <# of CPUs>
should continue to run a FCFS scheduler. Note: you can use getopt(), which we used earlier in the semester or just parse the command line arguments passed into main using if statements.
Implement preempt().
preempt() should place the currently running process back in the ready queue, and call schedule() to select a new runnable process.
To specify a timeslice when scheduling a process, use the timeslice parameter of context switch(). The simulator will simulate a timer interrupt to preempt the process and call your preempt() handler if the process executes on the CPU for the length of the timeslice without terminating or yielding for I/O.
Add Priority with Aging scheduling to your code. Alter the provided enqueue() and/or dequeue() to support priority with aging. Modify main() to accept the -p parameter to select the Priority Scheduling with Aging algorithm. The command line argument will follow this format. ./os-sim <num CPUs> -p <age weight>. Take a look at your homework 4 if you are struggling with this.
Implement the function priority with age(). Each process has a base priority, however, we need to factor in its age to give us the processes’ functional priority. To do this, we must understand a few variables.
To calculate our functional priority use the equation
functional priority = base priority + (current time − enqueue time) ∗ age weight
We will calculate functional priority on every process in the ready queue and schedule the process with the highest priority. This means your ready queue does not have to stay in priority order, as you will need to iterate through and find the process with the highest functional priority, which will take O(n).
It’s important that you use the functional priority to find the process to schedule next, but never actually override the priority field in the PCB. Doing so would cause exponential priority growth, which is not what we want.
When a process is awakened, and all CPU cores are currently occupied by running processes, we preempt the lowest priority process if its priority is lower than that of the newly awakened process. Subsequently, we add this preempted process to the ready queue.
NOTE: Contrary to modern operating systems, higher number means higher priority..
Project 4 CS 2200 - Systems and Networks Spring 2024
Figure 5: Example Ready Queue
The above ready queue is an example of our priority with aging algorithm. Our example simulator is at current time 15, has an age weight of .2, and four processes in its ready queue. Notice the following:
Please write your answers to the following questions in answers.txt.
6.1 FIFO Scheduler
Run your OS simulation with 1, 2, and 4 CPUs. Compare the total execution time of each. Is there a linear relationship between the number of CPUs and total execution time? Why or why not? Keep in mind that the execution time refers to the simulated execution time.
6.2 Round-Robin Scheduler
Run your Round-Robin scheduler with timeslices of 800ms, 600ms, 400ms, and 200ms. Use only one CPU for your tests. Compare the statistics at the end of the simulation. Is there a relationship between the total waiting time and timeslice length? If so, what is it? In contrast, in a real OS, the shortest timeslice possible is usually not the best choice. Why not?
6.3 Preemptive Priority Scheduler
Priority schedulers can sometimes lead to starvation among processes with lower priority. What is a way that operating systems can mitigate starvation in a priority scheduler?
6.4 Priority Inversion
Project 4 CS 2200 - Systems and Networks Spring 2024
to come in and starve the window manager process. The starvation of the window manager will also mean the starvation of P1 (the process with high priority), since P1 is waiting for the window manager to finish running.
If we want to keep our non-preemptive priority scheduler, what edits can we make to our scheduler to ensure that the P1 can finish its execution before any of the medium priority processes finish their execution? Explain in detail the changes you would make.
You will be submitting files to Gradescope, where they will be tested in a VM setup that runs through Gradescope. The specifications of this VM are that it runs Ubuntu 20.04 LTS (64-bit) and gcc 9.3.0, and so we expect that your files can run in such a setup. This means thatwhen you are running your project locally, you will want to ensure you are using a VM/some setup that runs Ubuntu 20.04 LTS (64-bit) and gcc 9.3.0; this way, you can ensure that what occurs locally is what will occur when you submit to Gradescope.
IMPORTANT: Since we are dealing with different threads of execution, the result of each run in the simulation will be different. As a result, our gradescope autograder will accept a range of results for your simulation. In past semesters, we found that the range will start to increase the more computations you do in your enqueue/dequeue methods. If you find that you aren’t passing the autograder but you believe that your code is right, it is likely that you need to trim down your enqueue/dequeue methods. We are planning to start our autograder with a tighter acceptable range, and if required, we will increase it.
NOTE: You need to upload student.c, and answers.txt to Gradescope, and an autograder will run to check if your scheduler is working. The autograder might take a couple of minutes to run. Remember to upload student.c, and answers.txt for every submission as your last submission would be one we will grade.
Keep your answers detailed enough to cover the question, including support from simulator results if appro-priate. Don’t write a book; but if you’re not sure about an answer, give us more information.
Project 4 CS 2200 - Systems and Networks Spring 2024
9.1 Running
We have provided a Makefile that will run gcc for you. To compile your code with no optimizations (which you should do while developing, it will make debugging easier) and test with the FCFS algorithm and one CPU, run:
To run the other algorithms, run with the flags you implemented for round robin and priority. Remember that round robin requires you to enter a time slice.
In case you encounter difficulties with Project 4 and are uncertain about the direction to take, various resources are available to assist you.
9.2 GDB
Let us investigate how to debug deadlocks through a basic example:
Project 4 CS 2200 - Systems and Networks Spring 2024
As anticipated, the program continues to remain unresponsive when run within the gdb environment. To interrupt the program, press Ctrl + c. To analyze the various threads associated with the program, utilize the ”info threads” command within gdb, which provides detailed information regarding each active thread:
Upon examining the running threads using the ”info threads” command within gdb, we can observe that threads 2 and 3, which were created within the main() function, are currently situated within the lll lock wait function. To obtain the backtrace of these threads, we can use the ”thread apply all” command along with the ”backtrace” command, which can be abbreviated as ”t a a bt”.
Project 4 CS 2200 - Systems and Networks Spring 2024
By switching to thread 3 within gdb, we can identify the precise line of code where it has become deadlocked. Once we have identified the problematic line, we can utilize gdb’s features, such as printing values or switching stack frames, to investigate further and gain a better understanding of the issue at hand. Read the gdb thread documentation here for more information.
9.3 Valgrind (Helgrind or DRD)
What about issues when accessing a shared resource? Valgrind has some handy tools to help detect such problems. You may use your 2110 docker or just look up ”valgrind installation” online for download instruc-tions. Let’s modify the code from the previous section:
Project 4 CS 2200 - Systems and Networks Spring 2024
Upon executing Valgrind’s tool Helgrind, we can observe that it has successfully identified an issue within the program where thread2 is accessing a shared variable without acquiring a corresponding lock. Additionally, DRD (another tool within Valgrind) also provides comparable output, albeit with fewer error lines. It is essential to rectify these issues to ensure proper synchronization and avoid potential data race conditions. To compare, here is the same program run with DRD (’valgrind tool=drd <program>’):
Valgrind and DRD are also able to debug other types of synchronization errors. You can read the documen-tation about Helgrind here and DRD here.
Credit to this video from an old class.
9.4 Tips and Tricks
The following are some tips and tricks to help you with approaching and debugging this project. These are not comprehensive, but perhaps a good starting point for you. If you have further questions, please come to office hours or post to Ed Discussion: