Chapter 11. Signals

Signals act as software interrupts and indicate to the application such asynchronous events as a user pressing the interrupt key on a terminal, a broken pipe between processes, job control functions, and so on. To handle signals, a program can associate a signal handler with a particular signal type. For example, for the interrupt key signal, you can set up a handler that instead of terminating the process, first asks whether the user wishes to terminate. In most cases, your application will want to handle most error signals so that you can either gracefully terminate or retry the current task.

Once the signal is raised, the associated signal handler is invoked in signal context, which is separate the interrupted main execution context. After the signal handler returns, execution continues back in the main context from wherever it happened to be right before the signal was received, as if the interruption never occurred.

Windows provides minimal support for signals for ANSI (American National Standards Institute) compatibility. A minimal set of signals is available, but even fewer are raised by the operating system. Therefore, the usefulness of signals is generally somewhat limited for Windows programmers.

In Chapter 7, we explained how you can use the ACE Reactor framework to handle signal events, along with several other event types. Here, we talk about how to use the signal-handling features of ACE, independent of the Reactor framework. This comes in handy in the following situations.

• You do not want to add the extra complexity of the reactor, as it isn't needed.

The signal-handling actions aren't associated with any of your event handlers.

• You are developing a resource-constrained system in which you cannot afford to waste any memory.

In this chapter, we explain how ACE makes it easy to set up one or more handlers for a signal type. We start by looking at the simple ACE_Sig_Action wrapper, which calls a user-specified handler function when a signal occurs. We then look at the higher-level ACE_Sig_Handler class, which invokes an ACE_Event_Handler::handle_signal() callback when the specified signal occurs. Finally, we talk about the ACE_Sig_Guard class, which allows you to guard certain sections of your code from signal interruptions.

11.1 Using Wrappers

The POSIX sigaction() call enables a programmer to associate an action, such as the execution of a callback function, with the occurrence of a particular signal. ACE provides a wrapper around the sigaction() call. This wrapper provides a type-safe interface for signal registration. ACE also provides a type-safe wrapper to one of its argument types, sigset_t. This type represents a collection of signals and is discussed in detail in Section 7.3.2.

The following example illustrates the use of wrappers to register callback functions for a few signals:


#include "ace/Signal.h"

// Forward declarations.
static void my_sighandler (int signo);
static void register_actions ();

int ACE_TMAIN (int, ACE_TCHAR *[])
{
  ACE_TRACE (ACE_TEXT ("::main"));

  ::register_actions ();    // Register actions to happen.

  // This will be raised immediately.
  ACE_OS::kill (ACE_OS::getpid(), SIGUSR2);

  // This will pend until the first signal is completely
  // handled and returns, because we masked it out
  // in the registerAction call.

  ACE_OS::kill (ACE_OS::getpid (), SIGUSR1);

  while (ACE_OS::sleep (100) == -1)
    {
      if (errno == EINTR)
        continue;
      else
        ACE_OS::exit (1);
    }
  return 0;
}

When we enter the program, we first register actions, using the ACE-provided ACE_Sig_Action class. Then we explicitly raise certain signals, using the ACE_OS::kill() method. In this case, we are asking for SIGUSR2 to be sent to the current process, followed by SIGUSR1; both of these signal types are meant to be user definable.

Our signal handler function follows:


static void my_sighandler (int signo)
{
  ACE_TRACE (ACE_TEXT ("::my_sighandler"));

  ACE_OS::kill (ACE_OS::getpid (), SIGUSR1);

  if (signo == SIGUSR1)
    ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("Signal SIGUSR1 ")));
  else
    ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("Signal SIGUSR2 ")));

  ACE_OS::sleep (10);
}

Note the signature of the function. It returns void and is passed an integer argument representing the signal that occurred. Under the current model for signals, only one signal handler function can be associated for any particular signal number. This means that you can't have multiple functions be called back when a single signal is raised. You can, of course, associate the same signal handler function for multiple signals, as we have done in this example.

