The purpose of this laboratory experiment is to explore and analyze the concept of context switching within computer systems. Context switching plays a crucial role in the efficient multitasking capabilities of modern operating systems, allowing them to manage multiple tasks concurrently. This lab aims to provide a comprehensive understanding of the mechanisms involved in context switching, its impact on system performance, and the techniques employed by operating systems to facilitate seamless transitions between different execution contexts.
The TA present in your lab will do a brief explanation of the various parts of this lab, but you are expected to answer all questions by yourself. Please raise your hand if you have any questions during the lab section. Questions and Parts have a number of points marked next to them to signify their weight in this lab’s final grade.
Please read through this lab and follow the instructions. After you do that, visit Gradescope and complete the questions associated with this lab by the assigned duedate.
Once you have logged in to Edlab, you can clone this repo using
git clone <https://github.com/umass-cs-377/377-exercise-context_switch.git>
Then you can use cd
to open the directory you just cloned:
cd 377-exercise-context_switch
This repo includes a Makefile that allows you to locally compile and run all the
sample code listed in this tutorial. You can compile them by running make
. Feel free to modify the source files yourself, after making changes you can run make
again to build new binaries from your modified files. You can also use make clean
to remove all the built files, this command is usually used when something went wrong during the compilation so that you can start fresh.
In this lab, your task is to implement a basic context switch in a simulated environment. We provide you with two structures: cpu
, representing CPU registers, and task_struct
, representing a process control block with details such as process ID, state, CPU registers, and maximum run time.
Tasks in the ready state are stored in run_list
for those currently running and complete_list
for those that have finished execution. The main loop iterates over all
processes in the ready state, and a context switch is performed as
needed.
Your responsibility is to implement the context_switch()
function.
#include <iostream>
#include <list>
#include <iomanip> // For std::setw
#define rd() \\
rand()%90 + 10
#define RUNNING 0
#define READY 1
#define SLEEPING 2
#define EXIT 3
using namespace std;
struct cpu
{
int eip;
int esp;
int exb;
int exc;
int exd;
int pc;
};
struct task_struct
{
int pid;
int state;
struct cpu reg;
int max_run;
};
list<task_struct *> run_list;
list<task_struct *> complete_list;
void pcb_print(list<task_struct *> l) {
cout << "PID STATE eip esp exb exc exd pc max_run" << endl;
for(auto pcb : l) {
cout << pcb->pid << setw(5) << pcb->state << setw(8) << \\
pcb->reg.eip << setw(5) << pcb->reg.esp << setw(5) << \\
pcb->reg.exb << setw(5) << pcb->reg.exc << setw(5) << \\
pcb->reg.exd << setw(4) << pcb->reg.pc << setw(4) << \\
pcb->max_run << endl;
}
}
void context_switch(struct task_struct *curr,
struct task_struct *prev,
struct cpu *cpu) {
// your code goes here
}
void run() {
struct cpu cpu;
struct task_struct *curr = nullptr;
struct task_struct *prev = nullptr;
pcb_print(run_list);
while (run_list.size() > 0) {
curr = run_list.front();
run_list.pop_front();
context_switch(curr, prev, &cpu);
// do some work
cpu.pc ++;
if (cpu.pc < curr->max_run)
run_list.push_back(curr);
else
complete_list.push_back(curr);
prev = curr;
}
context_switch(NULL, prev, &cpu);
pcb_print(complete_list);
}
int main(int argc, char const *argv[])
{
// init the list
for (int i = 1; i<10; i++) {
struct task_struct *pcb = new task_struct{i, READY, {rd(), rd(), rd(), rd(), rd(), 0}, i};
run_list.push_back(pcb);
}
run();
//cleanup
for (auto it = run_list.begin(); it != run_list.end(); ++it) {
delete *it;
}
for (auto it = complete_list.begin(); it != complete_list.end(); ++it) {
delete *it;
}
return 0;
}
In the above code, you may notice two new elements, so let's review them. The macro rd()
generates a random integer between [10, 100). A macro is a fragment of code that has been given a name. It's a way to define a shorthand for a more extended or complex sequence of instructions. Macros are typically used to simplify code, improve code readability, and make it easier to maintain. The other element is setw(n)
, which essentially outputs n whitespaces.