Christian Kruse: Locking-Mechanismus auf ein Shared-Memory-Segment

Hallo zusammen,

ich habe geringfuegige Probleme bei der Synchronisation auf ein
Shared-Memory-Segment gehabt. Ich habe ein Shm-Segment, auf
das lesend von mehreren Prozessen gleichzeitig zugegriffen
werden darf und schreibend nur von einem Prozess. Ich habe da
mal unter Zurhilfenahme eines Semaphores etwas gebastelt. Was
meint ihr, ist das so wasserdicht?

/*
 * pv_locking.h
 * Implementation of a read-write lock using the P/V locking algorithm described by Dijkstra
 *
 * $Header$
 * $Log$
 *
 */

/* lock ressource */
#define P(id)  pv(id,-1)

/* unlock ressource */
#define V(id)  pv(id,1)

/* we wait 10 microseconds for a lock before checking again */
#define PV_WAIT_TIME 10L

/* the P/V lock routine */
int pv(int,int);

/* lock and unlock exclusive (writer) locks */
int pv_lock_exclusive(int,void *);
int pv_unlock_exclusive(int,void *);

/* lock and unlock shared (reader) locks */
int pv_lock_shared(int,void *);
int pv_unlock_shared(int,void *);

/* eof */

/*
 * pv_locking.c
 * Implementation of a read-write lock using the P/V locking algorithm described by Dijkstra.
 *
 * We use a part of the shared memory segment to signal that we wanna use this segment shared or
 * exclusive. If the exclusive bit has been set, we don't allow any shared access until the
 * exclusive bit has been released.
 *
 * Since we don't now how to wake up processes, we use usleep() to check the bits in an defined
 * interval and to avoid busy waits.
 *
 * $Header$
 * $Log$
 *
 */

#include "pv_locking.h"

/*
 * implementation of the P/V lock itself. We decrement the semaphore
 * when we reach a critical area and im inkrement the semaphore again
 * when the end of the critical area has been reached
 */
int pv(int id,int operation) {
  struct sembuf semaphor;
  semaphor.sem_op   = operation;
  semaphor.sem_flag = SEM_UNDO;

return semop(id,&semaphor,1);
}

/*
 * We want to lock the shared memory segment exclusively (e.g. for write access).
 * Therefore we have to set the exclusive-bit and we have to wait until the shared
 * counter is 0. The semaphore has to be locked during the exclusive operations, so
 * we *DON'T* unlock it in this routine!
 */
int pv_lock_exclusive(int semid,void *shmsegment) {
  int *bts = (int *)shmsegment;

if(P(semid) != 0) return -1;

/*
   * this should not happen ever. If this happens, we have a bug since
   * exclusive access means, the semaphore is *locked* (e.g. smaller than 0)
   */
  if(bts[0] == 1) {
    V(semid);
    return -1;
  }

bts[0] = 1;

/* we wait until all shared locks have been released */
  while(bts[1] != 0) {
    if(V(semid) != 0) return -1;
    usleep(PV_WAIT_TIME);
    if(P(semid) != 0) return -1;
  }

return 0;
}

/*
 * we release an exclusive lock by unsetting the exclusive bit and unlocking
 * the semaphore
 */
int pv_unlock_exclusive(int semid,void *shmsegment) {
  if(bts[0] == 0) return -1;
  bts[0] = 0;

return V(semid);
}

/*
 * We can lock a shared memory segment shared if the exclusive bit is *not* set
 * and the semaphore is *not* locked. The shared lock itself is just a incrementation
 * of the shared counter. The semaphore will be unlocked at the end of this routine,
 * but the shared counter has to be decremented, too.
 */
int pv_lock_shared(int semid,void *shmsegment) {
  if(P(semid) != 0) return -1;

/* we wait for the exclusive lock */
  while(bts[0]) {
    if(V(semid) != 0) return -1;
    usleep(PV_WAIT_TIME);
    if(P(semid) != 0) return -1;
  }

bts[1]++;

return V(semid);
}

/*
 * We release the shared lock by decrementing the shared counter.
 */
int pv_unlock_shared(int semid,void *shmsegment) {
  if(P(semid) != 0) return -1;
  if(bts[1] == 0) {
    V(semid);
    return -1;
  }

bts[1]--;
  return V(semid);
}

/* eof */

Das Prinzip ist im Grunde recht einfach: ich benutze einen
P/V-Lock, um exklusiven Zugriff auf das Shm-Segment zu
erhalten. Dann setze ich bei einem exklusiven Lock ein Bit
(eigentlich ja 4, aber das ist im Moment ja egal ;) und warte
darauf, dass der Zaehler der nicht-exklusiven Locks auf 0
wandert. Die Semaphore bleibt beim Verlassen der Lock-Routine
gesperrt (< 0)! Soll der Lock wieder aufgehoben werden, so
setze ich das "Exklusiv-Bit" wieder auf 0 und entriegle die
Semaphore.

Bei einem nicht-exklusiven Lock schaue ich, ob das
"Exklusiv-Bit" gesetzt ist. Wenn ja, dann warte ich darauf,
dass es wieder 0 wird. Danach setze ich einen Zaehler eins
hoeher, um anzuzeigen, dass ein Prozess einen Shared-Lock
gemacht hat. Wird dieser Lock wieder aufgehoben, so wird
schlicht der Zaehler wieder um eins dekrementiert.

Gruesse,
 CK

  1. hi!

    ich habe geringfuegige Probleme bei der Synchronisation auf ein
    Shared-Memory-Segment gehabt. Ich habe ein Shm-Segment, auf
    das lesend von mehreren Prozessen gleichzeitig zugegriffen
    werden darf und schreibend nur von einem Prozess. Ich habe da
    mal unter Zurhilfenahme eines Semaphores etwas gebastelt. Was
    meint ihr, ist das so wasserdicht?

    Ohne jetzt deinen Source angeschaut zu haben: dieser Fall ist in der
    Informatik bekannt als "Readers and Writers Problem" und es gibt
    bereits wasserdichte Lösungen für dieses Problem. Wirf mal Google
    danach an (oder schau in den Tanenbaum, Modern Operating Systems,
    falls du da rankommst).

    bye, Frank!

    --
    Never argue with an idiot. He will lower you to his level and then
    beat you with experience.