Miscellaneous Topics

Poll using select

The select() function call provides functionality to scan descriptors for a ready event. The select() call requires the system to scan through a bit mask of a file descriptor set against its list of open descriptors to check for ready events, only stopping when it reaches a maximum descriptor value. For a system with a relatively small number of open descriptors this is quite efficient but for a large amount of descriptors can be overly burdensome.

The Symbian P.I.P.S. libraries are not expected to be dealing with a very large number of file descriptors and so select() should be satisfactory for any applications on a phone.

Note: P.I.P.S. does not support the poll() function.

Monitoring two pipes using select()

The following code segment shows the usage of the select() function:

Int main(int argc, char **argv)
{
   FILE* ChildProcessStream1;
   FILE* ChildProcessStream2;
   int ChildProcessFD1;
   int ChildProcessFD2;
   fd_set SelectFDs;
   int MaxFD;
   struct timeval timeout;
   int Ret;

   //Create child process 1 using popen(). 
   ChildProcessStream1 = popen("/root/PortDoc/Example7_c/Symbian/Child/ChildProg", "r");
   if(ChildProcessStream1 == NULL)
   {
      printf("\n Failure to create child process 1 with popen()\n");
      return EXIT_FAILURE;
   }

   //Create child process 2 using popen(). 
   ChildProcessStream2 = popen("/root/PortDoc/Example7_c/Symbian/Child/ChildProg", "r");
   if(ChildProcessStream2 == NULL)
   {
      printf("\n Failure to create child process 2 with popen()\n");
      return EXIT_FAILURE;
   }

   //Convert streams to file descriptors
   ChildProcessFD1 = fileno(ChildProcessStream1);
   ChildProcessFD2 = fileno(ChildProcessStream2);

   //Setup the select set
   FD_ZERO(&SelectFDs);
   FD_SET(ChildProcessFD1, &SelectFDs);
   MaxFD = ChildProcessFD1;
   FD_SET(ChildProcessFD2, &SelectFDs);
   
   //Calculate the largest Descriptor
   if (ChildProcessFD2 > MaxFD)
      MaxFD = ChildProcessFD2;
   
   //Setup a time out value in case neither return in a sufficient time
   timeout.tv_sec = 3;
   timeout.tv_usec = 0;
   
   //Issue select request
   Ret = select(MaxFD + 1, &SelectFDs, NULL, NULL, &timeout);
   if (Ret == -1)
   {
      //Error occurred
      printf("\nCould not poll, error=%d",errno);    
      return EXIT_FAILURE;
   }
   else if (Ret == 0)
   {
      //Timed out
      printf("\nTimed Out");    
   }
   else
   {
      //Create a receive buffer, and zero contents before receiving.
      Char RxBuffer[100];
      memset(RxBuffer,0,sizeof(RxBuffer));

      if (FD_ISSET(ChildProcessFD1, &SelectFDs))
      {
         //Wait for data from child process 1. Child sends a string.
         Int nbytes = read(ChildProcessFD1,RxBuffer,sizeof(RxBuffer));
         printf("\nMessage Received from Child1 First =%s",RxBuffer);
      }
      else if (FD_ISSET(ChildProcessFD2, &SelectFDs))
      {
         //Wait for data from child process 2. Child sends a string.
         Int nbytes = read(ChildProcessFD2,RxBuffer,sizeof(RxBuffer));
         printf("\nMessage Received from Child2 First =%s",RxBuffer);
      }
   }

   //Wait for Child Process to complete
   pclose(ChildProcessStream1);
   pclose(ChildProcessStream2);

   return EXIT_SUCCESS;
}

Socket IOCtl options

The underlying implementation of sockets on the Symbian platform imposes some restrictions on the socket options available in P.I.P.S. and has also resulted in some new non-standard options to be created.

The introduction of multi-homing (single link, multiple IP addresses) on Symbian phones and the various methods of connecting to networks, such as Wi-Fi and 3G, with their varying degrees of cost to the user have made it important to be able to choose which interface or access point is used to route socket traffic. The Symbian RConnection class provides this functionality, and is exposed in P.I.P.S. using some extra IOCtl options.

The list of socket options available in P.I.P.S. are as follows:

Socket Option

New

Description

SIOCADDRT

Yes

Adds an entry to the interface routing table using the parameters in the rtentry structure.

SIOCATMARK

No

Determines whether the read pointer is currently pointing to the logical mark in the data stream, testing whether the next read will be Out-of-Band data or not.

SIOCDELRT

Yes

Deletes an entry from the interface routing table using the parameters in the rtentry structure.

SIOCGIFACTIVECONF

Yes

