IPC

From Control Systems Technology Group
Jump to navigation Jump to search

Inter Process Communication Using Shared Memory

For our project, the Football Table a synchronized high speed inter process communication(IPC) was necessary. For us to allow communication between our Motion Loop (in Simulink) and Gazebo. For simulation purposes we ideally want to run as fast possible, whilst retaining a causal link.

The library/communication makes use of the POSIX pthread library. Pthread was chosen for its compatibility with Matlab. For this work a very basic wrapper library was made for this project to deal clean up recurring parts of code. This library basically dumbs pthreads down a bit, making it more accesible. For advanced use i do not recommend using this. In our 'library' we used shared memory in combination with a mutex and a condition. For the sake of usability/adaptability this article will very shortly first discuss the basic principles used.

Shared Memory, Mutexes and Conditions

Shared Memory

Shared memory is one of the fastest methods available for inter process communication. Basically a segment of memory gets allocated to which multiple threads/programs have access. This way the processes can exchange data and synchronize with each other. Using shared memory however, is slightly tricky because a simultaneous read/write will cause undefined behavior (most likely a crash). To manage this, mutexes are used.

Mutexes

A mutex, or mutual exclusion, does exactly what its name describes. It provides a thread/program with exclusive access to a shared resource. It attains this exclusive access by 'locking' the mutex. In our case, we use it to control access to shared memory. As long as one process has a lock, no other process can access the data (using a basic mutex), hence ensuring thread-save behavior. There are other types of mutexes which operate slightly different (such as a readers-writers lock). By default if a process cannot get a lock, it will wait until it does. If you do not carefully programs you can easily get deadlocks.

Conditions

Synchronizing programs/threads one program might not be able to continue without data provided by the other. Conditions are a wonderful tool to perform this type of synchronization. They are always used in combination with a mutex and some condition variable (which can be anything). It's use is best described with an short example:

Process 1 will lock the shared memory, check if a condition is met, find out is not (process 2 has not updated data yet). It will wait (on a condition_signal) and automatically unlock the mutex. Once process 2 is ready to update it: locks the mutex, updates the data, signals process 1 (waking it) and unlocks the mutex. This is summarized in the table below for two processes counting together.

Process 1 SHM Count Process 2
Lock SHM 1 Can't Lock
if (uneven) wait; else count++ 1 Lock SHM
waiting 1 >> if (even) wait; else count++
waiting 2 << Unlock + Signal
Done waiting. Received lock <- 2 Can't Lock
Count++ >> 3 Can't Lock
Unlock 3 Lock SHM
Can't Lock 4 << if (even) wait; else count++

Usage in a feedback loop

Now, how do we use all this? Our goal is to place gazebo as the plant in our feedback loop in simulink and run as fast as possible.

Ipc control loop.png

As can be seen, gazebo now serves as the plant.

libshm_ipc

The library for this can be found on the Football Table SVN, called libshm_ipc. This is basically, thread-safe IPC for beginners, all functions are made so that the shared memory is not accessed unless a lock is achieved. The library functions should be quite self explanatory, however here i'll show a short example off how it is use in in S-function:

int timeout = 2; //time-out in full seconds
IPClient shm_client; //create an IPC client
shm_client.attach(shm_key);

  shm_client.lock(); //First we attempt to lock the shared memory
int ret = shm_client.flagWait(array_index,false,timeout); //We wait if the flag associated with array_index is false (giving up lock)
if(ret==ETIMEDOUT){printf("Timed out \n"); ssSetStopRequested(S,1); //If we timed-out we tell simulink to abort the simulation }else{ //If we are done waiting re-attaining lock, we start to read data (assign it to an output is this case)
for(int i=0; i<8; i++){
y[i] = shm_client.getData(i,0,array_index);//Reading and writing is done using functions usage: getData(row,column,array_index)
}
}
ret = shm_client.flagUpdate(array_index,false); //Here we reset the flag back to false, indicated that we've read it (no longer up-to-date)
shm_client.unlock();

The other program could do the opposite (wait if the flag is true -> write the position to the shm -> reset to true). This way both processes will take turns: one reading, the other writing. For a complete control loop we have matlab writing control commands and reading postions, the simulator does the opposite.

Gazebo plugin

The gazebo plugin does what was described in the previous paragraph, at the beginning of a physics step it reads and applies the control signals from simulink and the end of the simulator step, it updates the position to shared memory.

This plugin is universal, meaning that you can use it on any robot. There is only one part of code specific to the table