\documentclass{ictlab} \RCS $Revision: 1.1 $ \usepackage{alltt,key,color,answer2,float} \usepackage[nolineno,noindent]{lgrind} %\usepackage[leftnum,lineno5,noindent]{lgrind} \ifx\pdftexversion\undefined \else \usepackage[pdfpagemode=None,pdfauthor={Nick Urbanik}]{hyperref} \fi \renewcommand*{\subject}{Operating Systems and Systems Integration} \newcommand*{\labTitle}{Processes: Writing a Simple Shell} \definecolor{light-blue}{rgb}{0.4,0.4,1} \newcommand*{\gl}[1]{\textcolor{light-blue}{#1}} % good link \newcommand*{\ex}[1]{\textcolor{green}{#1}} % executable file \newcommand*{\bl}[1]{\colorbox{red}{\textcolor{white}{\textbf{#1}}}} % bad link \renewcommand{\floatpagefraction}{0.75} % default is .5, to increase % density. \renewcommand*{\bottomfraction}{0.6} % default is 0.3 \renewcommand*{\topfraction}{0.85} % default is 0.7 \renewcommand*{\textfraction}{0.1} % default is 0.2 \setlength{\extrarowheight}{1pt} \floatstyle{ruled} \floatname{program}{Program} \newfloat{program}{tbh}{lop} \begin{document} \section{Aim} \label{sec:aim} The successful student will write a very simple shell, i.e., a program that can interactively start other programs. \section{Background} \label{sec:background} A \emph{shell} is a program that can start other programs. A real shell can do lots of other things (it supports a programming language, for example), but here we keep it very simple, and restrict it to starting other programs. \subsection{How do you Run an External Program as a New Process?} \label{sec:new-process} \begin{itemize} \item Replace the instructions in a running process with a new set of instructions, using the \texttt{exec} function \item First make an exact copy of your process using \texttt{fork()} \item Then replace the contents of this new process with another program, using \texttt{exec()}. \end{itemize} \subsection{The \texttt{exec*()} functions} \label{sec:exec} \begin{itemize} \item There are six kinds of \texttt{exec*()} function; see \texttt{man 3 exec} and \texttt{man 2 execve} \item We will use \texttt{execl()}: \begin{alltt} int execl(const char *path, const char *arg, ...); \end{alltt} Parameter number: \begin{enumerate} \item gives full path of the program file you want to execute \item gives name of the new process \item specifies the command line arguments you pass to the program \item (in this example) is a \texttt{NULL} pointer to end the paramter list. We \emph{must} always put a \texttt{NULL} pointer at the end of this list. \end{enumerate} \item Program~\vref{prg:fork-1} is a simple example, without error checking. \begin{program} %\smallskip% \vspace*{-2ex} %[ #include #include int main() { printf( "hello world\n" ); int pid = fork(); printf( "fork returned %d\n", pid ); if ( pid == 0 ) execl( "/bin/ls", "ls", NULL ); else printf( "I'm the parent\n" ); } %] \vspace*{-3ex} \caption{A simple program using \texttt{fork()} and \texttt{execl()}. It does no error checking.} \label{prg:fork-1} \end{program} \end{itemize} As we saw in the lecture, Linux and Unix provide simple system calls to manage processes: \begin{alltt} #include #include pid_t fork(void); \end{alltt} \textnormal{Returns \emph{twice}; returns 0 if child, returns child's \PID if parent, returns \(-1\) if error.} \subsection{What Happens Between fork() and exec() and After?} \label{sec:what-fork-exec-do} \begin{itemize} \item Before calling \texttt{fork()}: \begin{itemize} \item There is one process, the parent process. \end{itemize} \item After calling \texttt{fork()}: \begin{itemize} \item Two process are running, both still have the original code \end{itemize} \item After calling \texttt{exec()}: \begin{itemize} \item The child process, which called \texttt{exec()}, now has completely different code. \end{itemize} \end{itemize} Program~\vref{prg:print} is called \texttt{print.c} and prints a number $n$ times. \begin{program} %\smallskip% %\begin{verbatim} \vspace*{-2ex} %[ #include #include int main( int argc, char *argv[] ) { // argv[0] is the program name int num = atoi( argv[1] ); int loops = atoi( argv[2] ); int i; for ( i = 0; i < loops; ++i ) printf( "%d ", num ); } %] \vspace*{-3ex} %\end{verbatim} \caption{A simple program \texttt{print.c} that takes two numbers as parameters, and repeats the first number the number of times given by the second number.} \label{prg:print} \end{program} Program~\vref{prg:call-print} is called \texttt{call-print.c}, and is written to call the program \texttt{print.c}, using \texttt{execl()}. \begin{program} %\smallskip% %\begin{verbatim} \vspace*{-2ex} %[ #include #include int main() { printf( "hello world\n" ); int pid = fork(); printf( "fork returned %d\n", pid ); if ( pid == 0 ) execl( "./print", "print", "1", "100", NULL ); else execl( "./print", "print", "2", "100", NULL ); } %] \vspace*{-3ex} %\end{verbatim} \caption{A program \texttt{call-print.c} that uses \texttt{execl()} to call program~\vref{prg:print}, \texttt{print.c}, in two separate processes.} \label{prg:call-print} \end{program} \subsection{Exercise Set 1} \label{sec:exercise-1} \begin{enumerate} \item Copy the programs from the network filesystem at \texttt{/home/nfs/processes-and-threads} to a new directory in your \texttt{\$HOME}\e. \item Compile and run program~\vref{prg:fork-1}. \begin{alltt} $ \textbf{gcc -o fork-1 fork-1.c} $ \textbf{./fork-1} \end{alltt} \item Modify the program \vref{prg:fork-1} so that it runs the program \texttt{ls} with the option \texttt{-l}. \item Compile the program \texttt{print.c} in program~\vref{prg:print} and run it: \begin{alltt} $ \textbf{gcc -o print print.c} $ \textbf{./print 10 5} \end{alltt} Try running it with a few different numbers. \item Compile the main program \texttt{call-print.c} in program~\vref{prg:call-print} and run it. \item Try printing each number: 100 times, 1000 times, 10,000 times, 100,000 times. \end{enumerate} \subsection{Implementing a Shell} \label{sec:shell} \begin{alltt} Prompt user Get command If not time-to-exit Fork new process Replace new process with either \texttt{who}, \texttt{ls} or \texttt{uptime} Read next command \end{alltt} Program~\vref{prg:simple-shell-program} is a simple example shell. \begin{program} %\smallskip% %\begin{verbatim} \vspace*{-2ex} %[ #include #include #include void print_menu( void ) { printf( "Enter 1=who, 2=ls, 3=uptime -> " ); } int main() { int cmd; print_menu(); scanf( "%d", &cmd ); while ( cmd != 0 ) { int pid = fork(); if ( pid == 0 ) { if ( cmd == 1 ) execl( "/usr/bin/who", "who", NULL ); if ( cmd == 2 ) execl( "/bin/ls", "ls", NULL ); if ( cmd == 3 ) execl( "/usr/bin/uptime", "uptime", NULL ); exit( 1 ); } /* add: wait( NULL ); here */ print_menu(); scanf( "%d", &cmd ); } } %] \vspace*{-3ex} %\end{verbatim} \caption{A simple shell program, \texttt{shell-1.c.}} \label{prg:simple-shell-program} \end{program} \subsection{Exercise Set 2} \label{sec:exercise-2} \begin{enumerate} \item Implement program~\vref{prg:simple-shell-program} and run it. What if you give it a wrong number? \item Open a second shell window, and monitor the creation of zombie processes by executing the command \texttt{watch -n1 "ps aux \textbar{} grep ' [Z] '"} \item Modify the program to print an error message if a command is not supported. \item Add the the call to \texttt{wait()} in the loop before printing the menu. What is the difference in the behaviour of your program? \item Modify the program and add two more commands to your shell, such as \texttt{date} and \texttt{hostame}. \item Modify the program so that it will exit cleanly if it reads end of file. You can manually provide end of file to a process reading standard input by pressing \key{Control-d}. Hint: see \texttt{man scanf}. \item Examine the program \texttt{simplesh.c}. Modify it to implement background or foreground processes. \end{enumerate} \end{document}