user1434287
user1434287

Reputation: 401

how synchronization is done in shared memory data linux c

I was asked a question in interview, how synchronization is done in shared memory. I told Take a struct. In that you have a flag and a data. Test the flag and change the data. I took the following program from internet as below-. Can anyone tell if there is better way of synchronization in shared memory

#define  NOT_READY  -1
#define  FILLED     0
#define  TAKEN      1

struct Memory {
     int  status;
     int  data[4];
};

Assume that the server and client are in the current directory. The server uses ftok() to generate a key and uses it for requesting a shared memory. Before the shared memory is filled with data, status is set to NOT_READY. After the shared memory is filled, the server sets status to FILLED. Then, the server waits until status becomes TAKEN, meaning that the client has taken the data.

The following is the server program. Click here to download a copy of this server program server.c.

#include  <stdio.h>
#include  <stdlib.h>
#include  <sys/types.h>
#include  <sys/ipc.h>
#include  <sys/shm.h>

#include  "shm-02.h"

void  main(int  argc, char *argv[])
{
     key_t          ShmKEY;
     int            ShmID;
     struct Memory  *ShmPTR;

     if (argc != 5) {
          printf("Use: %s #1 #2 #3 #4\n", argv[0]);
          exit(1);
     }

     ShmKEY = ftok(".", 'x');
     ShmID = shmget(ShmKEY, sizeof(struct Memory), IPC_CREAT | 0666);
     if (ShmID < 0) {
          printf("*** shmget error (server) ***\n");
          exit(1);
     }
     printf("Server has received a shared memory of four integers...\n");

     ShmPTR = (struct Memory *) shmat(ShmID, NULL, 0);
     if ((int) ShmPTR == -1) {
          printf("*** shmat error (server) ***\n");
          exit(1);
     }
     printf("Server has attached the shared memory...\n");

     ShmPTR->status  = NOT_READY;
     ShmPTR->data[0] = atoi(argv[1]);
     ShmPTR->data[1] = atoi(argv[2]);
     ShmPTR->data[2] = atoi(argv[3]);
     ShmPTR->data[3] = atoi(argv[4]);
     printf("Server has filled %d %d %d %d to shared memory...\n",
            ShmPTR->data[0], ShmPTR->data[1], 
            ShmPTR->data[2], ShmPTR->data[3]);
     ShmPTR->status = FILLED;

     printf("Please start the client in another window...\n");

     while (ShmPTR->status != TAKEN)
          sleep(1);

     printf("Server has detected the completion of its child...\n");
     shmdt((void *) ShmPTR);
     printf("Server has detached its shared memory...\n");
     shmctl(ShmID, IPC_RMID, NULL);
     printf("Server has removed its shared memory...\n");
     printf("Server exits...\n");
     exit(0);
}

The client part is similar to the server. It waits until status is FILLED. Then, the clients retrieves the data and sets status to TAKEN, informing the server that data have been taken. The following is the client program. Click here to download a copy of this server program client.c.

#include  <stdio.h>
#include  <stdlib.h>
#include  <sys/types.h>
#include  <sys/ipc.h>
#include  <sys/shm.h>

#include  "shm-02.h"

void  main(void)
{
     key_t          ShmKEY;
     int            ShmID;
     struct Memory  *ShmPTR;

     ShmKEY = ftok(".", 'x');
     ShmID = shmget(ShmKEY, sizeof(struct Memory), 0666);
     if (ShmID < 0) {
          printf("*** shmget error (client) ***\n");
          exit(1);
     }
     printf("   Client has received a shared memory of four integers...\n");

     ShmPTR = (struct Memory *) shmat(ShmID, NULL, 0);
     if ((int) ShmPTR == -1) {
          printf("*** shmat error (client) ***\n");
          exit(1);
     }
     printf("   Client has attached the shared memory...\n");

     while (ShmPTR->status != FILLED)
          ;
     printf("   Client found the data is ready...\n");
     printf("   Client found %d %d %d %d in shared memory...\n",
                ShmPTR->data[0], ShmPTR->data[1], 
                ShmPTR->data[2], ShmPTR->data[3]);

     ShmPTR->status = TAKEN;
     printf("   Client has informed server data have been taken...\n");
     shmdt((void *) ShmPTR);
     printf("   Client has detached its shared memory...\n");
     printf("   Client exits...\n");
     exit(0);
}

