Semafor Ve Mutex Kavramları

Semafor Ve Mutex Kavramları

Kaynak

Tarih

Versiyon

İşlem

Zafer SATILMIŞ

2020-01-13

0.1

Döküman oluşturuldu

1. Amaç        2

2. Semaforlar        2

2.1 İsimlendirilmiş POSIX Semaforlar        3

2.1.a sem_open()        3

2.1.b sem_post()        4

2.1.c sem_wait()        4

2.1.d sem_trywait()        5

2.1.e sem_timedwait()        5

2.1.f sem_getvalue()        5

2.1.g sem_unlink()        6

2.2 İsimlendirilmiş POSIX Semaforlar Örneği        7

2.3 İsimlendirilmemiş POSIX Semaforlar        12

2.1.a sem_init()        12

2.1.b sem_destroy()        12

2.4 İsimlendirilmemiş POSIX Semaforlar Örneği        13


1. Amaç

Bu belgenin amacı Semafor ve Mutex kavramlarını açıklayıp aralarındaki farkları ve ortak özellikleri okuyucuya sunmaktır. Anlatımı pekiştirmek için POSIX fonksiyonları kullanılarak C dilinde örnekler verilmiştir.

2. Semaforlar

Semaforlar process ve thread sekronizasyonu için kullanılır. Mutex’ler ikilik semaforlardır. Mutex’ler aynı proses ve thread’leri tarafından kullanılır. Fakat semaforlar farklı prosesler tarafından da kullanılabilir.

Sekronizasyonu açmak gerekirse bellek paylaşımı, kritik kod bloğunun kullanımı, donanım elemanını sırayla kullanma gibi örnekler verebiliriz. İster embedded sistemlerde isterse de daha üst cihazlarda çalışılsın yukarıda verilen örneklerin gereksinimi ele alınmalıdır. Hiç bir donanım parçası kullanılmasa dahi en basitinden kritik kod bloğunu koruma durumu oluşacaktır. (ufak 1, 2 dosyadan oluşan projelerde göremeyebilirsiniz.)

İki çeşit semafor bulunmaktadır. Birincisi System V diğeri de POSIX semafordur. Biz bu belgede POSIX semafor ele alacağız. POSIX semaforlar 2.6 glibc versiyonundan sonra kullanılabilirdir ve kullanımı System V göre daha basittir.

POSIX semaforlar isimli ve isimsiz (named and unamed semaphores) semaforlar olmak üzere ikiye ayrılırlar. Her ne kadar kulağa aralarında sadede isimlendirme ya da isim kabul etme gibi bir fark varmış görülsede kullanım biçimi ve yetenekleri farklıdır. Fakat isimlendirilmiş semaforlar isimsiz semaforların kullanıldığı yerde kullanılabilir iken tersi mümkün değildir.

2.1 İsimlendirilmiş POSIX Semaforlar

İsimlendirilmiş semaforların oluşumu, tıpkı dosya işlemlerinde gerekli dosya ismi gibi kullanıcının belirlediği bir isim üzerinden oluşur. Belirlenen isim ile oluşturulan semafor diğer işlem birimleri(process) tarafından da bilindiğinde aynı semafora ulaşabilir. Yani kısaca bu tipteki semaforlar, tanımlanan bir isim üzerinden farklı process'ler arasında kullanılabilirler.

İsimlendirimiş semaforların kullanımı için gerekli POSIX fonksiyonları şu şekildedir:

sem_open() ile semafor oluşturulur veya kullanım için açılır.

sem_post() ile semaforun değeri artırılırken sem_wait() ile semaforun azaltılır

sem_getvalue() ile o anki değeri öğrenilir

sem_close() ile semafore erişim iptal edilir

sem_unlink() ile semafor tamamen silinmek üzere işaretlenir, semaforu kullanan tüm process'ler sem_close() yaptıktan sonra silme gerçekleşir.

2.1.a sem_open()

#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>

sem_t *sem_open (const char *name, int oflag);
sem_t *sem_open (const char *name, int oflag,  mode_t mode, unsigned int value);

  • sem_open() semaforu başlatmak için gerekli ilk fonksiyondur.
  • Verilen isimdeki semaforu oluşturur. Eğer bu isimde bir semafor daha öncesinden oluşturulmuş ilse sadece açar.
  • Burada kullanılan bit maskesi, dosya işlemlerinde kullanılanlarla aynıdır. Dolayısı ile birden fazla flag “ | ” işlemi kullanılarak fonksiyona iletilebilir.
  • O_CREAT ve O_EXCL bit maskeleri beraber kullanılır ise bu durumda belirtilen semaforun daha öncesinden var olması durumunda hata döner.
  • mode parametresi, tıpı dosyalara erişim iznindeki gibi semafora erişim iznini belirler.
  • Value parametresi semaforun başlangıç değerini belirler.
  • Eğer ilgili semafor daha öncesinden yaratılmış ise mode ve value değerleri kullanılmayacak ve daha öncesinden ayarlanan değerler ile semafora erişilecektir.
  • Eğer sem_open() işlemini başarılı yapar ise sem_t * türünde bir adres dönecektir.
  • Eğer sem_open() işlemini başarılı bir şekilde yapamaz ise SEM_FAILED dönecektir ve sistemdeki errno set edecektir. Burada geri dönüş değeri hem pointer hem de nasıl bir SEM_FAILED türünde sabir olabilir diye düşünebilirsiniz. Sorunun cevabı aslında çok basit, tıpkı kendi kodlarımızda yaptığımız gibi başarısızlık durumunda NULL adres dönmektedir. Fakat hatayı kullanıcının daha rahat anlaması için(okunabilirliği arttırmak için) NULL addres yerine SEM_FAILED demişler. Sonuç olarak SEM_FAILED null adresin ta kendisidir. semaphore.c dosyasına bakarsanız bu makroyu göreceksiniz.
    #define SEM_FAILED      ((sem_t *) 0)
  • Son olarak başarılı bir semafor oluşturmanın/açmanın ardından /dev/shm dizininin oluştuğunu göreceksiniz

2.1.b sem_post()

#include <semaphore.h>

int sem_post (sem_t *sem);

Bu fonksiyon semafor değerini bir arttırır. Semafor değerinin bir arttırılma işlemine semafor için V operasyonu denir. Semafor değerinin bir arttırılması bir başka process ya da thread ilgili alana erişim izni vererek sıradaki process ya da thread çalışmasını ya da uyanmasını sağlar. Kısaca kapının anahtarını açıp içeri başkasının girmesine izin vermek diyebiliriz.

Eğer işlem başarılı ise 0, hatalı ise -1 değerini döner.

2.1.c sem_wait()

#include <semaphore.h>

int sem_wait (sem_t *sem);

Bu fonksiyon semafor değerini bir azaltmak için kullanılır. Eğer semafor değeri sıfır değilse azaltma işlemi gerçekleşir. Eğer semafor değeri sıfır ise semafor değeri bir başkası tarafından bir arttırılana kadar bekler ve tekrar kendisi bir azaltır. Semafor değerinin azaltılması işlemine P operasyonu denir.

Eğer işlem başarılı ise 0, hatalı ise -1 değerini döner. Hata durumunda ayrıca errno da ilgilli hata numarası yüklenir.

2.1.d sem_trywait()

#include <semaphore.h>

int sem_trywait (sem_t *sem);

