Crisp Reading Notes on Latest Technology Trends and Basics

Signals are one of the most complex pieces to code for, within the Linux environment. It is difficult to fully get it right, unless one understands a little bit of the internals. In this write up, I summarize a few points from a couple of good ppts on the topic.

Enjoy the perusal …

MS-Word version of this write-up
 
 

Overview

References

What is a signal

A short message sent by the kernel to a process or group of processes

  • Signal number is present as part of a message
  • Parameters related to the message are allowed in POSIX.4

Details

Signal Transmission

  • Signal sending
    • Kernel updates data structures in the Process Control Block (PCB)
  • Signal receiving
    • Kernel forces the process to handle the signal
  • Pending signals
    • Signals that have been sent, but not yet received
    • Processes/threads can block signals – prevent them from being received.

Hints to handle signals

  • The user process declares how it wants to handle a signal using the data structure
    • The action may be SIG_IGN to ignore the signal, or SIG_DFL for the default action
    • If not ignored or defaulted, the handler is execute
struct sigaction {

  void (*sa_handler)();/* handler address, or SIG_IGN, or SIG_DFL */

  sigset_t sa_mask;    /* blocked signal list */

  int sa_flags; /* options e.g., SA_RESTART */

}

 

Sending signals

  • Signals are sent using one of the following system calls
    • info is 0 if sender is a user-process.
    • Info is 1, if sender is the kernel
send_sig_info(int sig, struct siginfo *info, struct task_struct *t);

kill_proc_info(int sig, struct siginfo *info, pid_t pid);

Receiving Signals

  • Handling is done in entry.S , after handling the system call or interrupt. Called from ret_from_intr()
  • The function handle_signal() calls deque_signal() multiple times, till there are no more signals
  • Each signal that is not ignored or defaulted must be caught.

Receiving signals during a System call

  • handle_signal() is called in the kernel mode, while the handlers are present in the user_mode. This can create problems.
  • A blocked call is placed in TASK_INTERRUPTIBLE state.
  • When a signal arrives, it is woken up in the TASK_RUNNING state to execute the signal handler.
  • After this, the process/task has to be placed back in the old state.
  • This is done, if a flag SA_RESTARTABLE is set as part of sa_flags, while registering the signal handler.

Real-time Signals

  • Real-time signals are sent as part of a signal queue, and are dequeued similarly.
  • To send a real-time signal

int sigqueue(pid_t pid, int sig, const union sigval value);

  • When dequeued, the handler function contains the following data structure.
typedef union sigval {

int sigval_int;

void *sival_ptr;

} sigval_t;

Usage of alarm

alarm(2) is a system call in Linux, that sends a signal after the specified time has expired.  Let us discuss various aspects of integrating the alarm system call, as an example.

Usage

In the example below, the alarm system call is supposed to timeout on a slow system call, such as read

if( signal(SIGALRM, sig_alrm) == SIG_ERR )

{
printf(“signal(SIGALRM) error\n”);
exit(1);
}


alarm(10);
n = read( 0, line, MAXLINE );

Premature alarm expiry

Even though the alarm is set of 10 seconds, which should be adequate time to read the data,

  • there may be cases, because of extreme scheduling issues, where the read may not even be executed.
  • In that case, the alarm would expire, and the read function would execute, unprotected by the alarm.
  • To avoid this, use setjmp and longjmp to ensure restartability of application code.
    • setjmp may be used to save the context of the program, prior to the alarm system call.
    • When the signal for alarm expires, longjmp may be used to set the context to that saved
    • This will execute the alarm call prior to the read.
void sig_alrm(int signo)
/* interrupt the read() and jump to
setjmp() call with value 1
*/
{
longjmp(env_alrm, 1);
}

int main(int argc, char *argv[] )

{

if( signal(SIGALRM, sig_alrm) == SIG_ERR) {
printf(“signal(SIGALRM) error\n”);
exit(1);
}

if( setjmp(env_alrm) != 0 )

{
fprintf(stderr, “\nread() too slow\n”);
exit(2);
}

alarm(10);
n = read(0, line, MAXLINE);

}

Signal handling within a signal handler

  • One more problem remains with the above scenario.
  • If the program has several signal handlers, and the program is inside one of the signal handlers, when the alarm goes off,
    • The longjmp inside the alarm handler will jump to the setjmp location.
    • This may abort the other handler, or corrupt its data
  • To avoid this, sigsetjmp and siglongjmp need to be used, which will work correctly with signals also.

Restartability within a slow system call

  • As mentioned before, we may be in the middle of a system call such as the read, when this signal handler triggers.
  • This system call then has to be restarted.
  • To allow this, the handler should be registered with the SA_RESTART flag

The modified code

void sig_alrm(int signo)
/* interrupt the read() and jump to
setjmp() call with value 1
*/
{
siglongjmp(env_alrm, 1);
}

void catch_int(int signo) {

}

sigfunc *signal( int signo, Sigfunc *func )
{

struct sigaction act, oact;

act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;

act.sa_flags |= SA_INTERRUPT;

// Fill the full set of signals in the mask

sigfillset(&(act.sa_mask));

if( signo != SIGALRM )

act.sa_flags |= SA_RESTART;

/* any system call interrupted by a signal

* other than alarm is restarted */

if( sigaction( signo, &act, &oact) < 0 )
return(SIG_ERR);
return( oact.sa_handler );
}

int main(int argc, char *argv[] )

{

struct sigaction act;

act.sa_handler = catchint;

/* now, SIGINT will cause catchint to be executed */

sigaction( SIGINT, &act, NULL );

sigaction( SIGQUIT, &act, NULL );

act.sa_handler = sig_alrm;

if( sigaction(SIGALRM, &act, NULL) == SIG_ERR) {
printf(“signal(SIGALRM) error\n”);
exit(1);
}

if( sigsetjmp(env_alrm) != 0 )

{
fprintf(stderr, “\nread() too slow\n”);
exit(2);
}

alarm(10);
n = read(0, line, MAXLINE);

}

Re-entrancy

Some re-entrant functions

  • Sometimes the signal handler is called when the main program is in the middle of a function.
  • The same function could get called inside the signal handler as well.
  • In that case, these functions have to be re-entrant.
  • Not all functions are re-entrant

Some re-entrant functions

  • read(), write(), fork()

Some non-reentrant functions

  • malloc() /* The worst offender */
  • scanf(), printf(), strtok() etc

Setting of errno

  • Some signal handlers may call other functions, which may actually set the value of errno.
  • These must be restored when the error handler exits.
  • Otherwise functions could randomly, see their return values changed, if the signal handler was called.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Tag Cloud

%d bloggers like this: