Below is one approach to implementing a state machine in C. In the example below, I used forking to create the processes you mentioned and I used a thread to emulate some input that transitions the state. I've used this pattern for cases where I have a device that implements a state machine with input from some external source.
This can be done a bunch of ways, but the core idea behind this approach is to have the worker threads (or processes) watching a state variable, have a main thread (or process) watching a state transition variable(s) and changing the state variable accordingly, and then an independent thread (or process) that changes the state transition variable(s) according to the external data received.
Keep in mind that it is necessary for the main thread to "reset" the state transition variable so that it doesn't immediately transition out of the new state. I did this by creating a NO OP transition state. Since both the main and ExternalSource write to the state transition variable, I used a mutex for protection.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <string.h>
pthread_mutex_t mutex;
typedef enum _State{
EXECUTE,
IDLE,
CALCULATE
} State;
typedef enum _StateTransition{
TRANS_EXECUTE,
TRANS_IDLE,
TRANS_CALCULATE,
TRANS_NOOP
} StateTransition;
void Calculate(State *state_p){
int flag = 0; // Only calculate once per state transition
while (1){
if ((*state_p == CALCULATE) && flag){
puts("Calculated");
flag = 0;
} else if (*state_p != CALCULATE){
flag = 1;
}
}
}
void Execute(State *state_p){
int flag = 0; // Only execute once per state transition
while (1){
if ((*state_p == EXECUTE) && flag){
puts("executed");
flag = 0;
} else if (*state_p != EXECUTE){
flag = 1;
}
}
}
void * ExternalSource(void * param){
StateTransition * transition_p = (StateTransition*) param;
// Emulate External input
while(1){
sleep(2);
puts("Transiton to idle");
pthread_mutex_lock(&mutex);
*transition_p = TRANS_IDLE;
pthread_mutex_unlock(&mutex);
sleep(2);
puts("Transition to calculate");
pthread_mutex_lock(&mutex);
*transition_p = TRANS_CALCULATE;
pthread_mutex_unlock(&mutex);
sleep(2);
puts("Transition to execute");
pthread_mutex_lock(&mutex);
*transition_p = TRANS_EXECUTE;
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_t externalThread;
int rv;
pid_t pidExecute;
pid_t pidCalculate;
volatile StateTransition transition;
State * state;
// Setup Initial Values
transition = TRANS_NOOP;
state = mmap(NULL, sizeof(State), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
*state = IDLE;
// Create Processes for Calculate and Execute
pidExecute = fork();
if (pidExecute == 0){
Execute(state);
} else{
pidCalculate = fork();
if (pidCalculate == 0){
Calculate(state);
}
}
// Create Thread for Input Emulation
rv = pthread_create(&externalThread, NULL, ExternalSource, (void*)&transition);
if (rv){
perror("ERROR Unable to create thread");
return -1;
}
while(1){ //Implement State Machine
switch (*state){
case IDLE:
if (transition == TRANS_CALCULATE){
// Do some transition stuff
*state = CALCULATE; // Update new state
pthread_mutex_lock(&mutex);
transition = TRANS_NOOP; // Reset transition
pthread_mutex_unlock(&mutex);
}
break;
case CALCULATE:
if (transition == TRANS_EXECUTE){
// Do some transition stuff
*state = EXECUTE; // Update new state
pthread_mutex_lock(&mutex);
transition = TRANS_NOOP; // Reset transition
pthread_mutex_unlock(&mutex);
}
break;
case EXECUTE:
if (transition == TRANS_IDLE){
// Do some transition stuff
*state = IDLE; // Update new state
pthread_mutex_lock(&mutex);
transition = TRANS_NOOP; // Reset transition
pthread_mutex_unlock(&mutex);
}
break;
default:
break;
}
}
return 0;
}
This code does compile, but it is not platform independent. This is really just to demonstrate the idea. If you're going to use this code as a starting place, make sure understand what's going on in each line, or else it will end up taking you more time in the long run.