Saturday, November 15, 2008

SIGNAl(LINUX)

Books and manuals:
1. Advanced Programming in the UNIX Environment
by W.Richard Stevens.
2. Manual of signal(7)

Create a directory [ signal1 ] in your home directory. Carry out this
experiment in that directory.
$ mkdir $HOME/signal1
$ cd $HOME/signal1


Signals are software interrupts and act asynchronously on a process.
Every signal has a name and a corresponding number. To get a list of
signals available in your machine use this command.
$ less /usr/include/bits/signum.h

POSIX.1 defines signal number zero as null signal.
You may read manual of [signal(7)].

A signal can be generated by different conditions:

The terminal generated signal occur when the user presses certain
key-combinations.
Example:
key-combination Signal name Intention

CTRL-C SIGINT Interrupt from keyboard
CTRL-Z SIGTSTP Stop typed at keyboard
CTRL-\ SIGQUIT Quit from keyboard

Hardware exceptions ( division by zero, invalid memory reference,
illegal instruction ) are detected by hardware and the kernel is
notified. The kernel in turn sends the appropriate signal to the
process, that has caused the hardware exception.
Example:
SIGILL Illegal Instruction
SIGFPE Floating point exception
SIGSEGV Invalid memory reference


The shell command [ kill ] can send a specified signal to a process.
The sender of the command must be the owner of the process or the
superuser to have effect on the process. You may read [ kill(1) ].
Note: The name of the command is a misnomer. It is not used only
to terminate a process.
Example:
Command executed at shell prompt to stop a process
$ kill -SIGSTOP 356
[ SIGSTOP ] signal is delivered to the process with PID=356,
if the user executing the command has proper permission.


The system call [ kill ] can be used to send a specified signal to
another process or process group. The process sending the signal
must be the superuser or owner of the process that the signal is
send to, to be effective. You must read [ kill(2) ].
Note: The name of this function is a misnomer too.
Example:
The following is a line of C program, which tries to restart
a stopped process.
n = kill( SIGCONT, pid );
The value of [n] would be -1, if the calling process is not
the owner of process of PID = [pid]. There are other reasons
for the call to fail.


The process has to be notified, when some software conditions, such
as, when an alarm clock set by the process expires. This notificati-
on comes to the process as a signal. ( SIGALRM, number= 14 in this
case.)

See [$ cat /usr/include/bits/signum.h | grep SIGALRM )

Example:
The following are a few lines of C program

alarm(10);
read( STDIN_FILENO, buffer, BUFFERSIZE );
alarm(0);

An alarm clock is set with ten seconds. If keyboard input is
completed within ten seconds, the alarm clock is cleared.
The alarm clock goes off after 10 seconds and SIGALRM signal
is delivered to the process.

Example:
Signal [SIGCHLD] is delivered to the parent when a [ child ]
is stopped or terminated. By default the [ parent ] ignores
this signal.

When a process receives a signal, it can tell kernel to do one out of
three actions ( also called "disposition" ) of the signal.

1. The signal can be ignored. SIGKILL and SIGSTOP cannot be ignored.
2. The process can invoke a function when a particular signal is
received. This is called catching the signal. The function is
called the signal handler.
( According to POSIX, the behaviour of a process is
undefined after it ignores a SIGFPE, SIGILL, or
SIGSEGV signal, under certain conditions ).
3. The default action of the signal may take place.

Example with shell scripts:

Save the following lines in a file named [s1].

-----------------------
#!/bin/bash
# file-name s1

while : # endless loop
do
echo my PID = $$
done

exit 0
-----------------------

Make the file [s1] readable and executable by owner
$ chmod 0500 ./s1

Assignment-1.
Run the shell script in Terminal-2
T-2 $ ./s1

Login to your account in Terminal-3 and check the running process
T-3 $ ps ax

PID of the running process----->
Status of the running process--> ( R / S / T )

Press CTRL-C ( send signal SIGINT to the process ) in the Terminal-2
T-2 $ CTRL-C

Did the process exit ? ( Y / n )


Run the program again and record PID & status of the running process
T-2 $ ./s1

PID--> and Status-->

Press CTRL-Z ( send signal SIGTSTP to the process )
T-2 $ CTRL-Z

The process stopped running ? ( Y / n )
PID--> and Status-->
Record job number within square bracket-->

Send signal SIGINT to the process )
T-2 $ CTRL-C

Pressing CTRL-C now had any effect ? ( y / N )

Bring the process in foreground
T-2 $ fg job-number

