Symbian
Symbian Developer Library

SYMBIAN OS V9.4

Feedback

[Index] [Previous] [Next]


POSIX Signals

[Top]


Introduction

A POSIX signal is the software equivalent of an interrupt. As part of the configuration for signal handling, a signal handler (that is, a function) will have been registered against a given signal. On receipt of that signal the operating system stops the process at its point of execution and executes the signal handler. If the signal does not result in killing the process, it will continue to execute from the same point following the execution of the signal handler.

[Top]


Signal handling in a Unix-like environment

Signals can be sent to a process/thread at any time and will be handled immediately, unless

The kernel/user barrier provides a 'natural' mechanism to determine when it's safe to interrupt a process.

User space library functions must do one of three things:

  1. Be reentrant, allowing signals to be handled with no issues

  2. Block signals during the function, preventing reentrance

  3. Document that the function cannot be used from a signal handler, and hope that the client obeys this.

Library code is the only major issue when programming with signals in UNIX (assuming that the client code itself doesn't do anything unsafe). However, the vast majority of library code is either innately reentrant, or has been made so as a side effect of being thread-safe. Very few well-written libraries have any problems; the kernel's natural protection of system calls renders most things safe to be interrupted.

[Top]


Signal handling in the P.I.P.S. environment

Typically, any OS kernel could function as a signal barrier in much the same way as in UNIX, that is, it could

  1. not handle signals until leaving the kernel, and

  2. introduce a new interruptible-sleep state, and choose which places this needs to be used.

However, the Symbian OS kernel does not provide as many services as a Unix-like kernel. Many important services are accessed via client/server IPC, for example, file server, socket server and so forth.

Thus, many POSIX library functions that would, on UNIX, map to system calls are built using IPC calls and user space operations under P.I.P.S.. These have no innate protection against being interrupted by signals, except when actually inside the kernel to send/receive IPC messages. This is a problem because many coding idioms in Symbian OS, especially IPC, are not reentrant, even if they are thread-safe. Therefore P.I.P.S. functions are likely to be unsafe in the presence of signals. This is best explained with an example.

The following code shows a possible implementation of the read() function.

ssize_t read(int fd, void *buf, size_t count) {
    RFile *file = <RFile corresponding to fd>;
    TPtr8 des(buf, count);
    TInt r = file->Read(des);
    if (r == KErrNone)
        return des.Length();
    else
        return -1; //should also set errno
}

On first impression this looks, and appears to be reentrant. However, read() is a synchronous IPC call. Unwrapping the read() yields the code below.

ssize_t read(int fd, void *buf, size_t count) {
    RFile *file = <RFile corresponding to fd>;
    TPtr8 des(buf, count);
    TRequestStatus s = KRequestPending;
    TInt r = Exec::SessionSendSync(handle,EFileRead,args,&s);
    if (r == KErrNone)
        {
        User::WaitForRequest(s);
        r=s.Int();
        }
    if (r == KErrNone)
        return des.Length();
    else
        return -1; //should also set errno
}

Imagine that the client executing this code receives a signal while it's in the middle of running Exec::SessionSendSync(). This is a kernel function and thus will not be interrupted. The signal will fire on the return to user space before the call to User::WaitForRequest(). This will be handled correctly unless the signal handler happens to make a synchronous file server call. Exec::SessionSendSync() uses a single, per-thread message object to store the message. This is safe under normal circumstances because a thread can only be running one synchronous call at a time. When the signal handler comes to access a file, the message object for the current thread will already be in use, and the kernel will panic the client.

[Top]


Signal usage in UNIX

Signals can be used for a number of purposes, which are detailed below.


Exception handling

An exception can occur on a page fault, a divide by zero exception, and so on. These are synchronously generated signals because they happen in immediate, or near immediate, response to something that the process itself does. A signal handler in this case would traditionally do some cleaning up, for example, cleaning the computer monitor before allowing the process to terminate.

P.I.P.S. Alternative

If a signal handler is present to catch any exception signal it is unlikely to do anything more than some basic cleaning up before stopping the process.

Currently in P.I.P.S. there is no mechanism to support this feature. If you can manage without the explicit clean up, any such signal handling functionality should be removed.


Abnormal process termination

Abnormal process termination occurs when an external agent to a process sends an event to that process to stop it, for example, when a user types <CTRL-C> at the keyboard. This is an asynchronous generated signal because it is something which has interrupted the normal execution of the process.

P.I.P.S. Alternative

The situation is very much like exception handling, that is, the signal handler is unlikely to do very much. In addition, when executing in a Symbian environment no such mechanism exists for user interaction.

In P.I.P.S., currently there is no mechanism to support this feature. If you can manage without the explicit clean up, any such signal handling functionality should be removed.


Asynchronous events

Like the abnormal process termination case, an asynchronous event has occurred which is passed to the process through a signal. Examples of such are described in the following subsections:

Timer expiration

In a Unix-like environment timers are implemented using the SIGALRM signal. An example of this is shown in the following code. Note that the main loop uses select() in an infinite loop while it waits to read data from a FIFO file descriptor. Note also that this example omits the clearing up of the FIFO file if it is interrupted by a <CTRL-C> from the user.

Timers using SIGALRM Signal

The following example code illustrates how timers are implemented using the SIGALRM signal:

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>

const  char fifoFileName[] = "/root/PortDoc/Example4_c/fifofile";
void timer_signal_handler(int SigNum,siginfo_t *extra, void *Ignore)
{
   printf("Parent Timer Handler\n");
}

int SetUpTimerHandler(void)
{
   //set up the signal handler for the timer expiries
   struct sigaction sa;        
        
   //Set up the signal handler
   sa.sa_flags = SA_SIGINFO; //Use sigaction as the handler field rather than sa_handler
   sa.sa_sigaction = timer_signal_handler;
   
   //Set up signal handler for SIGALRM 
   if(sigaction(SIGALRM,&sa,NULL) != 0)
   {
      printf("\r\n*** sigaction failure ***\r\n");
      
      return EXIT_FAILURE;
   }   
   else
   {
      //create a timer. Use SIGALARM as the timer expiry signal
      timer_t createdTimer;
       
      if(timer_create(CLOCK_REALTIME,NULL,&createdTimer) != 0)
      {
         printf("\r\n*** timer_create failure ***\r\n");
        
         return EXIT_FAILURE;
      }
      else
      {       
         //Start the timer. First timer to expire after 5 sec, and then continually 
         //for 5 sec afterwards.
         struct itimerspec TimerData;
         TimerData.it_value.tv_sec = 5;
         TimerData.it_value.tv_nsec = 0;
         TimerData.it_interval.tv_sec = 5;
         TimerData.it_interval.tv_nsec = 0;
         
         if(timer_settime(createdTimer,0,&TimerData,NULL) != 0)
         {
            printf("\r\n*** timer_settime failure ***\r\n");
        
            return EXIT_FAILURE;
         }
      }
   }
   return EXIT_SUCCESS;
}//end SetUpTimerHandler

int CreateMyFifo(void)
{
   //Create the FIFO
   int fifoResult = mkfifo(fifoFileName,S_IXGRP);
  
   if(fifoResult == -1)
   {
      //FIFO creation failure.
      printf("\n*** failure mkfifo ***\n");

      return EXIT_FAILURE;
   }
   else
   {
      return EXIT_SUCCESS;
   }
}//CreateMyFifo

int DoMyProcessing(void)
{
   //Open the FIFO. Parent reads from the FIFO
   int ReadFifoFd = open(fifoFileName,O_RDONLY);
  
   if(ReadFifoFd == -1)
   {
      //Failed to open the Fifo
      printf("\n*** failure Fifo Open ***\n");

      return EXIT_FAILURE;
   }
   else
   {
      //create a receive buffer and clear
      char RxBuffer[100];
      memset(RxBuffer,0,sizeof(RxBuffer));
      int NumActivedescriptors;
      
      fd_set readset;
      FD_ZERO(&readset);

      while(1)
      {
         //Note the setting of the file descriptor set before each call into select()
         FD_SET(ReadFifoFd,&readset);

         //Wait for activity on the read set
         NumActivedescriptors = select(ReadFifoFd+1,&readset,NULL,NULL,NULL);
 
         if(NumActivedescriptors > 0)
         {
            if(FD_ISSET(ReadFifoFd,&readset))
            {
               //Read the data out.
               int nbytes = read(ReadFifoFd,RxBuffer,sizeof(RxBuffer));

               printf("\nMessage Received by Parent=%s",RxBuffer);
            }
         }
      }
   }
}//end DoMyProcessing

int main(int argc, char *argv[])
{
   if(SetUpTimerHandler() != EXIT_SUCCESS)
   {
      return EXIT_FAILURE;
   }
   else
   {
      if(CreateMyFifo() != EXIT_SUCCESS)
      {
         return EXIT_FAILURE;
      }
      else
      {
         //create chid process
         pid_t Childpid = fork();
  
         if(Childpid == 0)
         {
            //exec() to replace the child process executable
            char execFileName[] = "/root/PortDoc/Example4_c/ChildProg";
            execl(execFileName,NULL);
         }
         else
         {
            if(DoMyProcessing() != EXIT_SUCCESS)
            {
               return EXIT_FAILURE;
            }
         }
      }
   }//setup Timer Handler
   
   return EXIT_SUCCESS;
   
}//end main
Timers with P.I.P.S.

Due to the lack of signal support in P.I.P.S. the above code example needs to use an alternative strategy to support timers. The following code example uses the select() API, but with the timeout parameter defined.

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>

//Timer Duration macros
#define TIMER_SEC_OFFSET       5
#define TIMER_NSEC_OFFSET      0

//Timer expiry macros
#define TIMER_EXPIRED     1
#define TIMER_NOT_EXPIRED 0

const  char fifoFileName[] = "/root/PortDoc/Example4_c/fifofile";

void HandleTimerExpiry(void)
{
   printf("Parent Timer Handler\n");
}//end HandleTimerExpiry

int TimerExpired(struct timespec *CurrentTimePtr,struct timespec *ExpiryTimePtr)
{
   if(CurrentTimePtr->tv_sec < ExpiryTimePtr->tv_sec)
   {
      return TIMER_NOT_EXPIRED;
   }
   else if(CurrentTimePtr->tv_sec > ExpiryTimePtr->tv_sec)
   {
      return TIMER_EXPIRED;
   }
   //the tv_sec values are the same. 
   else if(CurrentTimePtr->tv_nsec > ExpiryTimePtr->tv_nsec)
   {
      return TIMER_EXPIRED;
   }
   else
   {
      // Expiry time is the same, or is later than the CurrentTime 
      // if the difference between the tv_nsec fields is less than 1usec, i.e. 1000nsecs, then 
      // the timer has expired.
      int TimeDiff = ExpiryTimePtr->tv_nsec - CurrentTimePtr->tv_nsec;
      if(TimeDiff < 1000)
      {
         return TIMER_EXPIRED;
      }
      else
      {
         return TIMER_NOT_EXPIRED;
      }
   }
}//end HandleTimerExpiry

int CreateMyFifo(void)
{
   //Create the FIFO
   int fifoResult = mkfifo(fifoFileName,S_IXGRP);
  
   if(fifoResult == -1)
   {
      //FIFO creation failure.
      printf("\n*** failure mkfifo ***\n");

      return EXIT_FAILURE;
   }
   else
   {
      return EXIT_SUCCESS;
   }
}//end CreateMyFifo

int DoMyProcessing(void)
{
   //Open the FIFO. Parent reads from the FIFO
   int ReadFifoFd = open(fifoFileName,O_RDONLY);
  
   if(ReadFifoFd == -1)
   {
      //Failed to open the Fifo
      printf("\n*** failure Fifo Open ***\n");

      return EXIT_FAILURE;
   }
   else
   {
      //create a receive buffer and clear
      char RxBuffer[100];
      memset(RxBuffer,0,sizeof(RxBuffer));
      int NumActivedescriptors;
      
      //Unfortunately select() uses timeval structure, and timer_gettime uses timespec structure.
      struct timeval MyTimer;
      
      //Expiry Time is used calculate the actual time of the timer expiry. 
      struct timespec CurrentTime, ExpiryTime;
      
      if(clock_gettime(CLOCK_REALTIME,&CurrentTime) != 0)
      {
         printf("\r\n clock_gettime() Error 1 \r\n");
 
         return EXIT_FAILURE;
      }
      else
      {
         ExpiryTime.tv_sec = CurrentTime.tv_sec + TIMER_SEC_OFFSET;
         ExpiryTime.tv_nsec = CurrentTime.tv_nsec + TIMER_NSEC_OFFSET;
 
         MyTimer.tv_sec = ExpiryTime.tv_sec;
         MyTimer.tv_usec = ExpiryTime.tv_nsec / 1000;
 
         fd_set readset;
         FD_ZERO(&readset);

         while(1)
         {
            //Note the setting of the file descriptor set before each call into select()
            FD_SET(ReadFifoFd,&readset); 
            
            //Wait for activity on the read set. Note the use of the timeout field.
            NumActivedescriptors = select(ReadFifoFd+1,&readset,NULL,NULL,&MyTimer);
 
            if(NumActivedescriptors > 0)
            {
               if(FD_ISSET(ReadFifoFd,&readset))
               {
                  //Read the data out.
                  int nbytes = read(ReadFifoFd,RxBuffer,sizeof(RxBuffer));

                  printf("\nMessage Received by Parent=%s",RxBuffer);
               }
            }
    
            //Read the current time to see if it is the same or after the timer expiry.
            if(clock_gettime(CLOCK_REALTIME,&CurrentTime) != 0)
            {
               printf("\r\n clock_gettime() Error 2 \r\n");
 
               return EXIT_FAILURE;
            }
   
            if(TimerExpired(&CurrentTime,&ExpiryTime) == TIMER_EXPIRED)
            {
               //Call the timer handler
               HandleTimerExpiry();
       
               //Restart the timer.
               ExpiryTime.tv_sec = CurrentTime.tv_sec + TIMER_SEC_OFFSET;
               ExpiryTime.tv_nsec = CurrentTime.tv_nsec + TIMER_NSEC_OFFSET;
 
               MyTimer.tv_sec = ExpiryTime.tv_sec;
               MyTimer.tv_usec = ExpiryTime.tv_nsec / 1000;
            }
            else
            {
               //recalculate the MyTimer value.
               MyTimer.tv_sec = ExpiryTime.tv_sec - CurrentTime.tv_sec;
               MyTimer.tv_usec = (ExpiryTime.tv_nsec - CurrentTime.tv_nsec)/1000;
            }
         }
      } 
   }
}//end DoMyProcessing

int main(int argc, char *argv[])
{
   if(CreateMyFifo() != EXIT_SUCCESS)
   {
      return EXIT_FAILURE;
   }
   else
   {
      //create child process
      pid_t Childpid;
      char execFileName[] = "/root/PortDoc/Example4_c/ChildProg";
       
      int RetVal= posix_spawn(&Childpid,execFileName,NULL,NULL,NULL,NULL);
      
      if(RetVal != 0)
      {
         printf("\n*** failure posix_spawn ***\n");

         return EXIT_FAILURE;
      }
      else
      {
         if(DoMyProcessing() != EXIT_SUCCESS)
         {
            return EXIT_FAILURE;
         }
      }
   }
   return EXIT_SUCCESS;
}//end main

Note that if code which is to be ported to P.I.P.S. does not use select(), but instead blocks on the read() or write() on an individual file descriptor, then it will need to be modified to use select() with the timeout parameter defined.

Asynchronous I/O

Asynchronous I/O works by effectively multi-threading an application. The initiation of the I/O is achieved by calling the relevant API, for example, aio_read() and aio_write(), which initiate the I/O request. Once the I/O has completed the OS will send a signal to the relevant process, which is caught by a relevant signal handler.

P.I.P.S.'s lack of support for signals means that asynchronous I/O is not supported (if this functionality is needed, use Symbian OS APIs). Therefore I/O must be carried out in a synchronous manner.

The recommendation is to delegate the I/O to a separate thread within the same process. This separate thread can perform the I/O in a synchronous manner, and on completion inform the I/O requester as appropriate.

Asynchronous I/O with Signals

The following code is an example of where asynchronous I/O can be replaced by the use of Pthreads. The example periodically requests an asynchronous read of a file.

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include <aio.h>

#define TIMER_SEC_OFFSET     1
#define TIMER_USEC_OFFSET    0

//char buffer used to receive asynch read() data.
char RxBuffer[10];

//Asynchronous IO control block
struct aiocb myaiocb;

void io_signal_handler(int SigNum,siginfo_t *extra, void *Ignore)
{
   (void)aio_return(&myaiocb);
   
   printf("Parent IO Handler\n");
}

int SetUpReadIOHandler(void)
{
   //set up the signal handler for the timer expiries
   struct sigaction sa;
          
   //Set up the signal handler
   sa.sa_flags = SA_SIGINFO; //Use sigaction as the handler field rather than sa_handler
   sa.sa_sigaction = io_signal_handler;
   
   //Set up signal handler for SIGIO 
   if(sigaction(SIGIO,&sa,NULL) != 0)
   {
      printf("\r\n*** sigaction failure ***\r\n");

      return EXIT_FAILURE;
   }   

   return EXIT_SUCCESS;
}//end SetUpReadIOHandler

void DoAsynchRead(int ReadFd)
{
   int retval;
   bzero(&myaiocb,sizeof(myaiocb));
   myaiocb.aio_fildes = ReadFd;
   myaiocb.aio_offset = 0;
   myaiocb.aio_buf = (void *)RxBuffer;
   myaiocb.aio_nbytes = sizeof(RxBuffer);
   myaiocb.aio_sigevent.sigev_signo = SIGIO;
   myaiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;

   retval = aio_read(&myaiocb);

}//DoAsynchRead

int DoMyProcessing(void)
{
   const char FileName[] = "/root/PortDoc/Example5_c/file.txt";
   
   //Open the input file for reading.
   int ReadFd = open(FileName,O_RDONLY);
  
   if(ReadFd == -1)
   {
      //Failed to open the file
      printf("\n*** failure File Open ***\n");

      return EXIT_FAILURE;
   }
   else
   {
      struct timeval MyTimer;
      
      while(1)
      {
         MyTimer.tv_sec = TIMER_SEC_OFFSET;
         MyTimer.tv_usec = TIMER_USEC_OFFSET;

         DoAsynchRead(ReadFd);
 
         //Use select() for sleeping
         (void)select(0,NULL,NULL,NULL,&MyTimer);
      }
   }
}//end DoMyProcessing

int main(int argc, char *argv[])
{
   if(SetUpReadIOHandler() != EXIT_SUCCESS)
   {
      return EXIT_FAILURE;
   }
   else
   {
      if(DoMyProcessing() != EXIT_SUCCESS)
      {
         return EXIT_FAILURE;
      }
   }
   return EXIT_SUCCESS;
}//end main
Asynchronous I/O with P.I.P.S.
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

#define TIMER_SEC_OFFSET     1
#define TIMER_USEC_OFFSET    0

//char buffer used to receive asynch read() data.
char RxBuffer[10];

sem_t AsynchIOSem;

//This function is a pseudo Signal handler, since signals are not used for I/O completion.
void io_signal_handler(void)
{
   printf("Parent IO Handler\n");
}//io_signal_handler

void DoAsynchRead(void)
{
   //give the semaphore
   (void)sem_post(&AsynchIOSem);
}//DoAsynchRead

//This function operates on a different thread to the main processing thread.
//It blocks on a semaphore whilst it waits for requests to do a synchronise read.
void AsynchReadHandler(void)
{
   //Open FILE for Asynch Read Requests
   const char FileName[] = "/root/PortDoc/Example5_c/file.txt";

   int ReadFd = open(FileName,O_RDONLY);
   
   if(ReadFd == -1)
   {
      //Need to do some cleaning up. Omitted for clarity.
      printf("AsynchReadHandler FILE Error\n");
   }
   else
   {
      while(1)
      {
         //Wait for activity send request via the Asynch semaphore. 
         (void)sem_wait(&AsynchIOSem);
 
         //Do the synchonous read.
         int nbytes = read(ReadFd,RxBuffer,sizeof(RxBuffer));
         io_signal_handler();
      }
   }
}//AsynchReadHandler

int SetupAsynchIOHandler(void)
{
   pthread_t thread_id;
   
   //Initialise the semaphore used for synching the asynch IO thread and the main thread
   int RetVal = sem_init(&AsynchIOSem,0,0);
   
   //create a new thread
   int retval = pthread_create(&thread_id,NULL,AsynchReadHandler,(void *)NULL);
   
   if(retval != 0)
   {
      return EXIT_FAILURE;
   }
   else
   {
      return EXIT_SUCCESS;
   }
}//SetupAsynchIOHandler

int DoMyProcessing(void)
{
   struct timeval MyTimer;
   while(1)
   {
      MyTimer.tv_sec = TIMER_SEC_OFFSET;
      MyTimer.tv_usec = TIMER_USEC_OFFSET;

      DoAsynchRead();
 
      //Use select() for sleeping
      (void)select(0,NULL,NULL,NULL,&MyTimer);
   }
}//end DoMyProcessing

int main(int argc, char *argv[])
{
   if(SetupAsynchIOHandler() != EXIT_SUCCESS)
   {
      return EXIT_FAILURE;
   }
   else
   {
      if(DoMyProcessing() != EXIT_SUCCESS)
      {
         return EXIT_FAILURE;
      }
   }
   
   return EXIT_SUCCESS;
   
}//end main

Inter-process communication

Although it is not the normal mechanism used for IPC, one process can send a signal to a different process. Note that the use of Pipes, FIFOs, or message queues is the preferable mechanism for achieving IPC.

P.I.P.S. alternative

If code which is to be ported to P.I.P.S. uses signals for IPC then these should be removed and the code modified to use either pipes or FIFOs, which should be handled by the main processing loop of the process.