Assignment 1

Note: You may work with one other student on this assignment if you wish. See the attached request form if you wish to be assigned a partner. Turn in only one solution per group.

The assignment is due 2:40pm, Friday, September 14. Submit your solution electronically via the Web page, www.cburch.com/cs/350. Do not e-mail it, and do not give me a paper copy.

Objectives

Written assignment

  1. Unix computers include a program called ps. You can type man ps at the Unix prompt to learn about how it works.

  2. Compare software interrupts and process signals. In what ways are they similar? In what ways are they different?

Programming Assignment

Develop a shell program for Minix. A shell is a user program that allows you to enter command lines specifying other programs to run. When you start a Unix terminal, the shell program begins automatically. On the Solaris computers, the default shell is tcsh, but there are many others (e.g., bash and zsh).

Although the shell is not part of the operating system, it uses a wide variety of features provided by the operating system. So by writing one you will gain a greater understanding of the purpose of operating systems and in particular of the facilities provided by the Posix system calls.

Your program for this assignment must be a C program (not C++). The attached handout describes some of the more important differences.

Your shell program will repeatedly accept command lines from the user and execute the specified program, until finally reaching an end-of-file (which the user can designate by typing control-D). (This shell uses the minus sign `-' for its prompt.)

- ls
a.out
- ls -l > tmp
- sort -r < tmp
-rwxrwxrwx  1 cburch   nogroup     35849 Aug 17 06:33 a.out
-rw-------  1 cburch   nogroup         0 Aug 19 07:55 tmp
total 36
- control-D
Note that your shell should be able to handle redirection of input or output into files (using `<' and `>' as in tcsh). It need not handle pipes (`|') - I felt that would make the assignment too long.

The format of a command will be a series of arguments, with successive arguments separated by one or more spaces. Then, if the user wishes to redirect input or output (or both), the user may indicate that. You may assume the user will type a space on both sides of the redirection symbol (`>' or `<').

Basically, your program's structure will be as follows.

repeat indefinitely:
     Output prompt.
     Read command line.
     Break command line into arguments and redirected I/O.
     Search through path for the executable file.
     Fork off a process to execute this file.
     Wait for the forked process to terminate.
end repeat
Your shell program should output intelligible error messages when the user does something bad, like type a command that's not defined within the path, or redirect from a filename that doesn't exist.

Your shell should be able to support the cd command. This is not an executable file that you'll find within the Minix directory structure, because a forked process can't change the state of its parent process, and the whole point of the cd command is to change the shell's state. So you'll have to implement cd directly in the shell. (In Unix-speak, this is called a built-in.) Remember that cd with no arguments should change to the home directory, while with one argument it changes to the specified directory.

Environment variables

