|
||
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.
Signals can be sent to a process/thread at any time and will be handled immediately, unless
the relevant signal is currently blocked, or
the system is currently executing kernel code (a system call). Note, however, that some system calls enter a state called 'interruptible sleep' where they can be interrupted by signals.
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:
Be reentrant, allowing signals to be handled with no issues
Block signals during the function, preventing reentrance
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.
Typically, any OS kernel could function as a signal barrier in much the same way as in UNIX, that is, it could
not handle signals until leaving the kernel, and
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.
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.
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 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.
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.
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:
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.
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
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 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.
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
#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
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.
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.