Tag Archives: shared memory

Parallel RT Processes with Same Periodic Cycles

For many control applications, multiple RT processes need to run with periodic cycles.

This article presents an example code that enables two RT applications to run with a same periodic cycle. Two RT processes share one shared memory to pass the reference start time of the thread.

Once two processes are synchronized, then two processes control their cycles with “CLOCK_MONOTONIC” clock.

The code for the first app code

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include "shared_memory_base.h"

#define TIMESPEC_ADD(A,B) /* A += B */ \
    do {                                   \
    (A).tv_sec += (B).tv_sec;          \
    (A).tv_nsec += (B).tv_nsec;        \
    if ( (A).tv_nsec >= 1000000000 ) { \
    (A).tv_sec++;                  \
    (A).tv_nsec -= 1000000000;     \
    }                                  \
    } while (0)

#define TIMESPEC_SUB(A,B) /* A -= B */ \
    do {                                   \
    (A).tv_sec -= (B).tv_sec;          \
    (A).tv_nsec -= (B).tv_nsec;        \
    if ( (A).tv_nsec < 0 ) {           \
    (A).tv_sec--;                  \
    (A).tv_nsec += 1000000000;     \
    }                                  \
    } while (0)


void *thread_func ( void *param )
{
    FILE *fp;
    char filename_fp[50];
    shared_memory_base comm;
    comm.init();
    comm.data->first_app_on=1;

    struct timespec t_1us;
    t_1us.tv_sec = 0; t_1us.tv_nsec=1000;


    long thread_id = (long) param;

    sprintf (filename_fp, "%ld%s", thread_id, ".txt");
    fp = fopen(filename_fp, "w");//opening file

    struct timespec t_next, period, t_now, t_prev, t_diff;

    /* period = 0.5 ms * thread_id */
    period.tv_sec = 0;
    period.tv_nsec = ( 1 ) * 500000; // a x ms

    comm.data->first_app_on = 1;
    while (comm.data->second_app_on == 0);
    clock_nanosleep ( CLOCK_MONOTONIC, 0, &t_1us, NULL );   // wait 1us for second app till it set up the t_ref
    t_now = comm.data->t_ref;
    t_next = t_now;
    t_prev = t_now;

    int t_jitter[100];
    for (int i=0;i<100;i++) t_jitter[i] = 0;
    int t_j = 0;

    for ( int i = 0; i < 10000; i++ )
    {
        clock_gettime ( CLOCK_MONOTONIC, &t_now );
        t_diff = t_now;
        TIMESPEC_SUB ( t_diff, t_prev );
        t_prev = t_now;
        t_j = t_diff.tv_nsec / 10000;
        if (t_j<0) t_jitter[0]++;
        else if (t_j>99) t_jitter[99]++;
        else t_jitter[t_j]++;
        if(i%1000==1) printf("second: %d\n",i);
        TIMESPEC_ADD ( t_next, period );
        clock_nanosleep ( CLOCK_MONOTONIC, TIMER_ABSTIME, &t_next, NULL );
    }

    for (int i=0;i<100;i++) {
        printf("%d ",t_jitter[i]);
        fprintf(fp, "%d\n",t_jitter[i]);
    }
    fclose(fp);
    comm.detach_shared_memory();
    return NULL;
}

int main ()
{
    int policy;
    struct sched_param prio;
    pthread_attr_t attr;

    pthread_t tid1;

    policy = SCHED_OTHER;
    if (pthread_setschedparam( pthread_self(),policy, &prio )){
        perror ("Error: pthread_setschedparam (root permission?)");
        exit(1);
    }

    pthread_attr_init( &attr);
    pthread_attr_setinheritsched( &attr, PTHREAD_EXPLICIT_SCHED);
    policy = SCHED_RR;
    pthread_attr_setschedpolicy( &attr, policy);
    prio.sched_priority = 1; // priority range should be btw -20 to +19
    pthread_attr_setschedparam(&attr,&prio);

    if ( pthread_create(&tid1, &attr, thread_func, (void *)(1)) ){
        perror ( "Error: pthread1_create" );
        return 1;
    }

    /* wait for threads to finish */
    pthread_join ( tid1, NULL );

    return 0;
}

The code for the second application

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include "shared_memory_base.h"

#define TIMESPEC_ADD(A,B) /* A += B */ \
    do {                                   \
    (A).tv_sec += (B).tv_sec;          \
    (A).tv_nsec += (B).tv_nsec;        \
    if ( (A).tv_nsec >= 1000000000 ) { \
    (A).tv_sec++;                  \
    (A).tv_nsec -= 1000000000;     \
    }                                  \
    } while (0)

#define TIMESPEC_SUB(A,B) /* A -= B */ \
    do {                                   \
    (A).tv_sec -= (B).tv_sec;          \
    (A).tv_nsec -= (B).tv_nsec;        \
    if ( (A).tv_nsec < 0 ) {           \
    (A).tv_sec--;                  \
    (A).tv_nsec += 1000000000;     \
    }                                  \
    } while (0)