In this particular handler, once a signal is received, we sleep for 10 seconds before returning to regular program execution. Never do something like this in actual programs. In general, signal handler functions should be very light and should return immediately; otherwise, you will block execution of the rest of your program. Furthermore, the actions allowed in signal context are restricted; check your OS documentation for details. Signal handler functions are generally used to indicate to the application the occurrence of a signal.

The work that must be done owing to the signal occurs after the signal handler has returned. One way to do this is to use the ACE_Reactor notification mechanism (see Section 7.4) to transfer control back to normal execution context.

The following code shows how we register our signal handler:


static void register_actions ()
{
  ACE_TRACE (ACE_TEXT ("::register_actions"));

  ACE_Sig_Action sa (my_sighandler);

  // Make sure we specify that SIGUSR1 will be masked out
  // during the signal handler's execution.
  ACE_Sig_Set ss;
  ss.sig_add (SIGUSR1);
  sa.mask (ss);

  // Register the same handler function for these
  // two signals.
  sa.register_action (SIGUSR1);
  sa.register_action (SIGUSR2);
}

During registration, we create an ACE_Sig_Action object on the stack, passing in the address of a signal handler function that will be called back. We don't in fact register for the callback in the constructor. Instead, we defer that and do a series of register_action() calls, registering our callback function for SIGUSR1 and SIGUSR2.

Before registration, we also create an ACE_Sig_Set container and add SIGUSR1 to it. We then add this set of signals as a mask to the our sa object. When the sa object registers its action, it passes in this mask to the OS, informing it to automatically disable these signals during the execution of the signal handler function. This is why even though we explicitly raise SIGUSR1 during the execution of the signal handler, we don't see the signal handler being executed for SIGUSR1 until after the handler returns.

Be careful that you specify the mask before you register your action with the OS using the register_action() method. Otherwise, the OS will be unaware of this specification.

Note that you do not need to keep the ACE_Sig_Action object around after you register the action you want for a particular signal. If you wish to change the disposition of a certain signal you set up previously, you can create another ACE_Sig_Action object and do this.

You probably noticed the strange, errno-checking loop that we executed around the ACE_OS::sleep() in the main function of the previous example. The reason we needed to do this is that certain blocking system calls, such as read(), write(), and ioctl(), become unblocked on the occurrence of a signal. Once these calls become unblocked, they return with errno set to EINTR, or interrupted by a signal.

This can be painful to deal with in programs that make use of signals. Therefore, most implementations that provide the sigaction() function allow you to specify a flag, SA_RESTART, to cause signal-interrupted calls to automatically continue transparent to the programmer. Be careful though; every version of UNIX and UNIX-like operating systems deals differently with which calls are and are not restarted. Check your OS documentation for details.

In general, however, most SVR4 UNIX platforms provide sigaction() and the SA_RESTART flag. Using ACE, you can call the ACE_Sig_Action::flags() method to set SA_RESTART, or you can pass it in as an argument to the constructor of an ACE_Sig_Action object.

11.2 Event Handlers

Bundled above the type-safe C++ wrappers for sigaction and sigset_t is an object-oriented event handler-based signal registration and dispatching scheme. This is available through the ACE_Sig_Handler class, which allows a client programmer to register ACE_Event_Handler-based objects for callback on the occurrence of signals. The ACE_Event_Handler type is central to the ACE Reactor framework, but there is no registration with a reactor when used with ACE_Sig_Handler.

As client programmers, we must subclass from ACE_Event_Handler and implement the callback method handle_signal():



class MySignalHandler : public ACE_Event_Handler
{
public:
  MySignalHandler (int signum) : signum_(signum)
  { }

  virtual ~MySignalHandler()
  { }

  virtual int handle_signal (int signum,
                             siginfo_t * = 0,
                             ucontext_t * = 0)
  {
    ACE_TRACE (ACE_TEXT ("MySignalHandler::handle_signal"));

    // Make sure the right handler was called back.
    ACE_ASSERT (signum == this->signum_);

    ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("%S occured "), signum));
    return 0;
  }