Gets a list of interfaces/access points started by P.I.P.S. in an ifconf structure.

SIOCGIFADDR

Yes

Gets the interface address. This is valid only for sockets with address family AF_INET.

SIOCGIFCONF

No

Gets a list of available interfaces/access points in an ifconf structure.

SIOCGIFHWADDR

Yes

Gets the interface hardware address in an ifreq structure.

SIOCGIFINDEX

Yes

Sets the index of an interface/access point from a name in an ifreq structure.

SIOCGIFNUM

Yes

Return the total number of IP interfaces configured in the system.

SIOCIFACTIVESTART

Yes

Attempts to start a sub-connection on an available interface/access point using the parameters in an ifreq structure.

SIOCIFSTART

Yes

Attempts to start an interface/access point using the parameters in an ifreq structure.

SIOCIFSTOP

Yes

Stops a previously started interface or sub-connection.

SIOCSIFNAME

No

Sets the desired interface/access point name to start in an ifreq structure.

The follow code for a function shows how to start an interface called "3G Access Point" and return the created socket.

int opensocket()
{
  int Sock;
  struct ifreq Ifr;
  char AccessPoint[]="3G Access Point";

  //Open the socket
  Sock = socket(AF_INET, SOCK_STREAM, 0);
  if (Sock < 0) 
  {
    printf("\nCannot open socket, error=%d",errno);
    return -1;
  }

  //Set up the interface request structure
  bzero(&Ifr, sizeof(Ifr));
  strcpy(Ifr.ifr_name, AccessPoint);
    
  //Set the requested interface name
  if (ioctl(Sock, SIOCSIFNAME, &Ifr))
  {
    printf("\nCannot set the interface name, error=%d",errno);
    close(Sock);
    return -1;
  }

  //Start the interface
  if (ioctl(Sock, SIOCIFSTART, &Ifr))
  {
    printf("\nCannot start the interface, error=%d",errno);
    close(Sock);
    return -1;
  }

  //Return the opened socket
  return Sock;
}

Mathematical functions

The Symbian platform does not have any support for long doubles so any P.I.P.S. programs which call long double versions of APIs will actually invoke the double version of the API.

The Symbian platform supports the use of a hardware floating point co-processor, however not all phones incorporate an FPU (Floating Point Unit) and rely on software emulation of floating point operations. Phones and computers equipped with an FPU provide faster and more accurate floating point operations.

The Symbian platform does not support complex numbers so the P.I.P.S. libraries are not able to offer the POSIX complex number APIs.

Note: The mathematical functions are included in the libm.dll file.

Error handling and cleanup

It is important that the Symbian platform error codes do not reach any ported application code. P.I.P.S. logically maps the native OS error codes with the corresponding POSIX errno values as per the standard. So, ported programs will not usually have to alter their error checking/handling.

Mapping P.I.P.S. error codes to the Symbian platform error codes

Porting your application to the Symbian platform requires 'translating' the Symbian platform error codes to POSIX error codes. User::Leaves() from native Symbian APIs are trapped in P.I.P.S.. Calls to P.I.P.S. APIs from user code need not be wrapped in TRAP s.

Occasionally errors may be generated by the underlying Symbian platform that cannot be translated to POSIX error codes, in which case the error variable errno will be out of the usual range of values, above the maximum value of __EMAXERRNO or 124.

The Symbian platform error code can be calculated using the following formula:

Symbian Error Code = -(errno - __EMAXERRNO)

Error codes are defined in the errno.h file.

System logger

P.I.P.S. does not supply a system logger for use with openlog(), syslog() and closelog(). Instead, a rudimentary selection of functions which log to a file can be written as demonstrated by the following example.

//define maximum length of identifier
#define SysLogMax 80

//logging file and identifier
FILE* fSysLog = NULL;
char fSysLogIdent[SysLogMax];

//close the log file
void my_closelog()
{
   //close the log file if it is open
   if (fSysLog)
   {
      fclose(fSysLog);
      fSysLog = NULL;
   }
   fSysLogIdent[0] = '\0';
}

//open a new log file
int my_openlog(const char *ident)
{
   //close the log file if it is open
   if (fSysLog)
      my_closelog();    
    
   //make the logging directory
   mkdir("/syslog/", S_IWUSR | S_IRUSR | S_IXUSR);
    
   //open a new log file
   fSysLog = fopen("/syslog/syslog.log", "a");
    
   //return if the log file did not open
   if (!fSysLog)
      return -1;
    
   //set the identifier
   if (!ident)
      fSysLogIdent[0] = '\0';
   else    
      strncpy(fSysLogIdent, ident, SysLogMax);
    
   return 0;
}

