Shells, System Calls, and Signals What is a Shell? • A shell is a command line interface to the operating system – Fetch a command from the user and execute the command • Sometimes the commands are built-in to the shell • Other times the commands are external system programs or user programs • There are lots of different shells available in UNIX Bourne Shell • Historically the sh language was the first to be created and goes under the name of The Bourne Shell • It has a very compact syntax which makes it obtuse for novice users but very efficient when used by experts • It also contains some powerful constructs built in Bourne Shell • On UNIX systems, most of the scripts used to start and configure the operating system are written in the Bourne shell • It has been around for so long that is virtually bug free C Shell • The C Shell (csh) – Similar syntactical structures to the C language • The UNIX man pages contain almost twice as much information for the C Shell as the pages for the Bourne Shell, leading most users to believe that it is twice as good C Shell • Actually, there are several compromises within the C Shell which makes using the language for serious work difficult • (Check the list of bugs at the end of the man pages!). C Shell • The real reason why the C Shell is so popular is that it is usually selected as the default login shell for most users • The features that guarantee its continued use in this arena are aliases and history lists tcsh – An Enhanced C Shell • An enhanced but completely compatible version of the Berkeley UNIX C Shell, csh • It is a command language interpreter usable both as an interactive login shell and a shell script command processor • Uses a C-like syntax tcsh – An Enhanced C Shell • It includes: – Command-line editor – Programmable word completion – Spelling correction – History mechanism – Job control BASH • GNU Bourne Again Shell • A complete implementation of the IEEE POSIX.2 and Open Group Shell specificaiton with… – Interactive command line editing – Job control on architectures that support it – Csh-like features such as history substitution and brace expansion – …and a slew of other features Korne Shell • The ksh was made famous by IBM’s AIX version of UNIX • The Korne Shell can be thought of as a superset of the Borne Shell as it contains the whole of the Borne Shell world within its own syntax rules Processes and the CWD • Every process runs in a directory – The attribute is called the “current working directory” (cwd) • Finding the CWD char *getcwd( char *buf, size_t size ); – Returns a string that contains the absolute pathname of the current working directory • There are functions that can be used to change the current working directory (chdir) Other Process Attributes • Getting the process id number #include <unistd.h> pid_t getpid( void ); • Getting the group id number gid_t getgid( void ); • Getting the real user ID of a process uid_t getuid( void ); Creating a Process • The only way to create a new process is to issue the fork() system call • Fork() splits the current process into 2 processes, one is called the parent and the other is called the child Parent and Child Processes • The child process is a copy of the parent process • Same program • Same place in the program – Almost…. • The child process get a new process ID Process Inheritance • The child process inherits many attributes from the parent including… – Current working directory – User id – Group id The fork() system call #include <unistd.h> Pid_t fork( void ); • fork() returns a process id (small unsigned integer) • fork() returns twice!!!!!!! – In the parent process, fork returns the id of the child process – In the child, fork returns a 0 Example • #include <unistd.h> • #include <iostream> • using namespace std; • int main( int argc, char *argv[] ) • { • if( fork() ) • cout << "I am the parent" << endl; • else • cout << "I am the child" << endl; • return( 0 ); • } Bad Example (don’t do this) • #include <unistd.h> • #include <iostream> • using namespace std; // This is called a // fork bomb!!!!! // please don’t do this • int main( int argc, char *argv[] ) • { • while( fork() ) • cout << "I am the parent" << endl; • • cout << "I am the child" << endl; • return( 0 ); • } Switching Programs • fork() is the only way to create a new process • This would be almost useless if there was not a way to switch what program is associated with a process • The exec() system call is used to start a new program exec() • There are actually a number of exec functions – execlp, execl, execle, execvp, execv, execve • The difference between these functions is the parameters – How the new program is identified and some attributes that should be set The exec Family • When you call a member of the exec family, you give it the pathname of the executable file that you want to run • If all goes well, exec will never return!!! • The process becomes the new program!!! Execl() • int execl( char *path, char *arg0, char *arg1, …, char *argN, (char *) 0); execl( “/home/bin/foobar”, “alpha”, “beta”, NULL ); A Complete execl Example • • • #include <unistd.h> #include <iostream> using namespace std; • • int main( int argc, char *argv[] ) { • • char buf[ 1000 ]; cout << "Here are the files in " << getcwd( buf, 1000 ) << endl; • • • • execl( "/bin/ls", "ls", "-al", NULL ); cout << "If all goes well, this line will not be printed!!!" << endl; return( 0 ); } fork() and exec() Together • The following program does the following: – fork() – results in 2 processes – Parent prints out it’s PID and waits for child process to finish (to exit) – Child process prints out it’s PID and then exec() “ls” and then exits execandfork.cpp (1) • • • • #include #include #include #include <unistd.h> <iostream> <sys/types.h> <sys/wait.h> • using namespace std; // // // // exec, fork, getpid cout needed for wait wait() execandfork.cpp (2) • • • void child( void ) { int pid = getpid(); • • cout << "CHILD: Child process PID is " << pid << endl; cout << "CHILD: Child process now ready to exec ls" << endl; • • execl( "/bin/ls", "ls", NULL ); } execandfork.cpp (3) • • • • • • • • • void parent( void ) { int pid = getpid(); int stat; cout << "PARENT: Parent process PID is " << pid << endl; cout << "PARENT: Parent waiting for child" << endl; wait( &stat ); cout << "PARENT: Child is done. Parent returning" << endl; } execandfork.cpp (4) • • int main( int argc, char *argv[] ) { • cout << "MAIN: Starting fork system call" << endl; • • • • if( fork() ) parent(); else child(); • cout << "MAIN: Done" << endl; • • return( 0 ); } execandfork.cpp (output) neptune.cs.kent.edu] {58}% a.out MAIN: Starting fork system call CHILD: Child process PID is 32557 CHILD: Child process now ready to exec ls PARENT: Parent process PID is 32556 PARENT: Parent waiting for child a.out lowcost_DB_NOW.pdf asc mail Backup MASC bin MPISpawn2 hybrid_parallel_system.pdf Parallaxis icp_fall2004_prj4.txt Pictures PARENT: Child is done. Parent returning MAIN: Done neptune.cs.kent.edu] {59}% Project 1.pdf public_html Software ZephyrDemo A More Concise Example int main() { pid_t pid; /* fork another process */ pid = fork(); if (pid < 0) { /* error occurred */ fprintf(stderr, "Fork Failed"); exit(-1); } else if (pid == 0) { /* child process */ execlp("/bin/ls", "ls", NULL); } else { /* parent process */ /* parent will wait for the child to complete */ wait (NULL); printf ("Child Complete"); exit(0); } } System Calls for Files and Directories CALL Description fd = open( name, how ) Open a file for reading and/or writing s = close( fd ) Close and open file n = read( fd, buffer, size ) Read data from a file into a buffer n = write( fd, buffer, size ) Write data from a buffer into a file s = lseek( fd, offset, whence ) Move the “current” pointer for a file s = stat( name, &buffer ) Get a file’s status information( in buffer ) s = mkdir( name, mode ) Create a new directory s = rmdir( name ) Remove a directory (must be empty) s = link( name1, name2 ) Create a new entry (name2) that points to the same object as name1 s = unlink( name ) Remove the name as a link to an object (deletes the object if name was the only link to it) More System Calls Call Description pid = fork() Create a child process identical to the parent pid = waitpid( pid, &statloc, options ) Wait for a child to terminate s = execve( name, argv, environp ) Replace a process’ core image exit( status ) Terminate process execution and return status s = chdir( dirname ) Change the working directory s = chmod( name, mode ) Change a file’s protection bits s = kill( pid, signal ) Send a signal to a process seconds = time( &seconds ) Get the elapsed time since January 1, 1970 A Simple Shell while( true ) { type_prompt(); read_command( command, parameters ); if( fork() != 0 ) { waitpid( -1, &status, 0 ); } else { execve( command, parameters, 0 ); } } // repeat forever // display prompt // input from terminal // fork off child process // parent code // wait for child to exit // child code // execute command Signaling Processes • Signal – A signal is a notification to a process that an event has occurred. Signals are sometimes called “software interrupts”. • Features of Signal – Signal usually occur asynchronously. – The process does not know ahead of time exactly when a signal will occur. – Signal can be sent by one process to another process (or to itself) or by the kernel to a process. Sources for Generating Signals • Hardware – A process attempts to access addresses outside its own address space. – Divides by zero. • Kernel – Notifying the process that an I/O device for which it has been waiting is available. • Other Processes – A child process notifying its parent process that it has terminated. • User – Pressing keyboard sequences that generate a quit, interrupt or stop signal. Three Courses of Action • Process that receives a signal can take one of three action: – Perform the system-specified default for the signal • notify the parent process that it is terminating; • generate a core file; – (a file containing the current memory image of the process) • terminate. – Ignore the signal • A process can do ignoring with all signal but two special signals: SIGSTOP and SIGKILL. – Catch the Signal (Trapping) • When a process catches a signal, except SIGSTOP and SIGKILL, it invokes a special signal handing routine. POSIX-Defined Signals (1) • • • • • • SIGALRM: Alarm timer time-out. Generated by alarm( ) API. SIGABRT: Abort process execution. Generated by abort( ) API. SIGFPE: Illegal mathematical operation. SIGHUP: Controlling terminal hang-up. SIGILL: Execution of an illegal machine instruction. SIGINT: Process interruption. – Can be generated by <Delete> or <ctrl_C> keys. • SIGKILL: Sure kill a process. Can be generated by – “kill -9 <process_id>“ command. • SIGPIPE: Illegal write to a pipe. • SIGQUIT: Process quit. Generated by <crtl_\> keys. • SIGSEGV: Segmentation fault. generated by de-referencing a NULL pointer. POSIX-Defined Signals (2) • SIGTERM: process termination. Can be generated by – “kill <process_id>” command. • SIGUSR1: Reserved to be defined by user. • SIGUSR2: Reserved to be defined by user. • SIGCHLD: Sent to a parent process when its child process has terminated. • SIGCONT: Resume execution of a stopped process. • SIGSTOP: Stop a process execution. • SIGTTIN: Stop a background process when it tries to read from its controlling terminal. • SIGTSTP: Stop a process execution by the control_Z keys. • SIGTTOUT: Stop a background process when it tries to write to its controlling terminal. Sending Signals • You may send signals to a process connected to your terminal by typing – ^C – ^\ – ^Z SIGINT terminate execution SIGQUIT terminate and core dump SIGSTOP suspend for later • The terminal driver is a program that processes I/O to the terminal can detect these special character sequences and send the appropriate signal to your interactive shell. • The shell in turn generates an appropriate signal to the foreground process. Kill • The user can use the csh built-in kill command or use regular UNIX kill command to send a specific signal to a named process. – % kill [-sig] process • If no signal is specified, then SIGTERM (15)(terminate) is assumed • In C/C++ the system call is – #include <signal.h> – int kill( int pid, int sig_id ); – Return values: Success = 0, Failure = -1, Sets errno…YES Signal Delivery and Processing • When an interrupt or event causes a signal to occur, the signal is added to a set of signals that are waiting for delivery to a process. • Signals are delivered to a process in a manner similar to hardware interrupts. Signal Delivery • If the signal is not currently blocked by the process, it is delivered to the process following these steps: – The same signal is blocked from further occurrence until delivery and processing are finished – The current process context is saved and a new one built – A handler function associated with the signal is called – If the handler function returns, then the process resumes execution from the point of interrupt, with its saved context restored. Among other things, the signal mask is restored. • Signals have the same priority – But processes can block listening to specific signals via a signal mask Signal Trapping • The system call signal() is used to trap signals – #include <signal.h> – signal( int sig_id, void * handler() ); • Example: Write a C++ program to count the number of times CTRL-C is pressed at the terminal – cc_counter.cpp Alarms • Function – The alarm API requests the kernel to send the SIGALRM signal after a certain number of real clock seconds. – #include <signal.h> – int alarm( unsigned int time_interval); – Return: • Success: the number of CPU seconds left in the process timer; Failure: 1; Sets errno: Yes – Argument • time_interval: the number of CPU seconds elapse time. After which the kernel will send the SIGALRM signal to the calling process. • Example: Write a C++ program to set an alarm for signal 5 seconds after process startup and trap the alarm signal. – alarmer.cpp