private:
  int signum_;
};

We start by creating a signal handler class that publicly derives from the ACE_Event_Handler base class. We need to implement only the handle_signal() method here, as our event handler can handle only “signal”-based events. The siginfo_t and ucontext_t are also passed back to the handle_signal() method, in addition to the signal number, when the signal event occurs. This is in accordance with the new sigaction() signature; we will discuss the siginfo_t and ucontext_t types in Section 11.2.1 and Section 11.2.2, respectively.


int ACE_TMAIN (int, ACE_TCHAR *[])
{
  MySignalHandler h1 (SIGUSR1), h2 (SIGUSR2);
  ACE_Sig_Handler handler;
  handler.register_handler (SIGUSR1, &h1);
  handler.register_handler (SIGUSR2, &h2);

  ACE_OS::kill (ACE_OS::getpid (), SIGUSR1);
  ACE_OS::kill (ACE_OS::getpid (), SIGUSR2);


  int time = 10;
  while ((time = ACE_OS::sleep (time)) == -1)
    {
      if (errno == EINTR)
        continue;
      else
        {
          ACE_ERROR_RETURN ((LM_ERROR,
                             ACE_TEXT ("%p "),
                             ACE_TEXT ("sleep")), -1);
        }
    }
  return 0;
}

After the program starts, we create two signal event handler objects on the stack and register them to be called back for different signals. Registration occurs using the ACE_Sig_Handler::register_handler() method. This method accepts a signal number and a pointer to an ACE_Event_Handler object that you want to be called back when the signal occurs. You can also pass in a new ACE_Sig_Action that contains the mask and flags you want set up for the action associated with the signal you are currently registering for. This method can also be used to return the old event handler and old ACE_Sig_Action struct.

After we register our event handlers to be called back, we raise two signals that are caught in their respective event handlers. After sleeping until the signals have been handled, exit the program.

11.2.1 Introducing siginfo_t

Thus far, we have ignored the siginfo_t parameter of the handle_signal() method. This parameter contains more information about the causes and attributes of the signal that is being delivered. To illustrate what siginfo_t provides, we touch on a few examples here and encourage you to study the sigaction() man page for more detail on the data available in the siginfo_t structure.

The meaning of the information in the siginfo_t structure varies, depending on the signal caught. In our next example, we switch on the value of the signal to determine what information is relevant.

We begin our signal-handling method by using the %S format specifier to display a description of the signal received. If the siginfo_t structure was not provided, we exit:


