CS2106: Introduction to Operating Systems

As programmers, command-line interpreters, also known as shells, are an important and ubiquitous part of our lives. A command line interpreter (or command prompt, or shell) allows the execution of multiple user commands with a various number of arguments. The user inputs the commands one after another, and the commands are executed by the command line interpreter.

A shell is actually just another program implemented using the process-related system calls discussed in the lectures. In this lab, you are going to implement a simple shell with the following functionalities:

  1. Running commands in the foreground and background
  2. Chaining commands
  3. Redirecting input, output, and error streams
  4. Terminating commands
  5. Managing the processes launched by this shell

The main purpose of this lab is to familiarize you with:

  1. advanced aspects of C programming,
  2. system calls,
  3. process operations in Unix-based operating systems.
    • Exercises in Lab 2

    This lab has a total of four exercises.

    For exercise 1, only some simple functionalities are required. Take the opportunity to design your code in a modular and extensible way. You may want to look through the rest of the exercises before starting to code.

    Shell Implementation

    The driver of the shell has been implemented for you in driver.c. The driver will read and tokenize the user commands for you, where tokens will be separated by spaces.

    We provide a structure PCBTable in myshell.h as follows:

    struct PCBTable {
    pid_t pid;
    int status; // 4: Stopped, 3: Terminating, 2: Running, 1: exited
    int exitCode; // -1 not exit, else exit code status


    1. pid is the process id
    2. status indicates the status of the process ;
    3. exitCode indicate the exit status of process, -1 if still running

    Please use the PCBTable to maintain the details of all the processes you fork. Please define a structure variable of type PCBTable, which can be an array, a linked list or other containers. You can assume there won’t be more than 50 PCBTables.

    You should implement the three functions in the file myshell.c:

    1. my_init() is called when your shell starts up, before it starts accepting user commands. It should initialize your shell appropriately.
    2. my_process_command()is called in the main loop. It should handle every user command except the quit command. The function accepts two arguments:
       tokens is an array of tokens with the last element of the array being NULL.
       size is the size of that array, including the NULL.
      You may want to print out the tokens array or look at the driver.c file for your own understanding.
    3. my_quit() is called when a user inputs the quit command

    We have also provided some executable programs with various runtime behaviors in the programs folder to aid your testing later. You can also create your own programs and run them with your shell.

    1. programs/going to sleep: Prints “Good night!” and then prints “Going to sleep” 10 times with a 2-second sleep between each print.
    2. programs/lazy: Wake up, echo “Good morning…” and loops for 2 minutes. It takes a while (at least 5 seconds) to respond to the SIGTERM signal.
    3. programs/result: Takes in a single number x and exits with a return status of x.
    4.  programs/showCmdArg: Shows the command line arguments passed in by the user.
    5. programs/Makefile: To compile the above programs.

    Preventing Fork Bombs

    During implementation, you might mistakenly call fork infinitely and ignite a fork bomb. Thus we have provided a fork monitor (in monitor.c and fork-wrapper.c) to prevent the shell from creating too many subprocesses. If your shell creates too many processes, it will be killed with a “YOU ARE HITTING
    THE FORK LIMIT” message. Please check your code if you see that message.

    Although we have such precautions, you should still take care of the problem. Add in the “fork()” call only after thoroughly testing the basic code. You may want to test it separately without putting it in a loop first. For any child process, make sure you have a “return …” or “exit()” as the last line of code (even if there is an exec before as the exec can fail).

    If you accidentally ignite a fork bomb on one of the SoC Compute Cluster node, your account may be frozen. Please contact your lab tutor who will then contact SoC Technical Service to unfreeze your account and kill off all your processes.

    Build and Run

    Use the commands below to build and launch your shell.

    $ make # Build
    $ ./monitor myshell # Launch the shell with fork monitor

    You may add -Werror into the CFLAGS in the Makefile to make all warning errors. You will not be penalized for warnings, but you are strongly encouraged to resolve all warnings. Warnings are indications of potential undefined behavior that may cause your program to behave in inconsistent and unexpected ways that may not always manifest (i.e., it may work when you test, but it may fail when grading). You are also advised to use Valgrind to ensure your program is free of memory errors.

    On launching your shell, you will then see an myshell> prompt where you can input commands in a similar manner to a Bash shell. The commands that this shell should accept are elaborated in the following sections