Unix Signals - Computer Science
Download
Report
Transcript Unix Signals - Computer Science
Shells, System Calls, and
Signals
Michael Scherger
Department of Computer Science
Kent State University
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/mscherge/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
/.automount/b2/users/cs/grad/mscherge
[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
SIGQUIT
SIGSTOP
terminate execution
terminate and core dump
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
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