Bu fonksiyon tıpkı sem_wait() fonksiyonu gibi çalışır. Fakat semafor değeri sıfır iken sem_wait() gibi beklemez(unblock) ve hemen geri döner ve errno değerine EAGAIN değerini yükler.(#define        EAGAIN                11        /* Try again */)

2.1.e sem_timedwait()

#include <semaphore.h>

int sem_timedwait (sem_t *sem, const struct timespec *abs_timeout);

sem_wait () gibi P operasyonu için kullanılır. Fakat bu fonksiyon sem_wait()’ den farklı olarak zaman aşımı parametresi içerir. Hatırlar iseniz sem_wait() işlemini yapana kadar bekliyordu. sem_timedwait() eğer semafor değeri sıfır ise belirtilen zaman aşımı içinde semafor değeri sıfır değerinin üzerine çıkar ise işlemini başarı ile yapar. Aksi durumda zaman aşımı gerçekleşir ve errno değerine ETIMEOUT yükler ve işlemin başarısız olduğunu belirterek geri döner.

struct timespec
{
     
time_t tv_sec;      /* Seconds */ 
     
long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
};

2.1.f sem_getvalue()

#include <semaphore.h>

int sem_getvalue (sem_t *sem, int *sval);

sem_getvalue() ile sem adresinin (pointer) gösterdiği semaforun tam sayı değeri ile geri döner. Başarı durumunda 0, hata durumunda -1 döner.

2.1.g sem_unlink()

#include <semaphore.h>

int sem_unlink (const char *name);

Belirtilen isimdeki semaforu kaldırır.


2.2 İsimlendirilmiş POSIX Semaforlar Örneği

Bu başlık altında yukarıdaki senaryoyu gerçekleştiren bir örnek yazacağız. Amacımız bir bellek alanını birden farzla üretici ve tek bir kullanıcı arasında kullanıma sunmak.

$ gcc semafor_test.c -o semafor_test -lpthread

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <unistd.h>

#define SEM_MUTEX_NAME          "/sem-mutex"
#define SEM_BUFFER_COUNT_NAME   "/sem-buffer-count"
#define SEM_SPOOL_SIGNAL_NAME   "/sem-spool-signal"

// Buffer data structures
#define MAX_BUFFERS 10

char buf [MAX_BUFFERS] [100];
int buffer_index;
int buffer_print_index;

sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem;

void *producer (void *arg);
void *spooler (void *arg);

int main (int argc, char **argv)
{
   
pthread_t tid_producer [10], tid_spooler;
   
int i, r;

   
// initialization
   buffer_index = buffer_print_index =
0;

   
//  mutual exclusion semaphore, mutex_sem with an initial value 1.
   
if ((mutex_sem = sem_open (SEM_MUTEX_NAME, O_CREAT, 0660, 1)) == SEM_FAILED)
   {
       perror (
"sem_open"); exit (1);
   }

   
// counting semaphore, indicating the number of available buffers. Initial value = MAX_BUFFERS
   
if ((buffer_count_sem = sem_open (SEM_BUFFER_COUNT_NAME, O_CREAT, 0660, MAX_BUFFERS)) == SEM_FAILED)
   {
       perror (
"sem_open"); exit (1);
   }

   
// counting semaphore, indicating the number of strings to be printed. Initial value = 0
   
if ((spool_signal_sem = sem_open (SEM_SPOOL_SIGNAL_NAME, O_CREAT, 0660, 0)) == SEM_FAILED)
   {
       perror (
"sem_open"); exit (1);
   }

   
// Create spooler
   
if ((r = pthread_create (&tid_spooler, NULL, spooler, NULL)) != 0)
   {
       
fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1);
   }

   
// Create 10 producer threads
   
int thread_no [10];
   
for (i = 0; i < 10; i++)
   {
       thread_no [i] = i;
       
if ((r = pthread_create (&tid_producer [i], NULL, producer, (void *) &thread_no [i])) != 0)
       {
           
fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1);
       }
   }

   
// Wait for producers to terminate
   
for (i = 0; i < 10; i++)
   {
       
if ((r = pthread_join (tid_producer [i], NULL)) == -1)
       {
           
fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1);
       }
   }

   