Terminate the foreground process with CTRL-C ( with SIGINT signal )
T-2 $ CTRL-C



Re-run [./s1] from your account.
T-2 $ ./s1

Login as [ guest ] in terminal-4. Try to terminate the running process
as [guest] using [kill] command, using signal number 9.
T-4 guest $ kill -9 PID

Can another ordinary user send signal to the process run by
you ? ( y / N )
Can superuser send signal to the process run by you ? ( Y / n )

Terminate the program with CTRL-C
T-2 $ CTRL-C

Run the program again
T-2 $ ./s1

Record the following for the running process
T-3 # ps ax

Record PID--> and Status-->

Try to stop the process with [kill] commands using signal numbers
( Signal numbers taken from manual of [ signal(7) )

Try to stop the process with...
T-3 $ kill -17 value-of-PID
Record PID--> and Status-->

Try to stop the process with...
T-3 $ kill -23 value-of-PID
Record PID--> and Status-->

Try to stop the process with...
T-3 $ kill -19 value-of-PID
Record PID--> and Status-->

Restart the process with symbolic name of the signal..
T-3 $ kill -SIGCONT value-of-PID
Record PID--> and Status-->

Now stop the process with symbolic name of the stop signal
T-3 $ kill -SIGSTOP value-of-PID

Should you use symbolic constant instead of magic number ? ( Y / n )

Use a signal, other than [ SIGKILL ] to terminate the stopped process
and record the command

T-3 $ kill -______________ PID

End of Assignment-1.


Different signals have some system default actions on the process. In
user programs, these default actions can be ignored or some other
action may be taken when a particular signal arrives, using the [trap]
shell command. This is called "trapping of signal". [ SIGKILL ] and
[SIGSTOP] cannot be trapped. You may read manual of [ bash(1) ].

Save the following lines in a file [s2].
-------------------------------------------------------------------
#!/bin/bash
# file-name s2

function sig_handler1
{
echo "Who is hitting me with SIGHUP ?"
}

function sig_handler2
{
echo "I have not done any illegal operation !"
}

# when SIGHUP is delivered to the process, the function
# [sig_handler1] is invoked
trap 'sig_handler1' SIGHUP # Trapped

# when SIGILL is delivered to the process, the function
# [sig_handler2] is invoked
trap 'sig_handler2' SIGILL # Trapped

trap ': ' SIGINT # From keyboard, silently trapped
trap 'echo got SIGQUIT' SIGQUIT # From keyboard, trapped
trap 'echo got SIGTSTP' SIGTSTP # From keyboard, trapped

trap 'echo got SIGKILL' SIGKILL # unblock-able, can't be trapped!

trap 'echo got SIGALRM or SIGTERM' SIGALRM SIGTERM
# In the line above, the two signals are trapped.

# In the next line try to trap another unblock-able signal.


# Put in the next line any signal of your choice in Assignment-3


while : # endless loop
do
sleep 2
echo my PID = $$
done

exit 0
-------------------------------------------------------------------

When [ SIGHUP ] signal is delivered, the function [ sig_handler1 ] is
called. For [SIGILL] signal, the function [ sig_handler2 ] is called.
The three signals [SIGINT SIGQUIT and SIGTSTP], generated from the
keyboard are trapped. Among these [ SIGINT ] is silently ignored.
Signals [ SIGALRM and SIGTERM ] are trapped with a single [ trap ]
command. The [ SIGKILL ] signal was tried to be trapped. This was not
effective.

Assignment-3:
Make the shell script [s2] executable and run it
T-2 $ chmod 0500 ./s2
T-2 $ ./s2

Record PID--> and Status-->

What are the signal generated with key combinations given below ?

CTRL-C -> CTRL-Z -> CTRL-\ ->

Are these signals trapped in [s2] ? ( Y / n )

From another terminal send the following signals to the running
process. Restart the process if terminated.

SIGILL The process terminated ? ( Y / N )
SIGALRM The process terminated ? ( Y / N )
SIGTERM The process terminated ? ( Y / N )
SIGSTOP The process stopped ? ( Y / N )
SIGCONT The process re-started ? ( Y / N )
SIGKILL The process terminated ? ( Y / N )

Fill in the two blank lines provided in the shell script.

End of Assignment-3.

Example:
Write a program to print the default message of SIGINT signal
and also print the user defined message when SIGINT will be invoked.
(WBUT2004-oslab)

Note: There is no default message associated with a signal. There
is default disposition or default action, associated with a
signal. The default action associated with SIGINT is to
terminate the process. Read manual of [signal(7)]. In this
example, the default action of SIGINT signal is changed. The
process continues to run even after receiving SIGINT signal
and prints a user defined message.


-------------------------------------------------------------
#!/bin/bash
# file-name s3
# comment the following line for default action to take place
trap 'echo $USER: my script received SIGINT' SIGINT

while :
do
: # do nothing command
done

exit 0
-------------------------------------------------------------

Run the shell script and test with CTRL-C
T-2 $ sh ./s3
T-2 $ CTRL-C

From another terminal test with SIGINT signal
T-3 $ ps ax
PID = ______

T-3 $ kill -SIGINT value-of-PID

Terminate with [SIGHUP] signal.
T-3 $ kill -SIGHUP value-of-PID



Examples with system calls:

The system call [ signal(2) ] installs a signal handler. It takes two
arguments. First is the signal number. The second is one out of these
three..

SIG_IGN ( Ignore the signal )
User defined function ( Signal handler )
SIG_DFL ( Default action )

Older programs use the above system call to handle ANSI signals. It is
is still possible to use [ signal(2) ] but the actions of the call are
implemented using POSIX signals. This is transparent to the user.

The following lines are taken from manual of [ signal(2) ]:

"It is better to avoid signal altogether,
and use sigaction(2) instead."

[sigaction(2)] is used for POSIX signal handling.

Example:
Write a program to print the default message of SIGINT signal
and also print the user defined message when SIGINT will be invoked.
(WBUT2004-oslab)

Implementation using C program
------------------------------------------------------------
// file-name f1.c

#include
#include
#include

typedef void (*sighandler_t)(int);

// prototype of the signal handler
void function1( int sig_number );

int main( void )
{

sighandler_t n;

// when SIGINT is delivered to the process, [function1] is
// called
n = signal( SIGINT, function1 );
if( n == SIG_ERR ) { perror("signal"); exit(1); }

while(1) // endless loop
{
printf("one: PID = %u; process group = %u\n",
getpid(), getpgrp() );
sleep( 5 );
}

return 0;
}

// the signal handler
void function1( int sig_number )
{
printf("Received signal number %u \n", sig_number );
return;
}
------------------------------------------------------------
Compile and run
$ gcc -Wall ./f1.c -o ./one
$ ./one

Test with CTRL-C and terminate with CTRL-\ ( SIGQUIT )

The system call [kill(2) ] call can be used to send any signal to any
process group or process. It takes two arguments. The first is the PID
of the receiving process and the second is the signal name or signal
number. The sending process must have proper permission to send the
signal. For details you may read [kill(2)]. If the processes are of
different ancestors, one process has to know the PID of another
process by some means. It is possible to send a signal from child to
parent or parent to child, as they are related processes and are able
to find out the PID of each other.

Example:
Write a program to create a child process and send a SIGCLD
signal to parent process.(WBUT2004-oslab)

Note: SIGCLD is a synonym for SIGCHLD.

Save the following program in a file [f2.c].
-----------------------------------------------------------------
// file-name f2.c

#include
#include
#include
#include
#include

typedef void (*sighandler_t)(int);

// Function prototype.
void function( int number );

int main(void)
{
pid_t n,k;
int l;
sighandler_t j;

// main forks a child
n = fork();
if( n == -1 ) { perror("fork"); exit(1); }

if ( n == 0 ) // child process
{
// child finds out parent's PID, to use it as the first
// argument of [kill(2)] system call
k = getppid(); // k = parent's PID

printf("child: My PID = %u \n", getpid() );

// child is sending SIGCHLD signal to parent at intervals
// Five seconds
while(1)
{
sleep(5);
printf( "%u: sending signal %u to %u \n",
getpid(), SIGCHLD, k );
l = kill( k, SIGCHLD );
if( l == -1 ) { perror("kill"); exit(2); }
}
}

// rest is parent process

// parent installs a signal handler for SIGCHLD
j = signal( SIGCHLD, function );
if( j == SIG_ERR ) { perror("signal"); exit(3); }

printf("parent: My PID = %u \n", getpid() );

// parent enters endless loop
while(1) { sleep(5); };

return 0;
}

// the signal handler
void function( int number )
{
printf( "%u: Received signal %u \n", getpid(), number );
//[getpid()] is used to identify the process getting the signal

return;
}
-----------------------------------------------------------------
Compile, run, terminate with CRTL-C after a few lines of output

$ gcc -Wall ./f2.c -o ./two
$ ./two


Record with pencil:

parent: My PID = ______
child: My PID = ______

____: sending signal ___ to ____

____: Received signal ____


Note: The [parent] installed the signal handler after [fork()] call.
If [parent] installed the signal handler before the [fork()]
call, the [child] would have inherited the signal handler.


Example:
Write a program to show that SIGCHLD is delivered to the
parent process, whenever the child stops or is terminated.
---------------------------------------------------
// file-name f3.c

#include
#include
#include
#include
#include

typedef void (*sighandler_t)(int);

// Function prototype
void sig_handler( int number );

int main( void )
{
pid_t n;
sighandler_t j;

// main forks a child
n = fork();
if( n == -1 ) { perror("fork"); exit(1); }

if ( n == 0 ) // child process
{
printf("child: My PID = %u \n", getpid() );

// child enters an endless loop
while(1) { sleep(1); }
}

// rest is parent process

// parent installs a signal handler for SIGCHLD
j = signal( SIGCHLD, sig_handler );
if( j == SIG_ERR ) { perror("signal"); exit(2); }

printf("parent: My PID = %u \n", getpid() );
// parent enters endless loop
while(1) { sleep(1); };

return 0;
}

// the signal handler
void sig_handler( int number )
{
printf( "%u: Received signal number %u \n",
getpid(), number );

return;
}
---------------------------------------------------
Compile, run and record output by sending signals from Terminal-3
$ gcc -Wall ./f3.c -o ./three
$ ./three

parent: My PID = ____
child: My PID = ____
____: Received signal number ___ ( when child is stopped )
____: Received signal number ___ ( when child is terminated )

Login in another terminal and execute the following commands.

Stop the child process
T-3 $ kill -SIGSTOP value-of-child's-PID
Was the child stopped ? ( Y / n )
Was SIGCHLD delivered to parent, when child stopped ? ( Y / n )

Restart the child process
T-3 $ kill -SIGCONT value-of-child's-PID
Was the child restarted ? ( Y / n )
Was SIGCHLD delivered to parent, when child restarted ? ( y / N )

Terminate the child process
T-3 $ kill -SIGHUP value-of-child's-PID
Was SIGCHLD delivered to parent, when child was terminated ? ( Y / n )
Did the child become a zombie process ? ( Y / n )

Terminate the parent process
T-3 $ kill -SIGTERM value-of-parent's=PID
Was the parent terminated ? ( Y / n )
Was the zombie process removed ? ( Y / n )


Example:
Write a program to get an interrupt from machine and display
the value of the signal.(WBUT2004-oslab)

See [ f1.c ] for C program implementation and [ s3 ] for shell script
implementation.

Example:
What is a signal? Show appropriate programs that demonstrate
SIGINT, SIGHUP and SIGCLD signals.(WBUT2004-oslab)

Save the following program as [f4.c]
------------------------------------------------------
// file-name f4.c

#include
#include
#include

typedef void (*sighandler_t)(int);

// prototypes of the signal handlers
void function1( int sig_number ); // for SIGINT
void function2( int sig_number ); // for SIGHUP
void function3( int sig_number ); // for SIGCLD

int main( void )
{

sighandler_t n;

// three signal handlers are installed
n = signal(SIGINT,function1);
if( n == SIG_ERR ) { perror("signal-1"); exit(1); }

n = signal(SIGHUP,function2);
if( n == SIG_ERR ) { perror("signal-2"); exit(2); }

n = signal(SIGCLD,function3);
if( n == SIG_ERR ) { perror("signal-3"); exit(3); }


while(1) // endless loop
{
sleep (1);
printf("Running \n");
}


return 0;
}

// signal handler for SIGINT
void function1( int sig_number )
{
printf("Received signal number %u \n", sig_number );
return;
}

// signal handler for SIGHUP
void function2( int sig_number )
{
printf("Received signal number %u \n", sig_number );
return;
}

// signal handler for SIGCLD
void function3( int sig_number )
{
printf("Received signal number %u \n", sig_number );
return;
}
------------------------------------------------------
Compile, run and record output with three signals
T-2 $ gcc -Wall ./f4.c -o ./four
T-2 $ ./four

Received signal number ___ ( when SIGINT arrived )
Received signal number ___ ( when SIGHUP arrived )
Received signal number ___ ( when SIGCHLD arrived )

From another terminal test with following signals
T-3 $ ps ax
PID = _____

T-3 $ kill -SIGINT value-of-PID
T-3 $ kill -SIGHUP value-of-PID
T-3 $ kill -SIGCHLD value-of-PID

Terminate with CRTL-\ ( SIGQUIT )

---------------------------

Optional Assignment:

When [ kill ] system call is used with 0 (zero) as the first argument,
then the signal is sent to all processes whose process group ID equals
process group ID of the sender. To illustrate this feature, the
program [f5.c] may be used.

This example also shows that PID and process group ID do not change
after an [exec] and the new process inherits them ( among many other
properties ) from the calling process. (Read APIUE, page-210).

Save the following lines in a file [f5.c]
----------------------------------------------------------------------
// file-name f5.c

#include
#include
#include
#include
#include

typedef void (*sighandler_t)(int);

// function prototype
void handler( int signum );

int main(void)
{
pid_t y;
int n;
sighandler_t a;


printf( "Parent--> pid = %u pgrp = %u\n", getpid(),getpgrp() );

y = fork();
if( y == -1 ) { perror("fork-1"); exit(1); }

if ( y == 0 ) // 1st-child process
{
printf("1st-child:pid = %u pgrp = %u\n", getpid(), getpgrp() );

while(1) { sleep(1); } // endless loop
}

// parent process
y = fork();
if( y == -1 ) { perror("fork-2"); exit(2); }

if ( y == 0 ) // 2nd-child process
{
printf("2nd-child:pid = %u pgrp = %u\n", getpid(), getpgrp() );
n = execl("./s4", NULL );
if( n == -1 ) { perror("execl-1"); exit(3); }
}

// parent process.
y = fork();
if( y == -1 ) { perror("fork-3"); exit(4); }

if ( y == 0 ) // 3rd-child process
{
printf("3rd-child:pid = %d pgrp = %u\n", getpid(), getpgrp() );
n = execl("./one", NULL );
if( n == -1 ) { perror("execl-2"); exit(5); }
}

//parent process.

// parent installs a signal handler for SIGUSR1
a = signal( SIGUSR1, handler );
if( a == SIG_ERR ) { perror("signal"); exit(6); }
pause(); // wait for a signal to arrive
printf("parent: sending SIGTERM signal to process group members\n");
n = kill( 0, SIGTERM );
if( n == -1 ) { perror("kill"); exit(6); }

while(1) { sleep(1); } // endless loop

return 0;
}

void handler( int signum )
{
printf("%u: received signal %u \n", getpid(), signum );
return;
}
----------------------------------------------------------------------
In this example the parent forks three children.

1st child: enters an endless loop.

2nd child: Executes a shell script [./s4], which has an endless loop.
The child process is completely replaced by [./s4] but PID
and the process group identity remains same after [execl].

3rd child: Executes an executable file [ ./one ], which also has an
endless loop. The child process is completely replaced by
[ ./one ] but PID and the process group ID remains same
after [execl].

The parent installs a signal handler for [SIGUSR1] and calls [ pause ]
system call. With [ pause ] call, parent waits for any signal that
causes the calling process ( the parent ) to terminate or to invoke a
signal handler. In this example [ SIGUSR1 ] is sent to the parent from
another terminal. When [ SIGUSR1 ] signal arrives, the parent leaves
[ pause ] and sends [ SIGTERM ] to all processes in its process group,
including itself.

Write a shell script [s4]. Second [child] executes this program.
-----------------------------------
#!/bin/bash
# file-name s4

while : # endless loop
do
printf "%s: PID = %u \n" $0 $$
sleep 5
done

exit 0
-----------------------------------
Make the shell script executable
T-2 $ chmod 0500 ./s4


(a) Compile, run and record

T-2 $ gcc -Wall ./f5.c -o ./five
T-2 $ ./five

---------------------------------------
| Program name | PID | Process Group |
---------------------------------------
| Parent | | |
---------------------------------------
| 1st-child | | |
---------------------------------------
| 2nd-child | | |
---------------------------------------
| 3rd-child | | |
---------------------------------------
| s4 | | |
---------------------------------------
| one | | |
---------------------------------------


(b) Send [SIGUSR1] to the parent

T-3 $ kill -SIGUSR1 PID-of-parent

Were all the processes terminated ? ( Y / N )


(c) Run the program again and send [ SIGTERM ] to parent from another
terminal.

T-2 $ ./five

T-3 $ kill -SIGTERM PID-of-parent

Were all the processes terminated ? ( Y / N )

---------------------------