int handle_signal (int signum,
                   siginfo_t * siginfo = 0,
                   ucontext_t * = 0)
{
  ACE_DEBUG ((LM_INFO, ACE_TEXT ("Received signal [%S] "),
              signum));
  if (siginfo == 0)
    {
      ACE_DEBUG ((LM_INFO,
                  ACE_TEXT ("No siginfo_t available for ")
                  ACE_TEXT ("signal [%S] "),
                  signum));
      return 0;
    }

Now, si_signo, si_errno, and si_code are available for all signals received. We already printed the signal number but now will print the errno value. We also print the process ID and user ID that sent the signal. These values are valid only for POSIX.1b signals, but our simple example doesn't make that distinction.

The si_code member gives us information about why the signal was sent. Some values are signal specific, and others are relevant to all signals. Here, we look at some of this latter set and print a description of the reason code:


ACE_DEBUG ((LM_INFO,
            ACE_TEXT ("errno for this signal is %d [%s] "),
            siginfo->si_errno,
            strerror (siginfo->si_errno)));
ACE_DEBUG ((LM_INFO,
            ACE_TEXT ("signal was sent by process %d")
            ACE_TEXT (" / user %d "),
            siginfo->si_pid,
            siginfo->si_uid));

switch (siginfo->si_code)
  {
  case SI_TIMER:
    ACE_DEBUG ((LM_INFO, ACE_TEXT ("Timer expiration ")));
    break;

  case SI_USER:
    ACE_DEBUG ((LM_INFO,
                ACE_TEXT ("Sent by kill, sigsend or raise ")));
    break;

  case SI_KERNEL:
    ACE_DEBUG ((LM_INFO,
                ACE_TEXT ("Sent by kernel ")));
    break;
    // ...
  };

As our example continues, we now have to inspect the signal value before we can determine which parts of siginfo_t are applicable. The si_address attribute, for instance, is valid only for SIGILL, SIGFPE, SIGSEGV, and SIGBUS. For SIGFPE (floating-point exception), we will display a description of si_code along with si_address, indicating both why the signal was raised and the memory location that raised it:


switch (signum)
  {
  case SIGFPE:
    switch (siginfo->si_code)
      {
      case FPE_INTDIV:
      case FPE_FLTDIV:
        ACE_DEBUG ((LM_INFO,
                    ACE_TEXT ("Divide by zero at %@ "),
                    siginfo->si_addr));
        break;

      case FPE_INTOVF:
      case FPE_FLTOVF:
        ACE_DEBUG ((LM_INFO,
                    ACE_TEXT ("Numeric overflow at %@ "),
                    siginfo->si_addr));
        break;

      // ...
      }
    break;

As our outer switch() continues, we may also display similar details for SIGSEGV (segmentation violation):


case SIGSEGV:
  switch (siginfo->si_code)
    {
      // ...
    };
  break;

Our rather lengthy handle_signal() concludes by displaying the appropriate siginfo_t attributes when our process receives notification of a child process's termination:


case SIGCHLD:
  ACE_DEBUG ((LM_INFO,
              ACE_TEXT ("A child process has exited ")));
  ACE_DEBUG ((LM_INFO,
              ACE_TEXT ("The child consumed %l/%l time "),
              siginfo->si_utime,
              siginfo->si_stime));
  ACE_DEBUG ((LM_INFO,
              ACE_TEXT ("and exited with value %d "),
              siginfo->si_status));
  break;
  // ...
}

In our main() function, we create a full signal set so that we can listen for all signals. We also add a small snippet of code that will create a short-lived child process, allowing us to test our SIGCHLD-handling code:


ACE_Sig_Set signalSet;
signalSet.fill_set ();

MySignalHandler h1;
ACE_Reactor::instance ()->register_handler (signalSet, &h1);
pid_t childPid = ACE_OS::fork ();
if (childPid == 0)      // This is the parent process.
  {
    // ...
  }
ACE_Reactor::instance ()->run_reactor_event_loop ();

We have not shown all attributes of siginfo_t here. For information about these, as well as which attributes apply to each signal, refer to the sigaction documention of your operating system.

11.2.2 Introducing ucontext_t

The final parameter of the handle_signal() method is ucontext_t. This structure contains the application's execution context, such as CPU state and FPU (floating-point unit) registers, from just before the signal was raised. A thorough discussion of this topic is very much beyond the scope of this chapter. Consult the operating system and vendor documentation for details on the exact content and manner of interpreting the ucontext_t structure.

11.2.3 Stacking Signal Handlers

In certain cases, you may want to register more than one signal handler function for a particular signal event. In previous sections, we mentioned that the POSIX model of signals allows only a single handler to be called back when a signal occurs. You can use the ACE_Sig_Handlers class to modify this behavior. This class provides for the stacking of signal handler objects for a particular signal event. By making a simple modification to the previous example, we get:


int ACE_TMAIN (int, ACE_TCHAR *[])
{
  MySignalHandler h1 (SIGUSR1), h2 (SIGUSR1);
  ACE_Sig_Handlers handler;
  handler.register_handler (SIGUSR1, &h1);
  handler.register_handler (SIGUSR1, &h2);

  ACE_OS::kill (ACE_OS::getpid (), SIGUSR1);

  int time = 10;
  while ((time = ACE_OS::sleep (time)) == -1)
    {
      if (errno == EINTR)
        continue;
      else
        ACE_ERROR_RETURN ((LM_ERROR, ACE_TEXT ("%p "),
                           ACE_TEXT ("sleep")), -1);
    }
  return 0;
}

The only thing we do differently here is create an ACE_Sig_Handlers object instead of an ACE_Sig_Handler object. This allows us to register multiple handlers for the same signal number. When the signal occurs, all handlers are automatically called back.

11.3 Guarding Critical Sections

Signals act a lot like asynchronous software interrupts. These interrupts are usually generated external to the application—not the way we have been generating them, using kill(), to run our examples. When a signal is raised, the thread of control in a single-thread application—or one of threads in a multithreaded application—jumps off to the signal-handling routine, executes it, and then returns to regular processing. As in all such scenarios, certain small regions of code can be treated as critical sections, during the execution of which you do not want to be interrupted by any signal.

ACE provides a scope-based signal guard class that you can use to disable or mask signal processing during the processing of a critical section of code, although some signals, such as SIGSTOP and SIGKILL, cannot be masked:


class MySignalHandler : public ACE_Event_Handler
{
public:
  virtual int handle_signal (int signo,
                             siginfo_t * = 0,
                             ucontext_t * = 0)
  {
    ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("Signal %d "), signo));
    return 0;
  }
};