// No more strings to print? Wait for spool_signal_sem to become 0
   
int semval;
   
while (1)
   {
       
if (sem_getvalue (spool_signal_sem, &semval)== -1)
       {
           perror (
"sem_getvalue");
       }

       
if (!semval) break;

       sleep (
1);
   }
   
// terminate spooler
   
if ((r = pthread_cancel (tid_spooler)) != 0)
   {
       
fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1);
   }

   
// Remove semaphores
   
if (sem_unlink (SEM_MUTEX_NAME)==-1) { perror ("sem_unlink");exit(1);}

   
if (sem_unlink(SEM_BUFFER_COUNT_NAME) == -1){perror("sem_unlink"); exit(1);}

   
if (sem_unlink (SEM_SPOOL_SIGNAL_NAME) == -1) {perror ("sem_unlink"); exit (1);}

   
exit (0);
}

// producer: produce strings for printing
// There might be multiple producer threads
void *producer (void *arg)
{
   
// Create 10 strings and terminate
   
int i;
   
int my_id = *((int *) arg);
   
int count = 0;

   
for (i = 0; i < 10; i++)
   {
       
// get a buffer: P (buffer_count_sem);
       
if (sem_wait (buffer_count_sem) == -1)
       {
           perror (
"sem_wait: buffer_count_sem"); exit (1);
       }

       
/* There might be multiple producers. We must ensure that
           only one producer uses buffer_index at a time.  */

       
// P (mutex_sem);
       
if (sem_wait (mutex_sem) == -1)
       {
           perror (
"sem_wait: mutex_sem"); exit (1);
       }

       
// Critical section
           
int j = buffer_index;
           buffer_index++;
           
if (buffer_index == MAX_BUFFERS)
           {
               buffer_index =
0;
           }

       
// Release mutex sem: V (mutex_sem)
       
if (sem_post (mutex_sem) == -1)
       {
           perror (
"sem_post: mutex_sem"); exit (1);
       }

       
// Produce a string
       
sprintf (buf [j], "Thread %d: %d\n", my_id, ++count);
 
// Tell spooler that there is a string to print: V (spool_signal_sem);
       
if (sem_post (spool_signal_sem) == -1)
       {
           perror (
"sem_post: spool_signal_sem"); exit (1);
       }

       
// Take a nap
       sleep (
1);
   }
}

// There is only one spooler thread
void *spooler (void *arg)
{
   
while (1)
   {  
// forever
       
// Is there a string to print? P (spool_signal_sem);
       
if (sem_wait (spool_signal_sem) == -1)
       {
           perror (
"sem_wait: spool_signal_sem"); exit (1);
       }

       
printf ("%s", buf [buffer_print_index]);

       
/* Since there is only one thread (spooler) using the
          buffer_print_index, mutex semaphore is not necessary */

       buffer_print_index++;
       
if (buffer_print_index == MAX_BUFFERS)
       {
          buffer_print_index =
0;
       }

       
/* Contents of one buffer has been printed.
          One more buffer is available for use by producers.
          Release buffer: V (buffer_count_sem);  */

       
if (sem_post (buffer_count_sem) == -1)
       {
           perror (
"sem_post: buffer_count_sem"); exit (1);
       }
   }
}

Yukarıdaki örnek uygulamada for döngüsü ile oluşturulan 10 adet thread aynı buffer alanına sprintf (buf [j], "Thread %d: %d\n", my_id, ++count); satırı ile yazmaktadır. Spooler fonksiyonu içinde ise buffer içindeki yazılar kullanılmaktadır.

