Symbian
Symbian Developer Library

SYMBIAN OS V9.4

Feedback

[Index] [Previous] [Next]


Process Creation and IPC

[Top]


Introduction

Process creation on Unix-like machines is normally done in two stages. Firstly, the fork() function is called. This creates a child process which is a clone of the calling (parent) process. The child process is identical (except for a few details) to the parent. Secondly, a call to one of the exec() family functions is invoked by the child which changes the program being executed by that process.

P.I.P.S. does not provide the fork() and exec() APIs. The reasons for this are detailed below. However, there are industry standard alternatives which are supported by P.I.P.S. and are detailed in subsequent sections of this document.

[Top]


Omission of fork()

A Unix-like kernel is designed with the two stage process creation in mind. On Symbian OS (and Microsoft Windows®) the kernel was designed to create a process, which runs a single executable, in a single step.

P.I.P.S. does not implement the fork() operation because this would require an entirely new way of dealing with processes in the kernel and memory model. For example, duplicating the address space of an existing process was not something that was considered in the Symbian OS kernel's design. Symbian took this decision as the effort involved in customising this feature would be more than the benefit of providing the fork() function, especially in the provision of Pthreads.

[Top]


Omission of exec()

On Unix-like systems, exec() is the standard way to load a process image into memory. Only the initialisation process is loaded directly into a new address space (special case code in the kernel), all other processes are loaded into an existing address space which was initially created by forking.

On Symbian OS, the only way to load a process image into memory is using the loader. It assumes that the image is being loaded as a new process - one that owns no resources, has no open handles and so on. The change to the loader, and any associated kernel changes, to support the exec() function were deemed to be too risky.

[Top]


Alternatives to fork() and exec() for creating child processes


Parent and child with no IPC - use posix_spawn()

In this scenario, the parent process creates the child, but there is no subsequent communication between the two (except maybe the parent waiting for termination of the child via use of the waitpid() function).

Rather than use the fork()/exec() combination to achieve this the posix_spawn() API can be used instead. This API will create a process by a single API call. Additionally, actions can be performed on inherited file descriptors to change access before the child's main is called. For more detailed information on the posix_spawn() function consult the relevant Open Group standard pages available at www.opengroup.org.

The subsequent sections provide the following information:

Parent process fork() and exec() functions

The following code shows how the creation of a child process can be implemented using the fork() and exec() functions. Note that inclusion of header files has been omitted for clarity.

int main(int argc, char *argv[])
{
  pid_t Childpid = fork();
     
  if(Childpid == 0)
  {
      //Running in Child process

      //exec() to replace the child process executable
      char execFileName[] = "/root/PortDoc/Example0_c/Posix/child/ChildProg";

      execl(execFileName,NULL);
  }
  else
  {
     //Running in parent process

     //Wait for the child process to terminate
     waitpid(Childpid,NULL,0);
     
     printf("\r\n*** Child process finished ***\r\n");
     
  }
   
  return EXIT_SUCCESS;
}

Using the posix_spawn() function

The following code shows how the above can be modified to use the posix_spawn() operation.

int main(int argc, char *argv[])
{
   pid_t Childpid;
   char execFileName[] = "/root/PortDoc/Example0_c/Symbian/child/ChildProg";
   
   int RetVal= posix_spawn(&Childpid,execFileName,NULL,NULL,NULL,NULL);
   
   (void)waitpid(Childpid,NULL,0);
   
   printf("\r\n*** Child process finished ***\r\n");
    
   return EXIT_SUCCESS;
}

Child process example

The child code shown is unaffected by the mechanism used to spawn it, that is, fork()/exec() or posix_spawn(), but an example is shown below for completeness.

int main(void)
{
    printf("\r\n*** Child Running ***\r\n");
    
    return EXIT_SUCCESS;
}

Parent and child IPC using a single pipe - use popen()

In this scenario, the parent and child processes communicate using a single pipe. The following pseudo code details the mechanism used by code in the parent process in a Unix-like system.

Call pipe() to create the pipe.
Call Fork().
if (child process)
{
   //Child Process.
   duplicate (via a dup2() call) the read/write end of the pipe using  
   prior agreed file descriptor numbers which will subsequently be used by the 
   child process following the exec() call.
   Close the original file descriptors.
   Call exec() to replace the code image in the child process.
}
else
{
   //Parent process
   Do Whatever
}