void *thread_func ( void *param )
{
    FILE *fp;
    char filename_fp[50];
    shared_memory_base comm;
    comm.init();    // start the shared memory communication

    // get the file name
    long thread_id = (long) param;
    sprintf (filename_fp, "%ld%s", thread_id, ".txt");
    fp = fopen(filename_fp, "w");//opening file

    struct timespec t_next, period, t_now, t_prev, t_diff;

    /* period = 0.5 ms * thread_id */
    period.tv_sec = 0;
    period.tv_nsec = ( 1 ) * 500000; // a x ms

    comm.data->second_app_on = 1; // let the second app go
    while( comm.data->first_app_on == 0 );
    clock_gettime ( CLOCK_MONOTONIC, &t_now );
    comm.data->t_ref = t_now;
    t_next = t_now;
    t_prev = t_now;

    int t_jitter[100];
    for (int i=0;i<100;i++) t_jitter[i] = 0;
    int t_j = 0;


    for ( int i = 0; i < 10000; i++ )
    {
        clock_gettime ( CLOCK_MONOTONIC, &t_now );
        t_diff = t_now;
        TIMESPEC_SUB ( t_diff, t_prev );
        t_prev = t_now;
        t_j = t_diff.tv_nsec / 10000;
        if (t_j<0) t_jitter[0]++;
        else if (t_j>99) t_jitter[99]++;
        else t_jitter[t_j]++;
        if(i%1000==1) printf("first: %d\n",i);
        TIMESPEC_ADD ( t_next, period );
        clock_nanosleep ( CLOCK_MONOTONIC, TIMER_ABSTIME, &t_next, NULL );
    }

    for (int i=0;i<100;i++) {
        printf("%d ",t_jitter[i]);
        fprintf(fp, "%d\n",t_jitter[i]);
    }
    fclose(fp);
    comm.detach_shared_memory();
    return NULL;
}

int main ()
{
    int policy;
    struct sched_param prio;
    pthread_attr_t attr;

    pthread_t tid1;

    policy = SCHED_OTHER;
    if (pthread_setschedparam( pthread_self(),policy, &prio )){
        perror ("Error: pthread_setschedparam (root permission?)");
        exit(1);
    }

    pthread_attr_init( &attr);
    pthread_attr_setinheritsched( &attr, PTHREAD_EXPLICIT_SCHED);
    policy = SCHED_RR;
    pthread_attr_setschedpolicy( &attr, policy);
    prio.sched_priority = 1; // priority range should be btw -20 to +19
    pthread_attr_setschedparam(&attr,&prio);

    if ( pthread_create(&tid1, &attr, thread_func, (void *)(2)) ){
        perror ( "Error: pthread1_create" );
        return 1;
    }

    /* wait for threads to finish */
    pthread_join ( tid1, NULL );

    return 0;
}

The result of the execution of two programs

mok@mok-master-s:~/dev$ mkdir build
 mok@mok-master-s:~/dev$ cd build
 mok@mok-master-s:~/dev/build$ cmake ../rt_periodic_thread
 -- The CXX compiler identification is GNU 7.4.0
 -- Check for working CXX compiler: /usr/bin/c++
 -- Check for working CXX compiler: /usr/bin/c++ -- works
 -- Detecting CXX compiler ABI info
 -- Detecting CXX compiler ABI info - done
 -- Detecting CXX compile features
 -- Detecting CXX compile features - done
 -- Configuring done
 -- Generating done
 -- Build files have been written to: /home/mok/dev/build
 mok@mok-master-s:~/dev/build$ make
 Scanning dependencies of target first_app
 [ 16%] Building CXX object CMakeFiles/first_app.dir/first_rt_app.cpp.o
 [ 33%] Building CXX object CMakeFiles/first_app.dir/shared_memory_base.cpp.o
 [ 50%] Linking CXX executable first_app
 [ 50%] Built target first_app
 Scanning dependencies of target second_app
 [ 66%] Building CXX object CMakeFiles/second_app.dir/second_rt_app.cpp.o
 [ 83%] Building CXX object CMakeFiles/second_app.dir/shared_memory_base.cpp.o
 [100%] Linking CXX executable second_app
 [100%] Built target second_app
 mok@mok-master-s:~/dev/build$ mv ../rt_periodic_thread/runboth.sh ./
 mok@mok-master-s:~/dev/build$ chmod a+x runboth.sh 
 mok@mok-master-s:~/dev/build$ sudo ./runboth.sh 
 [sudo] password for mok: 
 second: 1
 first: 1
 second: 1001
 first: 1001
 second: 2001
 first: 2001
 first: 3001
 second: 3001
 second: 4001
 first: 4001
 first: 5001
 second: 5001
 first: 6001
 second: 6001
 first: 7001
 second: 7001
 second: 8001
 first: 8001
 second: 9001
 first: 9001
 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 60 2065 675 2339 2058 573 2187 32 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 7 40 2179 518 2484 2008 449 2269 37 5 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 mok@mok-master-s:~/dev/build$ ^C

Download and Build

https://drive.google.com/file/d/1kTSBW3HqnMccRxj6cudEWy5nLYXzAuuY/view?usp=sharing

You can build and run the two applications with the command below:

mkdir build
cd build
cmake ../rt_periodic_thread
make
mv ../rt_periodic_thread/runboth.sh ./
chmod a+x runboth.sh 
sudo ./runboth.sh 

Other Topics

Shared Memory IPC Ping-pong Sequence

Overview

In this post, I am sharing an example of IPC communication with a class using shared memory.

Two processes runs together, and their applications sequentially running. The end of each loop is marked at a shared memory with a “go-flag”. Once the other process see the end-of-the-loop flag, “go-flag”, the process continues the task.

Shared Memory IPC Class

I wrote this class to make the shared memory programming more portable. What you need to do is only to change the struct called “shared_memory_packet. You will see how to use this class in the examples of the next section. You can see the code of “shared_memory_base.h” and “shared_memory_base.cpp” below:

#ifndef SHARED_MEMORY_BASE_H
#define SHARED_MEMORY_BASE_H

#include <sys/ipc.h>
#include <sys/shm.h>

#define DEFAULT_KEY_ID  5700        // default key for shared memory

class shared_memory_base
{
    struct shared_memory_packet {
        float analog_input[16];
        bool go_flag_second_loop = 1;
        bool go_flag_first_loop = 1;
    };

private:
    key_t key;
    int shmid = 0;
public:
    shared_memory_base();
    ~shared_memory_base();

    shared_memory_packet* data;
    void init();
    void change_shared_memory_key(key_t k){key= k;} // only use this function before init
    void detach_shared_memory();
};

#endif // SHARED_MEMORY_BASE_H
#include "shared_memory_base.h"

shared_memory_base::shared_memory_base()
{
    key = ftok("shmfile",DEFAULT_KEY_ID);
}

shared_memory_base::~shared_memory_base(){
    detach_shared_memory();
}

void shared_memory_base::init(){
    // shmget returns an identifier in shmid
    shared_memory_packet temp;
    shmid = shmget(key, sizeof(temp),0666|IPC_CREAT);
    // shmat to attach to shared memory
    data = (shared_memory_packet*) shmat(shmid,(void*)0,0);
}

void shared_memory_base::detach_shared_memory(){
    shmdt(data);
    shmctl(shmid,IPC_RMID,nullptr);
}

Ping-pong Sequence between Two Processes

In this example, “first_app.cpp” the number of an integer variable incrementally, and change the “go_flag_second_loop” to be one. In “second_app.cpp” the number of an integer variable is reduced, and change the “go_flag_first_loop” to be one. With this two go_flags two processes run sequencially.

#include <iostream>
#include <unistd.h>
#include "shared_memory_base.h"

using namespace std;

int main()
{
    shared_memory_base comm;
    comm.init();

    comm.data->analog_input[0] =0;
    comm.data->analog_input[8] =0;

    for (int i=0;i<10;i++){
        while( comm.data->go_flag_first_loop != 1);
        comm.data->analog_input[0] = i;
        cout << "ai[8]: " << comm.data->analog_input[8] << endl;
        comm.data->go_flag_second_loop = 1;
        sleep(1);
    }

    cout << "end of the loop!" << endl;
    comm.detach_shared_memory();
    return 0;
}
#include <iostream>
#include "shared_memory_base.h"

using namespace std;

int main()
{
    shared_memory_base comm;
    comm.init();

    comm.data->analog_input[0] =0;
    comm.data->analog_input[8] =0;
    comm.data->go_flag_first_loop = 1;

    for (int i=0;i<10;i++){
        while( comm.data->go_flag_second_loop != 1);
        comm.data->analog_input[8] = 1000-i;
        cout << "ai[0]: " << comm.data->analog_input[0] << endl;
        comm.data->go_flag_second_loop = 0;
    }

    cout << "end of the loop!" << endl;
    comm.detach_shared_memory();
    return 0;
}

Result

The figures below shows the execution results of two applications.

Execution Result of “First_app”
Execution Result of “Second_app”

Download and Build the Codes

You can download the entire code and can build the code with the command below:

Download link

mkdir build
cd build
cmake ../shared_memory_example
make

More discussion

Concurrency problem is a very well-known and challenging problem. There are many other nice techniques other than this one. Particularly after C++11, the standard C++ has introduced a nice set of concurrent thread and process running functions. In addition, this example is not the best code for efficient applications.

I found a very good reference explaining Concurrency, Parallelism, Threads, Processes, Async and Sync. For the details, read the article in the link below:

https://medium.com/swift-india/concurrency-parallelism-threads-processes-async-and-sync-related-39fd951bc61d