Upvotes: 1

Views: 9291

Answers (1)

Nominal Animal
Nominal Animal

Reputation: 39298

Can anyone tell if there is better way of synchronization in shared memory?

Definitely, yes. I would say the way you waste CPU cycles in busy-wait (while (ShmPTR->status != FILLED) ;) is already a fatal mistake.

Note that POSIX shared memory has a much more sensible interface than the old SysV does. See man 7 shm_overview for details.

There are two distinct purposes for synchronization primitives:

  1. Data synchronization

    To protect data against concurrent modification, and to ensure each reader gets a consistent view of the data, there are three basic approaches:

    • Atomic access

      Atomic access requires hardware support, and is typically only supported for native machine word sized units (32 or 64 bits).
       

    • Mutexes and condition variables

      Mutexes are mutually exclusive locks. The idea is to grab the mutex before examining or modifying the value.

      Condition variables are basically unordered queues for threads or processes to wait for a "condition". POSIX pthreads library includes facilities for atomically releasing a mutex and waiting on a condition variable. This makes waiting for a dataset to change trivial to implement, if each modifier signals or broadcasts on the condition variable after each modification.
       

    • Read-write locks.

      An rwlock is a primitive that allows any number of concurrent "read locks", but only one "write lock" to be held on it at any time. The idea is that each reader grabs a read lock before examining the data, and each writer a write lock before modifying it. This works best when the data is more often examined than modified, and a mechanism for waiting for a change to occur is not needed.
       

  2. Process synchronization

    There are situations where threads and processes should wait (block) until some event has occurred. There are two most common primitives used for this:

    • Semaphores

      A POSIX semaphore is basically an opaque nonnegative counter you initialize to whatever (zero or positive value, within the limits set by the implementation).

      sem_wait() checks the counter. If it is nonzero, it decrements the counter and continues execution. If the counter is zero, it blocks until another thread/process calls sem_post() on the counter.

      sem_post() increments the counter. It is one of the rare synchronization primitives you can use in a signal handler.
       

    • Barriers

      A barrier is a synchronization primitive that blocks until there is a specific number of threads or processes blocking in the barrier, then releases them all at once.

      Linux does not implement POSIX barriers (pthread_barrier_init(), pthread_barrier_wait(), pthread_barrier_destroy()), but you can easily achieve the same using a mutex, a counter (counting the number of additional processes needed to release all waiters), and a condition variable.
       


There are many better ways of implementing the said server-client pair (where shared memory contains a flag and some data).

For data integrity and change management, a mutex and one or two condition variables should be used. (If the server may change the data at any time, one condition variable (changed) suffices; if the server must wait until a client has read the data before modifying it, two are needed (changed and observed).)

Here is an example structure you could use to describe the shared memory segment:

#ifndef   SHARED_H
#define   SHARED_H
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

struct shared_data {
    /* Shared memory data */
};

struct shared {
    pthread_mutex_t     lock;
    pthread_cond_t      change;     /* Condition variable for clients waiting on data changes */
    pthread_cond_t      observe;    /* Condition variable for server waiting on data observations */
    unsigned long       changed;    /* Number of times data has been changed */
    unsigned long       observed;   /* Number of times current data has been observed */
    struct shared_data  data;
};

/* Return the size of 'struct shared', rounded up to a multiple of page size. */
static inline size_t  shared_size_page_aligned(void)
{
    size_t  page, size;
    page = (size_t)sysconf(_SC_PAGESIZE);
    size = sizeof (struct shared) + page - 1;
    return size - (size % page);
}

#endif /* SHARED_H */

The changed and observed fields are counters, that help avoid any time-of-check-to-time-of-use race windows. It is important that before the shared memory is accessed the thread does pthread_mutex_lock(&(shared_memory->lock)), to ensure a consistent view of the data.

If a thread/process examines the data, it should do

    shared_memory->observed++;
    pthread_cond_broadcast(&(shared_memory->observe));
    pthread_mutex_unlock(&(shared_memory->lock));

and if a thread/process modifies the data, it should do

    shared_memory->modified++;
    shared_memory->observed = 0;
    pthread_cond_broadcast(&(shared_memory->change));
    pthread_mutex_unlock(&(shared_memory->lock));

to notify any waiters and update the counters, when unlocking the mutex.

Upvotes: 6

Related Questions