Görüldüğü gibi threadlar arası uyumu semafor ile sağlamış olduk. Ee biz bunu mutex ile de yapabilirdik. Evet doğru, mutex ile bu işlemi yapabilirdik, anlatımın bitmesi için biraz daha sabır. Belgenin başındaki şu cümleleri hatırlayalım; “Mutex’ler ikilik semaforlardır. Mutex’ler aynı proses ve thread’leri tarafından kullanılır.” Bu nedenle threadler arası (aynı process) semafor kullanılması boşa kaynak harcamaktır. Bu sebeple yukarıdaki örnek sadece konuyu açıklamak amacıyla yazılmıştır.(tek dosyada derlenip okuyucunun anlamasını kolaylaştırmak amaçlanmıştır). İsimlendirilmiş semaforlar threadler arası değil processler arası uyumu sağlamak amacıyla kullanılır. Belirtilen isimdeki semaforu sem_open() ile açan her process diğer processler ile uyumlu çalışmayı sağlayabilir.

Konuyu daha iyi anlamak için yukarıdaki örneğin producer ve spooler kısımlarını ayırarak ayrı bir uygulamaya çevirebilirsiniz.

2.3 İsimlendirilmemiş POSIX Semaforlar

İsimsiz semaforlara “Memory Based Samaphores” denir. Semaforların ismi yoktur ve ortak bellek alanında saklanır. Yukarıdaki örnekte de görüldüğü gibi aynı process içinde threadler arası uyumu sağlamak için kullanılır.

2.1.a sem_init()

#include <semaphore.h>

int sem_init (sem_t *sem, int pshared, unsigned int value);

sem_init(), ilgili semaforun kurulmasını sağlar. Fonksiyonun 2. parametresi olan pshared değeri 0 olduğu takdirde, semafor sadece aynı sürecin thread'leri içerisinde kullanılabilir. Bu nedenle shared memory kullanımına gerek kalmadan, global bir değişkende veya heap üzerinde ayrılan bir alanda tutulabilir.

pshared değeri 0'dan farklı olursa, semafor farklı süreçler arasında kullanılabilir. Bu durumda 1. parametredeki adres, shared memory üzerindeki bir yeri göstermelidir. Bu fonksiyonun aynı semafor için birden fazla çağrılması durumunda sistem kararlı davranamaz. Bu nedenle, her semaforun sadece bir defa kurulması garanti edilmelidir.

2.1.b sem_destroy()

#include <semaphore.h>

int sem_destroy (sem_t *sem);

İlgili semaforun sonlandırılmasını sağlar. Semafor sonlandırılmadan, tutulduğu bellek bölgesi free edilmemelidir.


2.4 İsimlendirilmemiş POSIX Semaforlar Örneği

İsimlendirilmiş semaforda verdiğimiz örneği bu başlık altında tekrar ele alalım. Fakat ilk olarak birden fazla thread aynı isimlendirilmemiş semaforu kullanabilmesi için sem_init() fonksiyonun sem_t * argümanı için vereceğimiz değişken global olmasına dikkat etmemiz gerekmektedir.

#include <sys/types.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <time.h>

#include <errno.h>

#include <pthread.h>

#include <fcntl.h>

#include <sys/stat.h>

#include <semaphore.h>

#include <unistd.h>

// Buffer data structures

#define MAX_BUFFERS 10

char buf [MAX_BUFFERS] [100];

int buffer_index;

int buffer_print_index;

sem_t mutex_sem, buffer_count_sem, spool_signal_sem;

void *producer (void *arg);

void *spooler (void *arg);

int main (int argc, char **argv)

