$30
This assignment has two parts. The first part consists of short answer questions that relate to the lectures on processes and threads, and the second part consists of a c program. The C program is a multipart question, and you are advised to start working on it as soon as possible. Part 1: Short answer questions P1Q1. Describe the difference between uniprogramming, multiprogramming and timesharing systems. (5%) P1Q2. Suppose you have three programs, A, B, and C, that are either running or waiting on a resource. Each program runs for 2msec, waits on a resource for 10msec, and then runs for 4msec. Suppose that the first program starts running at t=0msec, at what time will all 3 programs complete in a uniprogramming system? What about a multiprogramming system? (5%) P1Q3. Consider the following simple C program, which outputs a line of code on the screen. Modify the program using system calls in order to redirect the output to a file called redirect.txt. (5%) #include #include #include int main() { printf(“A simple program output.”); return 0; } Part 2: A simple shell General requirements In this assignment you are required to create a C program that implements a shell interface that accepts user commands and executes each command in a separate process. Your shell program provides a command prompt, where the user inputs a line of command. The shell is responsible for executing the command. The shell program assumes that the first string of the line gives the name of the executable file. The remaining strings in the line are considered as arguments for the command. In order to complete the assignment, you will need to implement the following built-in commands using system calls: ls: list files in the current directory cd: change directory cat: Example usage in shell: sh cat file1 cp: copy file. Example usage in shell: sh cp file1 file2 fg: foreground a background job jobs: list background jobs exit: quit the shell The shell will allow commands to either be run in the foreground or the background by specifying the ampersand (&) at the end of the command. In addition to the built-in commands, you are also to implement output redirection. As an example, if you type sh cat file1 file2 The output of the cat file1 command should be sent to the file2 file. See the class notes on how to implement this feature. Details that would guide you through the implementation are shown below. Implementation guidelines Consider the following example. sh cat prog.c The cat is the command that is executed with prog.c as its argument. Using this command, the user displays the contents of the file prog.c on the display terminal. If the file prog.c is not present, the cat program displays an appropriate error message. The shell is not responsible for such error checking. In this case, the shell is relying on cat to report the error. One technique for implementing a shell interface is to have the parent process first read what the user enters on the command line (i.e., cat prog.c), and then create a separate child process that performs the command. Unless specified otherwise, the parent process waits for the child to exit before continuing. However, UNIX shells typically also allow the child process to run in the background – or concurrently – as well by specifying the ampersand (&) at the end of the command. By re-entering the above command as follows the parent and child processes can run concurrently. sh cat prog.c & Remember that parent is the shell process and child is the process that is running cat. Therefore, when the parent and child run concurrently because the command line ends with an &, we have the shell running before the cat completes. So the shell can take the next input command from the user while cat is still running. As discussed in the lectures, the child process is created using the fork() system call and the user’s command is executed by using one of the system calls in the exec() family (see man exec for more information). Simple Shell A C program that provides the basic operations of a command line shell is supplied below for illustration purposes. This program is composed of two functions: main() and getcmd(). The getcmd() function reads in the user’s next command, and then parses it into separate tokens that are used to fill the argument vector for the command to be executed. If the command is to be run in the background, it will end with ‘&’, and getcmd() will update the background parameter so the main() function can act accordingly. The program terminates when the user enters because getcmd() invokes exit(). The main() calls getcmd(), which waits for the user to enter a command. The contents of the command entered by the user are loaded into the args array. For example, if the user enters ls –l at the command prompt, args[0] is set equal to the string “ls” and args[1] is set to the string to “– l”. (By “string,” we mean a null-terminated C-style string variable.) This programming assignment is organized into three parts: (1) creating the child process and executing the command in the child, (2) signal handling feature, and (3) additional features. Creating a Child Process The first part of this programming assignment is to modify the main() function in the figure below so that upon returning from getcmd() a child process is forked and it executes the command specified by the user. #include #include #include #include // // This code is given for illustration purposes. You need not include or follow this // strictly. Feel free to writer better or bug free code. This example code block does not // worry about deallocating memory. You need to ensure memory is allocated and deallocated // properly so that your shell works without leaking memory. // int getcmd(char *prompt, char *args[], int *background) { int length, i = 0; char *token, *loc; char *line = NULL; size_t linecap = 0; printf("%s", prompt); length = getline(&line, &linecap, stdin); if (length <= 0) { exit(-1); } // Check if background is specified.. if ((loc = index(line, '&')) != NULL) { *background = 1; *loc = ' '; } else *background = 0; while ((token = strsep(&line, " \t\n")) != NULL) { for (int j = 0; j < strlen(token); j++) if (token[j] <= 32) token[j] = '\0'; if (strlen(token) 0) args[i++] = token; } return i; } int main(void) { char *args[20]; int bg; while(1) { bg = 0; int cnt = getcmd("\n ", args, &bg); /* the steps can be..: (1) fork a child process using fork() (2) the child process will invoke execvp() (3) if background is not specified, the parent will wait, otherwise parent starts the next command... */ } } As noted above, the getcmd() function loads the contents of the args array with the command line given by the user. This args array will be passed to the execvp() function, which has the following interface: execvp(char *command, char *params[]); Where command represents the file to be executed and params store the parameters to be supplied to this command. Be sure to check the value of background to determine if the parent process should wait for the child to exit or not. You can use the waitpid() function to make the parent wait on the newly created child process. Check the man page for the actual usage of the waitpid() or similar functions that you can use. Signal Handling Feature The next task is to modify the above program so that it provides a signal handling feature. You can think of the signals as software interrupts. The signals are sent to a process because of external events such as keyboard presses (pressing the Ctrl + C key), external programs sending signals with specific values, or abnormal conditions created by the program (segmentation faults). A process is wired to act in a default way on the receipt of a given signal. You can change this default behaviour using a system call. The figure below shows how a program reacts to a delivery of a signal assuming the program is programmed to anticipate the arrival of the signal. You need to develop a signal feature that would do the following: (a) kill a program running inside the shell when Ctrl+C (SIGINT) is pressed in the keyboard and (b) ignore the Ctrl+Z (SIGTSTP) signal. That is for SIGINT you kill the process that is running within the shell. For Ctrl+Z, you just ignore it so that your shell would not react to it using the default action. There are two ways to setup signal handlers. We would use the legacy interface because it is easy to understand. For portable programs, the approach is not recommended because it works differently on different Unix/Linux implementations. The general approach is very simple. You create a function for handling a particular signal and hookup the function to act as the signal handler using the signal() system call. The following example program should give you the general idea. Built-in Commands A command is considered built-in, when all the functionality is completely built into the shell (i.e., without relying on an external program). The main feature of the shell discussed in the first two sections of this handout is about forking a child process to run external commands. The cd command could be implemented using the chdir() system call, the pwd could be implemented using the getcwd() library routine, and the exit command is necessary to quit the shell. The fg command should be called with a number (e.g., fg 3) and it will pick the job numbered 3 (assuming it exists) and put it into the foreground. The command jobs should list all the jobs that are running in the background at any given time. These are jobs that are put into the background by giving the command with & as the last one in the command line. Each line in the list provided by the jobs should have a number identifier that can be used by the fg command to bring the job to the foreground (as explained above). Other useful information You need to know how process management is performed in Linux/Unix to complete this assignment. Here is a brief overview of the important actions. (a) Process creation: The fork() system call allows a process to create a new process (child of the creating process). The new process is an exact copy of the parent with a new process ID and its own process control block. The name “fork” comes from the idea that parent process is dividing to yield two copies of itself. The newly created child is initially running the exact same program as the parent – which is pretty useless. So we use the execvp() system call to change the program that is associated with the newly created child process. (b) The exit() system call terminates a process and makes all resources available for subsequent reallocation by the kernel. The exit(status) provides status as an integer to denote the exiting condition. The parent could use waitpid() system call to retrieve the status returned by the child and also wait for it. (c) File descriptor duplication system call (dup()). Marking scheme for the programming part A simple shell that runs: shows prompt, runs commands, goes to the next one, does not crash for different inputs (e.g., empty strings) 30% Signal feature 10% Other built-in features 20% Output redirection 15% Memory leak problems 5% Code quality and general documentation 5%