|
||
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.
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.
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.
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:
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;
}
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;
}
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;
}
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:
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;
}
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;
}
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;
}
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;
}
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:
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;
}
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;
}
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:
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:
#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;
}
#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;
}
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.
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.
P.I.P.S. supports two types of IPC between processes or threads:
Pipes (named and unnamed): Unnamed pipes are created
using pipe()
and named pipes are created using
mkfifo()
. Pipe-based communication is also possible
between a parent and a child process when the child is created using
popen()
or popen3()
.
Local file sockets: These correspond to sockets
created with AF_LOCAL/PF_LOCAL/AF_UNIX
as the address family. The
semantics of their use are similar to those of their Unix equivalents.