{

    pthread_t tid_producer [10], tid_spooler;

    int i, r;

    // initialization

    buffer_index = buffer_print_index = 0;

    //  mutual exclusion semaphore, mutex_sem with an initial value 1.

    if (sem_init (&mutex_sem, 0, 1) == -1)

    {

        perror ("sem_init"); exit (1);

    }

    // counting semaphore, indicating the number of available buffers. Initial value = MAX_BUFFERS

    if (sem_init (&buffer_count_sem, 0, MAX_BUFFERS) == -1)

    {

        perror ("sem_init"); exit (1);

    }

    // counting semaphore, indicating the number of strings to be printed. Initial value = 0

    if (sem_init (&spool_signal_sem, 0, 0) == -1)

    {

        perror ("sem_init"); exit (1);

    }

    // Create spooler

    if ((r = pthread_create (&tid_spooler, NULL, spooler, NULL)) != 0)

    {

        fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1);

    }

    // Create 10 producer threads

    int thread_no [10];

    for (i = 0; i < 10; i++)

    {

        thread_no [i] = i;

        if ((r = pthread_create (&tid_producer [i], NULL, producer, (void *) &thread_no [i])) != 0)

        {

            fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1);

        }

    }

    // Wait for producers to terminate

    for (i = 0; i < 10; i++)

    {

        if ((r = pthread_join (tid_producer [i], NULL)) == -1)

        {

            fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1);

        }

    }

   

    // No more strings to print? Wait for spool_signal_sem to become 0

    int semval;

    while (1)

    {

        if (sem_getvalue (&spool_signal_sem, &semval)== -1)

            perror ("sem_getvalue");

       

        if (!semval) break;

       

        sleep (1);

    }

    // terminate spooler

    if ((r = pthread_cancel (tid_spooler)) != 0)

    {

        fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1);

    }

    // Destroy semaphores

    if (sem_destroy (&mutex_sem) == -1)

    {

        perror ("sem_destroy"); exit (1);

    }

    if (sem_destroy (&mutex_sem) == -1)

    {

        perror ("sem_destroy"); exit (1);

    }

    if (sem_destroy (&mutex_sem) == -1)

    {

        perror ("sem_destroy"); exit (1);

    }

    exit (0);

}

// producer: produce strings for printing

// There might be multiple producer threads

void *producer (void *arg)

{

    // Create 10 strings and terminate

    int i;

    int my_id = *((int *) arg);

    int count = 0;

    for (i = 0; i < 10; i++)

    {

        // get a buffer: P (buffer_count_sem);

        if (sem_wait (&buffer_count_sem) == -1)

        {

            perror ("sem_wait: buffer_count_sem"); exit (1);

        }

        /* There might be multiple producers. We must ensure that

            only one producer uses buffer_index at a time.  */

        // P (mutex_sem);

        if (sem_wait (&mutex_sem) == -1)

        {

            perror ("sem_wait: mutex_sem"); exit (1);

        }

        // Critical section

            int j = buffer_index;

            buffer_index++;

           

            if (buffer_index == MAX_BUFFERS)

                buffer_index = 0;

        // Release mutex sem: V (mutex_sem)

        if (sem_post (&mutex_sem) == -1)

        {

            perror ("sem_post: mutex_sem"); exit (1);

        }

    // Produce a string

        sprintf (buf [j], "Thread %d: %d\n", my_id, ++count);

    // Tell spooler that there is a string to print: V (spool_signal_sem);

        if (sem_post (&spool_signal_sem) == -1)

        {

            perror ("sem_post: spool_signal_sem"); exit (1);

        }

        // Take a nap

        sleep (1);

    }

}

// There is only one spooler thread

void *spooler (void *arg)

{

    while (1)

    {  // forever

        // Is there a string to print? P (spool_signal_sem);

        if (sem_wait (&spool_signal_sem) == -1)

        {

            perror ("sem_wait: spool_signal_sem"); exit (1);

        }

        printf ("%s", buf [buffer_print_index]);

        /* Since there is only one thread (spooler) using the

           buffer_print_index, mutex semaphore is not necessary */

        buffer_print_index++;

        if (buffer_print_index == MAX_BUFFERS)

           buffer_print_index = 0;

        /* Contents of one buffer has been printed.

           One more buffer is available for use by producers.

           Release buffer: V (buffer_count_sem);  */

        if (sem_post (&buffer_count_sem) == -1)

        {

            perror ("sem_post: buffer_count_sem"); exit (1);

        }

    }

}