Instead of using the fork()/exec() as described above, POSIX libraries including P.I.P.S. provide the popen() function as defined in stdio.h. Rather than using a pipe for the IPC mechanism the parent process communicates with the child using the stream returned by the popen() call, while the child process input or output is via the stdin()/stdout() streams.

However if the parent process requires a file descriptor it can be obtained by using the fileno() API call. For more information about the popen() function, see http://www.opengroup.org/.

The subsequent sections provide the following information:

Parent process fork() and exec() functions

The following code shows the use of a pipe and subsequent fork()/exec() calls to create a Pipe for IPC. The example shown is the child process writing data to the pipe, and the parent process receiving the data.

//Child Process File Descriptors for the pipe. These should be defined in a common header 
//file used to compile both the parent and child process's executable.
//However #define is here for clarity.
#define WRITE_FD  104
int main(int argc, char *argv[])
{
  int pipeEnds[2]; //Pipe file descriptors [0] is for read [1] is for write
  
  //create the pipe
  if(pipe(pipeEnds) != 0)
  {
     //Pipe creation error
     return EXIT_FAILURE;
  }
  else
  {
     pid_t Childpid = fork();
     
     if(Childpid == 0)
     {
         //close the redundant read FD obtained from the parent process
         (void)close(pipeEnds[0]);
    
         //duplicate the file descriptor for use following exec().
         if(dup2(pipeEnds[1], WRITE_FD) == -1)
         {
            printf("dup2 error\n");
            return EXIT_FAILURE;
         }
         //close the redundant write FD obtained from the parent process
         (void)close(pipeEnds[1]);
    
         //exec() to replace the child process executable
         char execFileName[] = "/root/PortDoc/Example1_c/Posix/Child/ChildProg";
         execl(execFileName,NULL);
      }
      else
      {
         //Parent process. This reads from the pipe. Therefore close the write end of the
         //pipe.
         close(pipeEnds[1]);
    
         //declare receive buffer, and clear its contents
         char RxBuffer[100];
         memset(RxBuffer,0,sizeof(RxBuffer));
    
         //Wait for data from the child process. Child sends a string.
         read(pipeEnds[0],RxBuffer,sizeof(RxBuffer));
    
         printf(RxBuffer);

         //close the Read end of the pipe
         close(pipeEnds[0]);
    
         //Wait for the child process to terminate
         waitpid(Childpid,NULL,0);
      }
   }
   return EXIT_SUCCESS;
}

Child process created using fork() or exec() functions

The following code shows an example of the child process source which will be executed following the exec().

//Child Process File Descriptors for the pipe. These should be defined in a common header 
//file used to compile both the parent and child process's executable. Shown here for
//clarity.
#define WRITE_FD  104

int main(void)
{
   char TxMsg[] = "Hello Parent\n";
    
   //Send the message to the parent
   write(WRITE_FD,TxMsg,sizeof(TxMsg));
    
   //close the File Descriptor
   close(WRITE_FD);
   return EXIT_SUCCESS;
}

Parent process P.I.P.S. example for popen() function

The following code shows how the above code can be modified to use popen(), rather than the fork()/exec() combination.

int main(int argc, char *argv[])
{
   //Create child process using popen(). Child process writes to the Parent therefore "r" 
   //parameter.
   FILE* ChildProcessStream = popen("/root/PortDoc/Example1_c/Symbian/Child/ChildProg","r");
          
   if(ChildProcessStream == NULL)
   {
      printf("\n Failure to create child process with popen()\n");
      return EXIT_FAILURE;
   }
   else
   {
      //Use a file descriptor rather than a stream
      int ChildProcessFD = fileno(ChildProcessStream);

      //Create a receive buffer, and zero contents before receiving.
      char RxBuffer[100];
      memset(RxBuffer,0,sizeof(RxBuffer));
      
      //Wait for data from the child process. Child sends a string.
      int nbytes = read(ChildProcessFD,RxBuffer,sizeof(RxBuffer));

      //printf is slightly different from the POSIX example to verify that the data output to the 
      //screen is done by the parent process.
      printf("\n Message Received by Parent=%s",RxBuffer);
      
      //Wait for Child Process to complete
      pclose(ChildProcessStream);
   }
   return EXIT_SUCCESS;
}