int ACE_TMAIN (int, ACE_TCHAR *[])
{
  MySignalHandler sighandler;
  ACE_Sig_Handler sh;
  sh.register_handler (SIGUSR1, &sighandler);

  ACE_Sig_Set ss;
  ss.sig_add (SIGUSR1);
  ACE_Sig_Guard guard (&ss);
  {
    ACE_DEBUG ((LM_DEBUG,
                ACE_TEXT ("Entering critical region ")));
    ACE_OS::sleep (10);
    ACE_DEBUG ((LM_DEBUG,
                ACE_TEXT ("Leaving  critical region ")));
  }

  // Do other stuff.

  return 0;
}

Here, an ACE_Sig_Guard is used to protect the critical region of code against all the signals that are in the ACE_Sig_Set ss. In this case we have added only the signal SIGUSR1. This means that the scoped code is “safe” from SIGUSR1 until the scope closes.

To test this, we ran this program and used the UNIX kill(1) utility to send the process SIGUSR1 (signal number 16):


$ SigGuard&
[1]         15191
$ Entering critical region
$kill 16 15191
$ Leaving critical region
$ Signal   16
[1]   +   Done                          SigGuard&

Note that even though we sent the signal SIGUSR1 before the program had left the scope block of the critical region, the signal was received and processed after we had left the critical region. This happens because while in the critical region, we were guarded against SIGUSR1, and thus the signal remains pending on the process. Once we leave the critical section scope, the guard is released and the pending signal delivered to our process.

11.4 Signal Management with the Reactor

As we mentioned in the beginnning of this chapter, the reactor is a general-purpose event dispatcher that can handle the dispatching of signal events to event handlers in a fashion similar to ACE_Sig_Handler. In fact, in most reactor implementations, ACE_Sig_Handler is used behind the scenes to handle the signal dispatching. It is important to remember that signal handlers are invoked in signal context, unlike the other event types that are handled by the Reactor framework. You can, however, transfer control back to a defined point of control in user context using reactor notifications. For more on this topic, see Section 7.4.

11.5 Summary

In this chapter, we looked at how signals, which are asynchronous software interrupts, are handled portably with ACE. We explained how to use the low-level sigaction and sigset_t wrappers to set up our own signal handler functions. We then discussed how ACE supports object-based callbacks with the ACE_Event_Handler abstract base class. Stacking signal handlers for a single signal number was also introduced. Finally, we showed you how to protect regions of code from being interrupted by signals by using the ACE_Sig_Guard class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.216.174