Remember that when your shell is started (which, confusingly, you'll start from within another shell), it enters into the main() function defined in your program. Technically, in Unix, the main() function takes three arguments.

 int
main(int argc, char **argv, char **envp); 
The first, argc, tells how many arguments there are. The second, argv, lists the arguments. (Your shell program doesn't need to heed any arguments, so you can ignore these first two parameters.) The last, envp, points to the first element of an array of pointers to the first character of a environment variable definition. The definition is a string containing the variable's name and value separated by an equal sign. For example, one environment variable definition might point to the string HOME=/usr/home/cburch. The envp pointer points to the first item of an array in memory, with successive elements of the array pointing to the first characters of different environment variable values. The array ends with a NULL pointer. See page~1 of the handout for a function that searches through an environment for the value of an environment variable.

Although your program can ignore the argc and argv arguments, the envp argument is important. It contains both the PATH variable (important to finding the executable file requested by the user) and the HOME variable (important to handling cd with no arguments). Additionally, the environment should be handed to the child processes so that they too can access the user's environment.

The value of PATH is a sequence of directory names separated by colons. For example, the default value of PATH in Minix is ``:/bin:/usr/bin''. This refers to three directories: An empty string (referring to the current working directory), the /bin directory, and the /usr/bin directory. Whenever a user types a command like more, the shell should search through these three directories (or whatever directories are listed in the PATH variable) to find which directory contains the executable file more.

System calls

Your program will employ a number of system calls. In writing my solution, I found the following system calls useful.

read() for reading commands from the user (used indirectly via fgets())
write()for printing to the display (used indirectly via printf())
chdir()for changing the current working directory in cd built-in
stat() for identifying executable files within the path
fork() for creating a child process
close()for closing standard I/O for redirection (cf textbook, bottom p~109)
open() for opening files for redirected I/O
execve()for executing an executable file within the child process
exit() for terminating the child process on an error
waitpid()for waiting for the child process to terminate
The textbook (Section 1.4) explains these functions, and the on-line man pages provide more detail. (Under either Solaris or Minix, type ``man -s 2 chdir'' to read the exact details of a system call (chdir() in this example). The ``-s 2'' option indicates that you want to look in Section~2 of the man pages, the section devoted to explaining system calls. The Minix man pages tend to be more concise than the Solaris pages.)

Remember that the 0 file descriptor is for standard input, and the 1 file descriptor is for standard output. You need to keep this in mind when you want to override the descriptor for a child process if its input is to be directed from a file or its output is to be directed to a file.

Using Minix

Your final product must run on Minix to receive full credit. Thus you should eventually test it under Minix. Because the program uses Posix calls, and Minix and Solaris both implement Posix, your program will likely be compatible with Solaris too. So you might develop and debug it your program under Solaris and save Minix for your final testing. (I personally found this more convenient for this assignment.) To compile a program under Solaris, type gcc mysh.c to compile the program, and then ./a.out to run it.

To run Minix, we will be using the smx environment, a program that emulates Minix as a Solaris process. This is quite convenient, as it means you'll get all the benefits of Solaris-developed tools (such as GUI editors and printers) while enjoying an easy transfer to Minix.

To test the program under Minix, you must first set up your environment so that you easily access the Solaris programs related to Minix. To do this, type ``setup_cs350'' at the shell prompt. Then log out of the computer and log back in again. You only have to do this once - the program edits the shell configuration file .tcshrc so that your path is automatically set up to include the directory where the other CSCI 350 programs are located.

After you've accomplished this, you will want to set up your own individual Minix system. To do this, create a directory where you want the various Minix files to be placed and type ``minix_setup''. (This creates two files: the configuration file .minix and a 4MB file root containing the filesystem.

After running minix_setup, you can start Minix in the terminal window by typing minix in the directory where it is set up. You can log into Minix using the root login id (no password). The first thing you'll want to do is to create a user account for running your shell. Type the following at the shell prompt.

adduser userid nogroup /usr/home/userid
After doing this, you can log out from the root account, and then you'll be able to log into Minix using userid.

To run your program, do the following.

  1. To compile your program mysh.c for Minix, go to the Solaris shell prompt and type ``mcc mysh.c''. (This is patently weird: You compile for the Minix operating system using the Solaris program mcc. But that's how smx works.) This will generate an a.out file in the Solaris filesystem suitable for use under Minix (and which won't work within Solaris).

  2. Transfer a.out from the Solaris filesystem to the Minix filesystem by going to Minix under your Minix user account and typing ``sunread a.out > a.out''.

  3. Set the file permissions on the Minix file in order to be able to execute it: ``chmod a+x a.out''.

  4. Run the program: ``./a.out''.

When you want to stop the Minix system, log into the system as root and type halt.

Advice

I strongly recommend that you develop this program incrementally. Although the solution may may not be very long (mine has 213~lines), several concepts are going on at once.

Here were the steps I went through before I reached my final solution. After each step, I completely debugged my solution before continuing.

  1. I first wrote the program to read command lines from the user, split the command line into arguments (and possibly redirected I/O), and simply display the arguments one by one back to the user.

  2. I extended the shell to search through the directories listed in the PATH variable for an executable file of the given name, printing out the file's location.

  3. I extended the program to actually fork off a child process to execute this file.

  4. I made it so that the child process respected I/O redirection requests.

You need to be careful to comment out or delete any print statements you add for the purposes of debugging.

Grading criteria

To grade your program, I will inspect your code and attempt to compile and execute it. I will assign points as follows.

15written assignment
10reads and parses arguments of command line
10correctly finds executable file
10handles the cd built-in
10creates child process to execute, and waits for termination
15handles I/O redirection (including redirection in both ways)
10programming style and quality of documentation in comments
10additional features (background processes, pipes, etc)
Notice you can earn up to 10 bonus points for going beyond the assignment in significant ways, usually in terms of additional functionality. This gives me some flexibility to reward exemplary programs. I won't give out such points lightly.