Child process P.I.P.S. example for popen() function

The following code shows how the child process source can be modified when it is executed via the popen() function. Note that rather than using a file descriptor for the communication via the pipe the stdin() or the stdout streams are directed to the parent process, as defined in the usage of the popen() call.

int main(void)
{
    //Child process created by popen() so that its stdout is streamed to the parent process
    char TxMsg[] = "Hello Parent\n";
    
    //Send the message to the parent
    printf(TxMsg);
    
    return EXIT_SUCCESS;
}

Parent and child IPC using named pipes or FIFOs - use mkfifo()

An alternative approach to using the popen() function is to use named pipes, or FIFOs. The advantage of using them over the popen() mechanism is that they allow the code in both the parent and child processes to continue to use file descriptors for communication rather than streams (and avoid modification to the stdin()/stdout() streams of the child process).

In addition, since each created FIFO is referenced as a file in the file system, FIFOs allow for more complicated IPC schemes than those offered by the popen() function, for example, inter-child process communication. For more information about the use of FIFOs, see http://www.opengroup.org/.

The subsequent sections provide the following information:

Parent process P.I.P.S. example using FIFOs

The following code shows how FIFOs can be used in P.I.P.S. by the parent process.

int main(int argc, char *argv[])
{
   char fifoFileName[] = "/root/PortDoc/Example2_c/Symbian/fifofile";

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

      return EXIT_FAILURE;
   }
   else
   {
      //FIFO creation successful.
      
      //Spawn the child process.
      pid_t Childpid;
      char execFileName[] = "/root/PortDoc/Example2_c/Symbian/ChildProg";
      int RetVal= posix_spawn(&Childpid,execFileName,NULL,NULL,NULL,NULL);
      
      if(RetVal != 0)
      {
         printf("\n*** failure posix_spawn ***\n");

         return EXIT_FAILURE;
      }
      else
      {
         //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));
      
            //Wait for data from the child process. Child sends a string.
            int nbytes = read(ReadFifoFd,RxBuffer,sizeof(RxBuffer));

            printf("\nMessage Received by Parent=%s",RxBuffer);
      
            //close the FIFO
            (void)close(ReadFifoFd);
         }

         //wait for the child process to finish      
         (void)waitpid(Childpid,NULL,0);
   
         //unlink the FIFO
         unlink(fifoFileName);
      }
   }
   return EXIT_SUCCESS;
}

Child process P.I.P.S. example using FIFOs

The following code shows how FIFOs can be used in P.I.P.S. by the child process.

int main(int argc, char *argv[])
{
   char fifoFileName[] = "/root/PortDoc/Example2_c/Symbian/fifofile";

   //Open the FIFO. child writes to parent
   int WriteFifoFd = open(fifoFileName,O_WRONLY);
     
   if(WriteFifoFd == -1)
   {
      //Failed to open the Fifo
      printf("\n*** child failure Fifo Open ***\n");
      return EXIT_FAILURE;
   }
   else
   {
      //create a message to send.
      char TxMsg[] = "Hello Parent\n";
       
      //Wait for data from the child process. Child sends a string.
      write(WriteFifoFd,TxMsg,sizeof(TxMsg));
     
      //close the FIFO
     (void)close(WriteFifoFd);
   }
   return EXIT_SUCCESS;
}

[Top]


Forking without exec()

A Unix-like system may create the child process via a fork(), which then does not make a subsequent exec() call. The result of this is that the parent and child processes run the same executable. The child may communicate with the parent via pipes, for example.

One example of a system which does this is the e-mail software program Exim. In addition to forking without exec(), it can also re-exec() itself to regain dropped root privileges.

The issues that such systems encounter when porting to P.I.P.S. fall into two categories:


Little or no state data passed to child - use posix_spawn()

The first issue is where there is a little/no data passed to the child process on the fork() operation. Many examples of this exist in pre/post forking of listening sockets in TCP server applications, for example, in the MPM pre-fork module of the Apache server. More details are available at http://httpd.apache.org/docs/2.2/mod/prefork.html.