//output a string to the log file with a variable argument list
void my_vsyslog(const char *format, va_list formatlist)
{
   //open a log file if one does not exist
   if (!fSysLog)
   {
      my_openlog(NULL);    
   }
    
   //check if there is a log file 
   if (fSysLog)
   {
      //print out the logging identifier if one exists
      if (strlen(fSysLogIdent))
      {
         fprintf(fSysLog, "%s ", fSysLogIdent);
      }
        
      //print out the logging string
      vfprintf(fSysLog, format, formatlist);    
      fprintf(fSysLog, "\r\n");    
   }
}

//output a string to the log file
void my_syslog(const char *format, ...)
{
   //create the variable argument list
   va_list formatlist;
   va_start(formatlist, format);
   my_vsyslog(format, formatlist);
   va_end(formatlist);
}


int main(int argc, char **argv)
{
   //open the log file
   my_openlog("test");
   
   //log a line
   my_syslog("testing logging of the error number : %d", errno);
   
   //close the log
   my_closelog();
   
   return EXIT_SUCCESS;
}

Pthread barriers, rwlocks and spinlocks

At present P.I.P.S. does not provide Pthread barriers, rwlocks or spinlocks, but there are techniques which can be used to implement the functionality using semaphores, mutexes and conditional variables.

The following code sample (originally taken from http://heather.cs.ucdavis.edu/matloff/public_html/158/PLN/ParProcIntro.pdf) demonstrates an implementation of a simple Pthread barrier.

struct barrier_t
{
   //number of nodes to synchronise
   int nodes;
   //two counts to avoid race conditions
   int count[2];
   //which count to use
   int whichcount;
   //mutex to lock
   pthread_mutex_t lock;
   //condition to lock
   pthread_cond_t cv;
};

//initialize a barrier 
void InitBarrier(struct barrier_t* PB, int nodes)
{
   PB->nodes = nodes;
   PB->count[0] = 0;
   PB->count[1] = 0;
   PB->whichcount = 0;
   pthread_mutex_init(&PB->lock, NULL);
   pthread_cond_init(&PB->cv, NULL);
}

//destroy a barrier
void DestroyBarrier(struct barrier_t* PB)
{
   pthread_mutex_destroy(&PB->lock);
   pthread_cond_destroy(&PB->cv);
}

//wait for a barrier
void WaitBarrier(struct barrier_t* PB)
{
   int WhichCount, Count;
   
   //which counter variable to use
   WhichCount = PB->whichcount;
   
   //lock the mutex
   pthread_mutex_lock(&PB->lock);
   
   //get the count of nodes
   Count = ++PB->count[WhichCount];
   
   //test whether it should block
   if (Count < PB->nodes)
      pthread_cond_wait(&PB->cv, &PB->lock);
   else
   { 
      //reset the counter
      PB->count[WhichCount] = 0;
      PB->whichcount = 1 - WhichCount;
      
      //release the wait
      pthread_cond_broadcast(&PB->cv);
   }
   
   //unlock the threads
   pthread_mutex_unlock(&PB->lock);
}

The following code was posted by Michael M. Lampkin as an open source implementation of Pthread spin threads.

/**********************************************************************
BETA User Space spinlocks for POSIX systems lacking this functionality.
Copyright ©) 2003-2006 Michael M. Lampkin 
Contact at michael.lampkin<at>ieee.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
version 2 along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301

**********************************************************************/

#define _POSIX_C_SOURCE 200112L
#define _XOPEN_SOURCE 600

#define SPINLOCK_SPIN_MAX 50

/**
 Need this "unique" value that we can use to take any spinlock
 that has been initialized and identify attempts to call init
 multiple times without corresponding calls to destroy.  A hard
 coded value should be fine though still a 1 in 4 billion chance
 of collision with random data in un-inited spinlock.
**/

static long int spin_magic = 0x2FCD51F9L;

/**
  The spinlock structure which should NEVER be manipulated
  by user code.
  owner:
    a pthread_t var indicating the current owner of the
    spinlock or filled with 0's if not owned
  mutex:
    the primary mutex that any incoming threads will spin on
    and attempt to obtain.
  magic:
    a field to hold a sentinel value indicating if the spinlock
    is initialized.
**/

typedef struct
{
  pthread_t       owner;
  pthread_mutex_t mutex;
  long int        magic;
} 
spinlock;

/**
 Function: spinlock_init
 Description:
   Initializes and allocates system resources to a
   spinlock structure.
 Parameters:
   spin - a pointer to the spinlock structure to
     be initialized.
   pshared - either PTHREAD_PROCESS_PRIVATE or
     PTHREAD_PROCESS_SHARED.  If the system does not
     support process shared mutexes or an unknown value
     is given then defaults internally to a private type
     with no error.
**/

int spinlock_init( spinlock * spin, int pshared )
{
  int result;
  pthread_mutexattr_t attr;

  /* If already inited... race condition with destroy */
  if ( NULL == spin )
  {
    return EINVAL;
  }

  if ( spin_magic == spin->magic )
  {
    return EBUSY;
  }

  ( void ) memset( & spin->owner, 0, sizeof( pthread_t ) );

  /* Set our process sharing attribute - default to PRIVATE */
  result = pthread_mutexattr_init( & attr );

  if ( 0 == result )
  {
    if ( 0 < sysconf( _SC_THREAD_PROCESS_SHARED ) )
    {
      if( PTHREAD_PROCESS_SHARED == pshared )
      {
        result = pthread_mutexattr_setpshared( & attr, pshared );
      }
      else
      {
        result = pthread_mutexattr_setpshared( & attr, PTHREAD_PROCESS_PRIVATE );
      }
    }
  }

  /* Need to add this to prevent recursive mutex default on some sys */
  if ( 0 == result )
  {
    result = pthread_mutexattr_settype( & attr, PTHREAD_MUTEX_ERRORCHECK );
  }

  /* The following is a race against simultaneous calls to init */
  if ( 0 == result )
  {
    result = pthread_mutex_init( & spin->mutex, & attr );
  }

  if ( 0 == result )
  {
    spin->magic = spin_magic;     
  }

  ( void ) pthread_mutexattr_destroy( & attr );
  return result;
}

/**
 Function: spinlock_destroy
 Description:
   Releases system resources allocated to a spinlock
   structure during initializion.
 Parameters:
   spin - a pointer to a previously initialized but
     not destroyed spinlock.
**/
int spinlock_destroy( spinlock * spin )
{
  int result;

  if ( NULL == spin || spin_magic != spin->magic )
  {
    return EINVAL;
  }

  if ( 0 != ( result = pthread_mutex_destroy( & spin->mutex ) ) )
  {
    return result;
  }

  ( void ) memset( & spin->owner, 0, sizeof( pthread_t ) );

  /**
   A return of EINVAL on destroy means another thread is
   also destroying.  Ignore it.
  **/
  spin->magic = 0;

  return 0;
}

/**
 Function: spinlock_lock
 Description:
   Attempts to acquire exclusive access to the specified
   spinlock.  If the spinlock is already owned then begin
   spinning until ownership is obtained.

 Parameters:
   spin - a pointer to an initialized spinlock.
**/
int spinlock_lock( spinlock * spin )
{
  pthread_t self;
  int result;
  int spin_count;

  if ( NULL == spin || spin_magic != spin->magic )
  {
    return EINVAL;
  }

  self = pthread_self( );
  if ( 0 == memcmp( & spin->owner, & self, sizeof( pthread_t ) ) )
  {
    return EDEADLK;
  }

  for ( ; 0 != ( result = pthread_mutex_trylock( & spin->mutex ) ) ; )
  {
    if ( EBUSY == result )
    {
      ++ spin_count;

      if ( SPINLOCK_SPIN_MAX == spin_count )
      {
        ( void ) sched_yield( );
        spin_count = 0;
      }
    }
    else
    {
      /* Destroy occurred on us... */
      return EINVAL;
    }
  }

  ( void ) memcpy( & spin->owner, & self, sizeof( pthread_t ) );
  return result;
}

/**

Command line shell

The Symbian platform phones do not have a command line shell as standard. P.I.P.S. does however support the stdin(), stdout() and stderr() standard streams, enabling parent processes to redirect a child's standard streams for the exchange of data. Without explicitly redirecting the standard streams, the default is for each to be redirected to the /dev/null directory. The P.I.P.S. system() function does not create a shell for a new program as in most POSIX implementations, however, it will start a program and return the program's exit value.

User interfaces

Most POSIX based systems interact with a windowing system such as X Windows and libraries such as GTK. P.I.P.S., however, does not have its own UI libraries, so any P.I.P.S. based applications that require UI functionality must rely on the UI from the device manufacturer. As a consequence, for any UI program that uses P.I.P.S., there must be an interaction between the Symbian platform native C++ and P.I.P.S. C code.

One Definition Rule - warning

Standard C++ states that the One Definition Rule (ODR) must be maintained within a program.

The Symbian platform can neither check (without significant modifications) that the ODR is violated nor use the technique called symbol pre-emption to ensure that the ODR is enforced.

Therefore, you must take care and must not assume that there is only one copy of the symbol (that is, all instances of the same symbol will have the same address) across all DLLs within a program.