Re: Executing 2 processes "fork()" alternatively
Jens.Toerring_at_physik.fu-berlin.de
Date: 12/11/04
- Next message: Jens.Toerring_at_physik.fu-berlin.de: "Re: Driver Development standards"
- Previous message: moi: "Re: how to tell if an open file has been removed"
- In reply to: Daniel: "Executing 2 processes "fork()" alternatively"
- Next in thread: Pascal Bourguignon: "Re: Executing 2 processes "fork()" alternatively"
- Reply: Pascal Bourguignon: "Re: Executing 2 processes "fork()" alternatively"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Date: 11 Dec 2004 15:04:00 GMT
Daniel <ve2bap@sympatico.ca> wrote:
> I want to create a C program (on LINUX platform) with a child and a
> parent. I want the parent and the child to be executed alternatively.
> In my example below, I want to get the following display (see
> EXECUTION, which gives the display I want), but without using "sleep"
> instruction... because I want this program to be executed fast.
I understand that this is, of course, a program you write to get an
understanding how things work, I hope you realize that you picked
a rather unrealistic example here - when you need that tight a
coupling between the processes using two independent processes is
about the worst idea you can come up with. Having two independent
processes makes only sense if they can run without or only with
occasional need for synchronization.
The way your program is written now (with using sleep()) it's only
working by accident. First of all, there's no guarantee that the
child is going to be the first process to be executed (on some
systems it happens 99% of the time, but relying on that is not a
good idea). Second, the sleep() does not necessarily guarantee
that the other process is going to be the one picked for execution
by the kernel - it probably will probably work most of the time,
but you can't be sure.
> Is there another way for the child to pass control to the parent, and
> the parent to pass control to the child, after 1 cycle of "FOR" loop ?
> I tried to implement "kill(pid, SIGSTOP)" and "kill(pid, SIGCONT)",
> but I don't understand the syntax of these signals.
There are several methods you can use for process synchronization.
One of them is using signals, but they need to be used with great
care, as I hope I am able to show in the following. You actually
don't need special signals like SIGSTOP and SIGCONT for that. To
be able to use signals you will have to install a signal handler
for the signal the process will have to handle. In your simple
example the signal handler function can be as simple as this,
i.e no action is needed at all:
void signal_handler( int signo )
{
}
Once you have this function installes as the signal handler, using
the function sigaction(2), the process won't get killed anymore
by the signal (the default disposition for most signals, and, of
course, you must pick a signal that can be caught by the process,
there are a few like SIGKILL that can't). and you simply can call
the function pause(2) (wich only returns when a signal is received)
to sleep until a signal arrives.
How to install a signal handler? Here's all you need in most cases:
struct sigaction sact;
sact.sa_handler = signal_handler;
sigemptyset( &sact.sa_mask );
sact.sa_flags = 0;
if ( sigaction( SIGUSR1, &sact, NULL ) < 0 ) {
fprintf( stderr, "Installing signal handler failed\n" );
exit( EXIT_FALURE );
}
I picked SIGUSR1 as the signal to use here because it's not used
for anything by the system, so you can use it freely for your own
purposes.
Sending the signal is simple, you just need the PID of the process
you want the signal being sent to. The parent already knows the
PID of the child from the return value of fork() and the child
can find out about the PID of its parent by using the function
getppid(). So in the parent you would have
kill( pid, SGUSR1 );
pause( );
and in the child
kill( getppid( ), SIGUSR1 );
pause( );
So, after the parent has done its printing it sends a signal to the
child and then sleeps until the next signal arrives. And the child
does exactly the same.
But there's one unfortunate thing about this scheme. You may not
notice it immediately, but there is what's called a "race condition".
That means that there is a chance that the process gets put to sleep
by the kernel (e.g. because its time slice is over) after calling
kill() but before calling pause(). If that happens and the other
process is run in between, sending its signal, then pause() gets
called _after_ the signal arrives, so the process is getting caught
in pause() and will sleep forever (the other process won't send
another signal since it's also waiting for a signal before it will
continue.
You my argue that that is a rather unlikely scenario, but, according
to Murphys law, it will happen, and, due to the collary to Murphys
law, probably at the worst possible moment, i.e. when you proudly
present your new program to an important customer. Moreover, since
it happens so rarely finding the reason is often difficult, because
the program may run flawlessly for days and only get stuck at random
moments. So a better scheme is required.
One thing one you might try is to set a flag in the signal handler
and test it before calling pause(), i.e. if the flag is set, indi-
cating that the signal handler has already been invoked, you would
not call pause() anymore but continue immediately. But if you look
carefully, you will find that this only shifts the race condition
to a different place, i.e. if you have
if ( ! signal_has_already_been_raised_flag )
pause( );
signal_has_already_been_raised_flag = 0;
the process could be stopped by the kernel between the check of
the flag and calling pause() and you're back to square one. What
is needed is a method to test and going to sleep in an atomic way,
i.e. it needs to be done in a way where you have a guarantee that
the kernel won't put the process to sleep in between and run a
different process.
Others have already pointed out semaphores as the solution. They
actually were created for exactly this kind of situations and are
thus the most natural method to use (and probably also the fastest).
Unfortunately, creating and dealing with semaphores isn't very simple,
you need to do quite a bit of reading about the API since it's not
very intuitive. If you want to use them have a look at the man
pages for semget(2), semctl(2) and semop(2). Another problem with
them is that they aren't owned by a single process, so the last
process must do some clean up or they will stay in kernel memory
until you reboot the machine (or delete them manually using the
ipcrm(1) utility) - it's not like with other resources that get
automatically deleted once the process exits.
So I am going to demonstrate a probably slower but easier to imple-
ment method for synchronizing the two processes. This works by
creating two pipes between the processes. Each process is putting
a single char into one of the pipes after it has written its output
and then starts to read on the other pipe, waiting until it receives
a single character. In this case no signals are needed. To do that,
you must create two pipes before you call fork(). The first pipe
is the one the parent is writing to and the child is reading from
and the other one is for sending the character the other way (some
systems have two-way pipes, so on them you would only need a single
pipe, but, since many system have only unidirectional pipes, it's
safer not to rely on that). Here's what you need to create the two
pipes:
int p2c[ 2 ], c2p[ 2 ];
pipe( p2c );
pipe( c2p )
Each pipe needs two file descriptors (integers), where the first one
is used for reading and the second one for writing (imagine them
as the two ends of the pipe). Using the above you now have a pipe
for parent-to-child communication (p2c) and one for the other way
round (c2p). When you call the function pipe(2) you of course should
check the return value for errors, but I leave that out here for
sake of brevity.
Now, with that pipes created you would have (after the fork()) in
the parent:
unsigned char flag;
while ( 1 ) {
if ( read( c2p[ 0 ], &flag, 1 ) != 1 ) {
fprintf( stderr, "Child must be dead, quitting\n" );
exit( EXIT_SUCCESS );
}
/* Do whatever you want to do in the parent here */
write( p2c[ 1 ], &flag, 1 );
}
and in the child:
unsigned char flag = 'x';
while ( 1 ) {
/* Do whatever you want to do in the child here */
write( c2p[ 1 ], &flag, 1 );
if ( read( p2c[ 0 ], &flag, 1 ) != 1 ) {
fprintf( stderr, "Parent must be dead, quitting\n" );
exit( EXIT_SUCCESS );
}
}
This should work reliably since (unless you explicitely change the
code to do non-blocking reading) the call of read() will block until
a single character has been received. It only returns prematurely,
i.e. without having read a single character, when the process on
the other side of the pipe has died.
One thing that's still missing from the above example is what
happens when the other process died just before you start to write
to it. In that case the other end of the pipe isn't connected to
anything anymore that could read what you write. And in that case
you will get a signal, SIGPIPE, and write() will return -1. Per
default the SIGPIPE signal will kill the writing process. But, of
course, you can catch that signal and then see from the return
value of write() that something went wrong and determine what
happened from errno, which then is going to be set to EPIPE.
Finally, another thing that needs to be taken into consideration,
at least for a "real" program, is the case that some other signal
is received by your process either while you start to write() or
while you're waiting in read(). In both cases the functions will
return immediately (unless you have set things up in a way, again
using using sigaction() that system calls like read() and write()
automatically get restarted after a signal). You can find about
that by checking the return value (it's going to be -1) and check
if errno has been set to EINTR.
Regards, Jens
-- \ Jens Thoms Toerring ___ Jens.Toerring@physik.fu-berlin.de \__________________________ http://www.toerring.de
- Next message: Jens.Toerring_at_physik.fu-berlin.de: "Re: Driver Development standards"
- Previous message: moi: "Re: how to tell if an open file has been removed"
- In reply to: Daniel: "Executing 2 processes "fork()" alternatively"
- Next in thread: Pascal Bourguignon: "Re: Executing 2 processes "fork()" alternatively"
- Reply: Pascal Bourguignon: "Re: Executing 2 processes "fork()" alternatively"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Relevant Pages
|
|