The first project is to build a basic shell that serves as a multi-core
process queue called pqsh
. This program allows the user to enter in
a series of commands and then schedules the specified jobs to run on
multiple CPU cores as separate processes.
As shown above, the process queue shell has a queue of running jobs (ie. tasks that are actively assigned CPU time), and a queue of waiting jobs (ie. tasks that haven't started yet or have been preempted). Its purpose is to schedule as many running jobs as there are CPU cores using either the FIFO or Round Robin scheduling policies.
Unlike a regular shell, commands in pqsh
are not executed right away.
Instead, new jobs are added to a waiting queue, where they will be
processed by an internal scheduling policy such as FIFO or Round Robin.
When there are available resouces (ie. CPUs), the scheduler will
move the job from the waiting queue to the running queue. When the
job terminates, it will then move to the finished queue.
Working individually or in pairs, you are to utilize low-level
system calls to create this pqsh
shell by noon on Saturday,
August 29, 2020. More details about this project and your deliverables
are described below.
To interact with the shell, the user has access to two commands:
add COMMAND
: This command adds the specified COMMAND
as a job to the
shell's internal waiting queue.
status [QUEUE]
: This command displays metrics from the internal
scheduler (ie. number of jobs in each queue, average turnaround time,
and average response time), including the jobs in each of the internal
running, waiting, and finished queues (if a QUEUE
is
specified then only that queue is displayed, otherwise all queues are
shown).
The order in which jobs should be executed is determined by one of the following scheduling policies:
FIFO: Execute jobs in the order in each they arrival in the process queue.
Round Robin: Execute jobs in a round robin fashion.
You must implement both scheduling disciplines along with any accounting and preemption mechanisms required to build these algorithms.
This project relates directly to our discussion on schedulers. Refer to your textbook, Operating Systems: Three Easy Pieces and the slides on scheduling: Scheduling (FIFO, Round Robin) and Scheduling (MLFQ, Lottery).
The full set of pqsh
command-line options are shown below:
$ bin/pqsh -h
Usage: ./bin/pqsh [options]
Options:
-n CORES Number of CPU cores to utilize
-p POLICY Scheduling policy (fifo, rdrn)
-t MICROSECONDS Timer interrupt interval
-h Print this help message
The number of CPU CORES
and the scheduling POLICY
can be specified
by the user when the shell is started, but cannot change once it is
active. By default, the shell assumes one core and the FIFO
scheduling policy.
Here is an example of pqsh
running one command using the FIFO scheduler
on one CORE
:
$ bin/pqsh
PQSH> help
Commands:
add command Add command to waiting queue.
status [queue] Display status of specified queue (default is all).
help Display help message.
exit|quit Exit shell.
PQSH> add bin/worksim 10
Added process "bin/worksim 10" to waiting queue.
PQSH> status
Running = 1, Waiting = 0, Finished = 0, Turnaround = -nan, Response = -nan
Running Queue:
PID COMMAND ARRIVAL START END
10071 bin/worksim 10 1568055878.75 1568055878.95 0.00
PQSH> status
Running = 0, Waiting = 0, Finished = 1, Turnaround = 10.00, Response = 00.00
Finished Queue:
PID COMMAND ARRIVAL START END
10071 bin/worksim 10 1568055878.75 1568055878.95 1568055889.19
Here is a video example using of pqsh
and the output of its test programs:
You can find a reference implementation of pqsh
in the following
location on the student machines:
/escnfs/home/pbui/pub/bin/pqsh
Feel free to play around with this in order to explore how pqsh
should
behave.
While it is unlikely that you will write an operating system scheduler in your career, you may need to implement or at least interact with things like load balancers or batch queues, especially in parallel or distributed environments such as the cloud.
For instance, many websites use Cloudflare which performs load balance to prevent denial-of-service attacks. Similarly, the CRC uses systems such as HTCondor to schedule many jobs on the distributed computing cluster at Notre Dame. In both of these situations some decision making is required to map jobs to a particular set of resources.
This project will allow to explore the challenges in this type of decision making by having you implementing and test different types of scheduling policies for a process queue.
As noted above, you are to work individually or in pairs to
implement pqsh
. You must use (C99) (not C++) as the implementation
language for pqsh
itself, while any test scripts are to be written in any
reasonable scripting language.
Here is a timeline of events related to this project:
Date | Event |
---|---|
Sunday, August 09 | Project description and repository are available. |
Sunday, August 23 | Brainstorming should be completed. |
Saturday, August 29 | Implementation of pqsh must be completed. |
For the final submission, please open a Pull Request on your repository
(for your pqsh
branch to the master
branch) and assign it to your
grader from the Reading 03 TA List.
To start this project, you must create a Project 01 repository on GitHub using the following template:
Note: Do your work in a separate git branch that can be later merged
into the master
branch (ie. make a pqsh
branch, DO NOT COMMIT OR
MERGE TO MASTER).
The Project 01 repository includes a README.md
file with the following
sections:
Students: This should be your name and email address.
Brainstorming: This contains questions that should help you design and plan your approach to the project. You do not need to fill these out, but they are meant to be helpful.
Errata: This should contain a description of any known errors or deficiencies in your implementation.
You must complete this document report as part of your project.
As you can see, the base Project 01 repository contains a README.md
file
and the following folder hierarchy:
project01
\_ Makefile # This is the project Makefile
\_ bin # This contains the executables
\_ include # This contains the header files
\_ lib # This contains the libraries
\_ src # This contains the C99 source code
\_ tests # This contains test scripts and source code
You must maintain this folder structure for your project and place files in their appropriate place.
While the exact organization of the project code is up to you, keep in mind that you will be graded in part on coding style, cleaniness, and organization. This means your code should be consistently formatted, not contain any dead code, have reasonable comments, and appropriate naming among other things:
Break long functions into smaller functions.
Make sure each function does one thing and does it well.
Abstract, but don't over do it.
Please refer to these Coding Style slides for some tips and guidelines on coding style expectations.
To help you get started, we have provided a Makefile that will build the
pq
application for you:
$ make # Build pq executable
Compiling src/pqsh.o
Compiling src/options.o
Compiling src/process.o
Compiling src/queue.o
Compiling src/signal.o
Compiling src/scheduler.o
Compiling src/scheduler_fifo.o
Compiling src/scheduler_rdrn.o
Compiling src/timestamp.o
Linking lib/libpqsh.a
Linking bin/pqsh
Once you have the pqsh
executable, you can run the shell with different
number of CORES
or with a different scheduling POLICY
:
$ ./bin/pqsh -p fifo # Run shell with FIFO policy
$ ./bin/pqsh -p rdrn -n 2 # Run shell with Round Robin policy and two cores
To help you test your process queue, the bin/worksim
script is
provided to you. This script is a workload simulator and uou can use
it to create a job that simulates a job that runs for a specified duration.
For example, to create a job that runs for 10
seconds:
$ ./bin/worksim 10
This script will be useful for testing the different scheduling policies.
All of the C99 header files are in the include/pq
folder while the
C99 source code is contained in the src
directory. To help you get
started, parts of the project are already implemented:
[x] options.c # PQSH parsing options (implemented)
[~] pqsh.c # PQSH main executable (partially implemented)
[ ] process.c # PQSH process structure (not implemented)
[ ] queue.c # PQSH queue structure (not implemented)
[ ] scheduler.c # PQSH scheduler structure (not implemented)
[ ] scheduler_fifo.c # PQSH FIFO scheduler policy (not implemented)
[ ] scheduler_rdrn.c # PQSH Round Robin scheduler policy (not implemented)
[~] signal.c # PQSH signal handlers (partially implemented)
[ ] timestamp.c # PQSH timerstamp (not implemented)
Basically, some of pqsh
shell code, signal utilities, and command-line
parsing functions are provided to you, but the scheduling and process
management functions are not. Each of the functions in the incomplete
files above have comments that describe what needs to be done.
You will need to examine these source files and complete the implementation
of the shell. To do so, you will first need to implement a basic
Process
structure and a corresponding Queue
structure:
process.c
: This structure is used to keep track of a process in the
system (reference at include/pqsh/process.h
).
queue.c
: This structure is used to track a list or queue or Process
structures. To enable efficient operations to the front and back of the
list, the structure maintains pointers to both the head
and tail
of
the queue. Additionally, it has a size
attribute that records the
number of items in the queue at all times.
scheduler.c
: This contains the main functions required for the shell
include the internal operations and command handlers.
scheduler_{fifo,rdrn}.c
: These contain the implementations to the
corresponding scheduling policies.
timestamp.c
: This contains the implementation of a function that
returns the current time in the form of a double
Here are some hints to help you get started with the project:
process.c
: To start a process, you will need to fork and exec.
Consider using execvp by using strtok to form a local array of
arguments to pass to the system call:
fork()
Child:
char *argv[MAX_ARGUMENTS] = {0}
...
for (char *token = strtok(command, " "); token; token = strtok(NULL, " ")) {
argv[i++] = token;
}
execvp(argv[0], argv);
...
Parent:
Update timestamp
scheduler.c
: When waiting, you should consider using waitpid so you
do not block or hang. If a child has terminated, then you should remove
it from the queue it is in, update its metrics, and then move it to the
finished queue.
while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) {
Process *found = queue_remove(&s->running, pid);
...
}
signal.c
: When the timer goes off, your signal handler should wait
on any terminated children and then schedule the next process.
schedule_wait(&PQShellScheduler);
schedule_next(&PQShellScheduler);
pqsh.c
: To activate a regular timer interval, you should consider using
the setitimer system call.
struct itimerval interval = {
.it_interval = { .tv_sec = 0, .tv_usec = s->timeout },
.it_value = { .tv_sec = 0, .tv_usec = s->timeout },
};
if (setitimer(ITIMER_REAL, &interval, NULL) < 0) {
...
}
To verify the correctness of the Process
, Queue
, and timestamp
functions, three test programs are provided:
$ make test-units
Testing test_process
Testing test_queue
Testing test_timestamp
Likewise, to verify the behavior of your pqsh
shell, we have provided
you a series of test scripts:
$ make test-scripts
Testing test_fifo_checklist ...
Running PQSH commands ... SUCCESS
Verifying PQSH output ... SUCCESS
Verifying PQSH memory ... SUCCESS
Testing test_fifo_multicore ...
Running PQSH commands ... SUCCESS
Verifying PQSH output ... SUCCESS
Verifying PQSH memory ... SUCCESS
Testing test_fifo ...
Running PQSH commands ... SUCCESS
Verifying PQSH output ... SUCCESS
Verifying PQSH memory ... SUCCESS
Testing test_rdrn_checklist ...
Running PQSH commands ... SUCCESS
Verifying PQSH output ... SUCCESS
Verifying PQSH memory ... SUCCESS
Testing test_rdrn_multicore ...
Running PQSH commands ... SUCCESS
Verifying PQSH output ... SUCCESS
Verifying PQSH memory ... SUCCESS
Testing test_rdrn ...
Running PQSH commands ... SUCCESS
Verifying PQSH output ... SUCCESS
Verifying PQSH memory ... SUCCESS
Note, you can always run a specific test directory:
$ ./tests/test_fifo.sh # Run FIFO test script
If necessary, you should not hesitate in create your own additional test programs or scripts to facilitate verification and debugging.
To reflect on the project and the concepts involved, you will need to create a group video demonstration of your software artifact and complete an individual quiz (each member will submit their own answers to their own private assignments repository).
As part of your grade, your group will need to create a video that demonstrates and discusses the following:
Your project passing the automated tests.
Your project working with manual input.
Any errata, quirks, or unresolved issues in your project.
What you learned by doing this project.
The video should include narration to describe what is being presented and
should cover the requirements enumerated above. It should be no longer than
5
minutes.
Please upload the video to either YouTube or Google Drive and provide a
link to it in your README.md
.
Once you have completed the project, answer the following Project 01 Quiz
questions individually in your own personal assignments
repository on GitHub:
The results of the quiz should look like the following:
Submitting project01 assignment ...
Submitting project01 quiz ...
Q1 0.40
Q2 0.40
Q3 0.40
Q4 0.50
Q5 0.90
Q6 1.40
Score 4.00
Each group member should do the quiz on their own and record their answers
in the project01
folder in their assignments GitHub repository.
Your project will be graded on the following metrics:
Metric | Points |
---|---|
Source Code
|
22.0
|
Reflection
|
8.0
|
To encourage you to work on the project regularly (rather than leaving it until the last second) and to practice performing incremental development, part of your grade will be based on whether or not you have regular and reasonably sized commits in your git log.
That is, you will lose a point if you commit everything at once near the project deadline.
In addition to meeting the functional requirements of the project (as described above), your program must not exhibit any memory leaks or invalid memory accesses as would be detected by Valgrind.
Additionally, because system calls can fail, your program must implement robust error handling and ensure your code checks the status of system calls when reasonable.
Moreover, you code must compile without any warnings (and -Wall
must
be one of the CFLAGS
).