This can be resolved by using the posix_spawn() operation, and passing any data using the argv parameters or environment variables. For more information about the posix_spawn() operation, see http://www.opengroup.org/. Note that some argv parameters must be used to distinguish the behaviour of the parent process from the subsequent behaviour of the child when the main() function is called; the behaviour of the child cannot be identical to the parent.

The subsequent sections provide the following information:

Parent process forking example

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#define NUM_OF_PROCS 5

int main(int argc, char *argv[])
{
  pid_t Processpid[NUM_OF_PROCS];
     
  int I;
  
  for(I=1;i<NUM_OF_PROCS;i++)
  {
     Processpid[i] = fork();
    
     if(Processpid[i] == 0)
     {
        printf("\r\n Child Process Inst[%d] running ***\r\n",I);

        //Terminate child process.5
        exit(0);
     }
     else
     {
        //Wait for the child process to terminate before forking the next one.
        waitpid(Processpid[i],NULL,0);
     
        printf("\r\n*** Child int[%d] process finished ***\r\n",I);
     }
  }
   
  return EXIT_SUCCESS;
}

P.I.P.S. equivalent

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <spawn.h>
#include <sys/types.h>
#include <sys/wait.h>

#define NUM_OF_PROCS 5

int main(int argc, char *argv[])
{
   if(argc != 2)
   {
      printf("\r\n One parameter is needed.  \r\n");
      
      return EXIT_FAILURE;
   }
   else
   {
      int argvAsInt = atoi(argv[1]);
      
      if(argvAsInt > NUM_OF_PROCS)
      {
         printf("\r\n parameter[%d] one is out of range  \r\n",argvAsInt);
       
         return EXIT_FAILURE;
      }
      else
      {      
         if(argvAsInt == 0)
         {
            //parent process.
            pid_t Processpid[NUM_OF_PROCS];
   
            //executable points to the compiled version of this source.
            char execFileName[] = "/root/PortDoc/Example3_c/Symbian/ParentProg";
            int RetVal;
            int I;
            char iAsString[2];
            char* spawnedArgs[3];
            spawnedArgs[0] = argv[0];
            spawnedArgs[2] = NULL;
    
            for(I=1;i<NUM_OF_PROCS;i++)
            {
               //store I as a string.
               bzero(iAsString,sizeof(iAsString));
               iAsString[0] = 0x30+I;
       
               spawnedArgs[1] = iAsString;
               
               RetVal= posix_spawn(&Processpid[i],execFileName,NULL,NULL,spawnedArgs,NULL);
   
               //wait for chid process to terminate before spawning the next.
              (void)waitpid(Processpid[i],NULL,0);
               printf("\r\n*** Child process finished ***\r\n");
            }
         }
         else
         {
            //child process
            printf("\r\n Child Process Inst[%d] running ***\r\n",argvAsInt);

            //Terminate child process.
            exit(0);
         }
      }
   }
   return EXIT_SUCCESS;
}

A lot of data passed to child - use Pthreads

The second issue is where there is too much data to be passed across to the child process via the posix_spawn() call. A common work-around used in systems where fork() is not available is to use POSIX threads, or Pthreads. These Pthreads will execute in the same process and share their memory space, that is, they can share the same data objects. One critical difference between using Pthreads and fork() is that fork() creates copies of the parent's data objects in the child. The copied data objects can then be modified independently by both processes. However, when using Pthreads such data objects are shared and extra care, such as the use of mutexes and semaphores, is required when accessing them if their values can change.

[Top]


exec() without forking - use posix_spawn()

A UNIX® program may exec without forking. The result is that the calling process executes another binary, but inherits certain properties, for example, file descriptors.

The P.I.P.S. equivalent would be to call posix_spawn() and pass any inherited data via argv parameters.

Note that if a parent process is waiting for the child to terminate then the termination of the newly created task must be synchronised. Therefore a waitpid() call should follow the posix_spawn() call so that it does not exit prematurely. Otherwise exit() can be called after the posix_spawn().

Note that if the process which formerly executed and which now uses posix_spawn() stores its PID (for example, in the file system) to allow other processes to target it, then care must be taken to store the relevant PID (Process ID). This is because the PID for the newly spawned process may need to be stored instead.

[Top]


Generic IPC

P.I.P.S. supports two types of IPC between processes or threads:

[Top]


Also see