[LinuxFocus-icon]
Ev  |  Erişimdüzeni  |  İçindekiler  |  Arama

Duyumlar | Belgelikler | Bağlantılar | LF Nedir
Bu makalenin farklı dillerde bulunduğu adresler: English  Castellano  Deutsch  Francais  Nederlands  Russian  Turkce  Polish  

convert to palmConvert to GutenPalm
or to PalmDoc

[Photo of the Author]
tarafından Leo Giordani
<leo.giordani(at)libero.it>

Yazar hakkında:
Milan,Politecnico'da Telekominikasyon Fakültesi'nde öğrenci.Netwok yöneticiliği yapıyor ve programlama ( özellikle assembly ve C++) ile ilgileniyor.1999'dan beri özellikle Linux/Unix üzerinde çalışıyor.

Türkçe'ye çeviri:
Özcan Güngör <ozcangungor(at)netscape.net>

İçerik:

 

Title: Eşzamanlı Programlama - İşlemler arası iletişim

[Illustration]

Özet:

Bu makale dizisinin amacı, okuyucuya çokluişlemler ve onun Linux'ta uygulanışı hakkında bilgi vermektir.Teorik bilgilerden başlayıp işlemler arası iletişim örneği gösteren tamamlanmış bir örnek vereceğiz.Basit ama etkili bir iletişim protokü kullanacağız.
Makalenin anlaşılabilmesi için şu bilgilere gereksinim vardır:

  • Biraz kabuk bilgisi
  • Temel C bilgisi ( yazım, döngüler, kütüphaneler )
Öncelikle Kasım 2002,makale 272 'yi okumalısınız çünkü bu makalenin devamıdır.
_________________ _________________ _________________

 

Giriş

Burada Linux'ta çokluişlemleri açıklayacağız.Bir önceki makalede gördüğümüz gibi bir programın çalışmasını bölmek için bir kaç kod satırı yeterlidir.Çünkü işletim sistemi yarattığımız işlemlerin başlangıçı, yönetilmesi ve zamanlamasıyla ilgilenir.
İşletim sistemi tarafından sağlanan servis biricil öneme sahiptir, o işlemlerin "çalışma yöneticisi"dir.Yani işlemler belirli bir ortamda çalışıtırılır.İşlemler çalışırken  kontrolünün kaybı, programcıya eşzamanlılık sorunu getirir.İlgilendiğimiz soru şudur:İki bağımsız işlemi nasıl bir arada çalıştabiliriz?
Sorun sandığımızdan daha karışıktır:Sorun sadece iki işlemin eşzamanlaması değil. Veri paylaşımı ve okuma-yazma da sorundur.
Sıradan bir eşzamanlama sorusu düşünelim:Eğer iki işlem aynı anda bir veri kümesini okuyacaksa sorun yoktur.Çalışma biçimlerinde değişiklik olmaz ve hep aynı sonucu üretirler.Şimdi işlemlerden birinin veri kümesini değiştirmek istediğini düşünelim.İkinci işlemin ilk işlemden önce ya da sonra çalışmasına göre vereceği sonuç farklı olacaktır.Örenğin:"A" ve "B" iki işlemimiz ve "d" bir tam sayı olsun.A işlemi d'yi 1 artırır.B ise d'yi ekrana yazdırır.Bunu şu şekilde ifade edebiliriz:

A { d->d+1 } & B { d->output }

Burada "&" eşzamanalı işlemi ifade eder.Birinci mümkün çalışma:

(-) d = 5 (A) d = 6 (B) output = 6

ama eğer önce B çalışırsa şunu elde ederiz :

(-) d = 5 (B) output = 5 (A) d = 6

Bu örnekten eşzamanalı çalışmanın doğru yönetilmesinin önemini anlıyorsunuz.Verilerin aynı olmaması riski büyüktür ve kabul edilemez.Bu veri kümelerinin banka hesaplarınız olduğunu düşünün.Bu sorunu asla küçümsemezsiniz.
Bir önceki makalede waitpid(2) fonksiyonu kullanarak eşzamanlamanın ilk biçimini görmüştürk.Bu fonksiyon, bir işlemin devam etmeden önce başka bir işlemin bitmesi için  beklemeye yarar.Aslında bu, okuma-yazma anında oluşabilecek karmaşıklığı büyük ölçüde engeller.Bir veri kümesi üzerinde P1 çalışıyorsa, P2 aynı veri kümesinde veya bir alt kümesinde çalışacaksa önce P1'in bitmesini beklemek zorundadır.
Açıkca bu yöntem bir çözümdür.fakan en iyisi değildir.P2, P1'in işleminin bitmesi için -çok kısa sürecekse bile- uzun zaman bekleyecektir.Biz kontrolümüzün alanını daraltmalıyız.Örneğin:tek bir veriye ya da  veri kümesine erişme gibi.Bu sorunun çözümü kütüphanelerin temeli olan,SysV IPC(System V InterProcess Communication-SistemV İşlemlerarası İletişim) adı verilen bir takım kütüphane ile verilir.  

SysV Anahtarları

Eşzamanlılık ve onun uygulamalırı ile ilgili teorilere girmeden önce tipik SysV yapısına bir göz atalım:IPC anahtarları.Bir IPC anahtarı, bir IPC kontrol yapısını (daha sonra açıklanacaktır) belirleyen bir sayıdır.Ancak bu, aynı tip belirleyicileri üretmek için de kullanılır.Öreneğin: no-IPC yapılarını organize etmek için.Bir anahtar ftok(3) fonksiyonu kullanılarak oluşturulur:

key_t ftok(const char *pathname, int proj_id);

Bu fonksiyon, var olan bir dosya adını (pathname) ve bir tam sayı kullanır.Anahtarın tek olacağının garantisi yoktur çünkü dosyadan alınan parametreler (i-node sayısı ve sürücü numarası), benzer kombinasyonları  oluşturabilir.Var olan anahtarları kontrol eden ve aynı anahtarın kullanımasını engelleyecek küçük bir kütüphane oluşturmak iyi bir çözüm olacaktır.
   

Semaforlar

Araç trafiğinde kullanılan semaforlar hiç değiştirilmeden veri akışı kontrolünde de kullanılabilir.Bir semafor, sıfır vaya daha büyük bir değer alan ve semafordaki bir koşulun gerçekleşmesini bekleyen işlem kuyruğunu yöneten bir yapıdır.Semaforların basit gibi görünmelerine karşın çok güçlüdürler.Hata kontrolünü dışarıda bırakarak başlayalım:Semaforları kamaşık programlarla karşılaştığımızda kullanacağız.
Semaforlar kontrol kaynaklarına ulaşırken kullanılabilirler:Semaforun değeri, bir kaynağa ulaşmaya çalışan bir işlemin numarasını gösterir.Bir işlem bir kaynağa her ulaşmaya çalıştığı zaman semafonun değeri azaltılır ve işlemin kaynakla işi bittiğinde tekrar artılır.Eğer kaynak özel ise ( sadce bir işlem ulaşabiliyorsa ) semaforun başlangıç değeri 1 olur.
Semafora benzer farklı bir kavram daha vardır: kaynak sayacı.Bu değer, bu durumda, ulaşılabilir kaynak sayısını gösterir ( serbest bellek hücre sayısı gibi ).
Gerçek bir durum düşünelim.Semafor tipleri kullanılsın:S1,..,Sn işlemlerinin yazma hakkı olan ama sadece L işleminin okuma hakkı olan bir tampon var.Bu tampon üzerinde belirli bir anda sadece bir işlem çalışabilsin.S işlemlerinin tamponun dolması dışında her zaman tampona yazabiliriler ve L işlemi sadece tampon boş olmadığı zaman okuyabilir.Bu durumda üç semafora gereksini var:Birincisi kaynağa erişimi yönetir, ikinci ve üçüncü semafor tampondaki eleman sayısını tutar (ileride neden iki semaforun yeterli olmadığını göreceğiz).
Tamponun özel olduğunu düşünelim.Birinci semafor sadece 0 veya 1 değerini alabilir.İkinci ve üçüncü semaforun alabileceği değer tamponun boyutuna bağlıdır.
Şimdi semaforların C'de SysV'yi kullanarak nasıl uygulandığını görelim.Semaforları oluşturan fonksiyon semget(2)'dir:

int semget(key_t key, int nsems, int semflg);

Burada key,IPC anahtarıdır.nsems, oluşturmak istediğimiz semafor sayısıdır.semflg, 12 bitlik erişim kontorlüdür.İlk 3'ü oluşturma politikasına göre değişir, diğer 9'ü ise kullanıcı, group ve diğerlerinin (Unix dosya sistemindeki gibi) okuma ve yazma haklarıdır.Tam açıklama için ipc(5)'nin kullanma klavuzunu okuyun.Farkettiğiniz gibi SysV bir semafor kümesini kontrol eder ve bu sebeple kod daha küçüktür.
İlk semaforumuzu oluşturalım:

#include <stdio.h>
#include <stdlib.h>
#include <linux/types.h>
#include <linux/ipc.h>
#include <linux/sem.h>
int main(void)
{
  key_t key;
  int semid;
  key = ftok("/etc/fstab", getpid());
  /* Bir semafor içren bir semafor kümesi oluşturalım */
  semid = semget(key, 1, 0666 | IPC_CREAT);
  return 0;
}

İleride semaforların nasıl yönetildiğini ve silindiğini öğreneceğiz.Semaforu yönetme fonksiyonu semctl(2)'dir:

int semctl(int semid, int semnum, int cmd, ...)

Burada parametreler semafor kümesinin veya (istenirse) sadece semnum ile belirtilen semaforun işlevine bağlı olarak cmd ile verilir.Biz gereksinim duydukça bazı seçenekleri anlatacağız.Ama tüm liste kullanma klavuzunda verilmiştir.cmd işlevine bağlı olarak fonksiyon için başka parametreler de tamınlamak gerekebilir.Şu şekildedir:

union semun {
 int val;                  /* SETVAL değeri */
 struct semid_ds *buf;     /* IPC_STAT, IPC_SET için tampon*/
 unsigned short *array;    /* GETALL, SETALL için dizi*/
                           /* Linux'a özgü kısım */
 struct seminfo *__buf;    /* IPC_INFO için tampon*/
};

Semafora değer verebilmek için SETVAL yönergesi kullanımlaıdır ve bu değer semun birliğinde (union) belirtilmelidir:Bir önceki programdaki semforun değerini 1 yapalım:

[...]
  /* Bir semaforlu bir semafor kümesi oluştur */
  semid = semget(key, 1, 0666 | IPC_CREAT);
  /* 0 numaralı semaforun değerini 1 yap */
  arg.val = 1;
  semctl(semid, 0, SETVAL, arg);
[...]

Şimdi semafor için ayrılmış yeri boşaltmamız gerekiyor.Bu işlem, semctl fonksiyonunda IPC_RMID yönergesi ile yapılır.Bu işlem semaforu siler ve bu kaynağa ulaşmak için bekleyen  butün işlemlere bir ileti gönderir.Yukarıdaki programın devamı olarak:

[...]
  /* Semafore 0'in değerini 1 yap */
  arg.val = 1;
  semctl(semid, 0, SETVAL, arg);
  /* Semaforu sil */
  semctl(semid, 0, IPC_RMID);
[...]

Daha önce de gördüğümüz gibi eşzamanlı işlerleri oluturmak ve yönetmek zor değildir.Hata yönetimi işin içine girdiğinde, kodun karmaşıklılığı açısından işler biraz zorlaşacak.
Artık semafor semop(2) fonksiyonu ile kullanılabilir:

int semop(int semid, struct sembuf *sops, unsigned nsops);

Burada semid, kümenin belirteci; sops, yapılacak işlemler dizisi ve nsops bu işlemlerin sayısıdır.Bütün işlemler sembuf yapısıyla temsil edilir:

unsigned short sem_num; short sem_op; short sem_flg;

Örneğin kümedeki semaforun numarası (sem_num), işlem (sem_op) ve bekleme politikasını ayarlayan bayrak (şimdilik sem_flg 0 olsun).Belirleyeceğimiz işlemler tam sayılardır ve aşağıdaki kurallara uyar:
 

  1. sem_op < 0

  2. Eğer semaforun mutlak değeri sem_op' eşit veya sem_op'dan büyükse, işlem devam eder ve semaforun değerine sem_op eklenir.Eğer semaforun mutlak değeri sem_op'dan küçükse, işlem belirli bir kaynak numarısı ulaşılabilir olana kadar uyku durumuna geçer.
  3. sem_op=0

  4. İşlem, semaforun değeri 0 oluncaya kadar uyku durumuna geçer.
  5. sem _op > 0

  6. sem_op değeri, semaforun değerine eklenir ve daha önce kullanılan kaynak bırakılır.
Aşağıdaki program, semaforların nasıl kullanılacağını göteriri.Ayrıca daha önce verdiğimiz tampon örneğinin kulanımını da gösterir:Biz beş adet Y (yazma) ve bir adet O (okuma) işlemi ouşturacağız.Her Y işlemi semafor kullanarak erişeceği kaynağı (tampon) kilitleyecek.Eğer tampon dolu değilse, tampona bie eleman koyacak ve tamponu bırakacaktır.O işlemi, tamponu kilitler, tampon boş değilse bir eleman alır ve kilidi kaldırır.
Neden üç semafora gereksinimimiz var?İlki (0 numaralı semafor), tamponun erişim kilididir ve en büyük değeri 1'dir.Diğer iki semafor doluluğu ve boşluğu temsil eder.
Durumu daha açık anlatalım: bir semaforumuz (adı O olsun) olsun ve tampondaki boş yerin değerini tutsun.Bir S işlemi her tambona birşey koyduğunda bu değer 1 azalır.Ve bu sıfır değeri alıncaya kadar devam eder yani tampon dolana kadar.Bu semafor boşluğu temsil edemez: R işlemi kendi değerini sınırsız artırabilir.Bundan dolayı başka bir semafora (adı U olsun) gereksinim duyarız.Bu semafor tampondaki eleman sayısını tutar.W işlemi her tampona bir eleman koyduğunda, W'nun değerini artırır ve O'nun değerini azaltır.Tersi durumda, R işlemi, U'nun değerini azaltır ve O'nun değerini artırır.

Doluluk, O semaforunun değerini artamamak ile; boşluk, U semaforunu değerini azaltamamak ile anlaşılır.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <linux/types.h>
#include <linux/ipc.h>
#include <linux/sem.h>
int main(int argc, char *argv[])
{
  /* IPC */
  pid_t pid;
  key_t key;
  int semid;
  union semun arg;
  struct sembuf lock_res = {0, -1, 0};
  struct sembuf rel_res = {0, 1, 0};
  struct sembuf push[2] = {1, -1, IPC_NOWAIT, 2, 1, IPC_NOWAIT};
  struct sembuf pop[2] = {1, 1, IPC_NOWAIT, 2, -1, IPC_NOWAIT};
  /* Diğerleri */
  int i;
  if(argc < 2){
    printf("Kullanım: bufdemo [bıoyut]\n");
    exit(0);
  }
  /* Semaforlar */
  key = ftok("/etc/fstab", getpid());
  /* 3 semafor içeren semafor kümesi oluştur */
  semid = semget(key, 3, 0666 | IPC_CREAT);
  /* Semaphore #0'ın değerini  1 yap - Kaynak kontrolü */
  arg.val = 1;
  semctl(semid, 0, SETVAL, arg);
  /* Semafor #1'in değerini buf_length yap- Doluluk kontrolü */
  /* Sem'in değeri 'tampondaki boş yer'dir */
  arg.val = atol(argv[1]);
  semctl(semid, 1, SETVAL, arg);
  /* Semafor #2'nin değeri buf_length'dir - Boşluk kontrolü */
  /* Sem'in değeri 'tampondaki eleman sayıs'dır */
  arg.val = 0;
  semctl(semid, 2, SETVAL, arg);
  /* Ayırma */
  for (i = 0; i < 5; i++){
    pid = fork();
    if (!pid){
      for (i = 0; i < 20; i++){
 sleep(rand()%6);
 /* Kaynağı kilitlemeyi dene - sem #0 */
 if (semop(semid, &lock_res, 1) == -1){
   perror("semop:lock_res");
 }
 /* Bir serbest boşluğu kilitle - sem #1 / Bir eleman koy - sem #2*/
 if (semop(semid, &push, 2) != -1){
   printf("---> İşlem:%d\n", getpid());
 }
 else{
   printf("---> İşlem:%d  TAMPON DOLU\n", getpid());
 }
 /* Kaynağı bırak */
 semop(semid, &rel_res, 1);
      }
      exit(0);
    }
  }
  for (i = 0;i < 100; i++){
    sleep(rand()%3);
    /* Kaynağı kilitlemeyi dene - sem #0 */
    if (semop(semid, &lock_res, 1) == -1){
      perror("semop:lock_res");
    }
    /* Serbest boşluğun kilidini kaldır - sem #1 / Bir eleman al - sem #2 */
    if (semop(semid, &pop, 2) != -1){
      printf("<--- İşlem:%d\n", getpid());
    }
    else printf("<--- İşlem:%d  TAMPON BOŞ\n", getpid());
    /* Kaynağı bırak */
    semop(semid, &rel_res, 1);
  }
  /* Seamforları sil */
  semctl(semid, 0, IPC_RMID);
  return 0;
}

Koddaki şu ilginç satılara bakalım:

struct sembuf lock_res = {0, -1, 0};
struct sembuf rel_res = {0, 1, 0};
struct sembuf push[2] = {1, -1, IPC_NOWAIT, 2, 1, IPC_NOWAIT};
struct sembuf pop[2] = {1, 1, IPC_NOWAIT, 2, -1, IPC_NOWAIT};

Bu dört satır, semaforlar üzerinde yapabileceğimiz işlemlerdir: İlk ikisi tek işlem iken diğer iki işlem çift işlemdir.İlk işlem, lock_res, kaynağı kilitmeyi dener:Bu işlem ilk semaforun (0 numaralı) değerini -eğer semaforun değeri sıfır değilse- bir azaltır.Kaynağın kilitli olması durunda politika hiçbiri değildir (örneğin ilem bekleme durumunda).rel_res, lock_res'e benzer ama kaynak bırakılır( değeri pozitiftir) .
Ekleme ve çıkarma işlemleri biraz özeldir.Bunlar iki işlem dizisidir.Birincisi 1 numaralı semafor üzerinde ve ikincisi 2 numaralı semafor üzerindedir.Birincisi artırma iken ikincisi azaltmadır veya tam tersi.Ama politika artık bekleme durumu değildir:IPC_NOWAIT, işlemi, kaynak kilitli ise çalışmasına devam etmesi için zorlar.

/* Semafo #0'in değerini 1 yap - Kaynak kontrolü */
arg.val = 1;
semctl(semid, 0, SETVAL, arg);
/* Semafor #1'i  buf_length yap - Doluluk kontrolü */
/* Sem'in değeri  'tampondaki boş yer'dir */
arg.val = atol(argv[1]);
semctl(semid, 1, SETVAL, arg);
/* Semafor #2'yi buf_length yap - Boşluk kontrolü */
/* Sem'in değeri 'tampondaki eleman sayısı'dir */
arg.val = 0;
semctl(semid, 2, SETVAL, arg);

Burada semaforlara ilk değerlerini veririz..Birincisini 1 yaparız çünkü belirili bir kaynağa ulaşmayı kontrol eder.İkincisini, tamponun uzunluğuna eşitleriz.Üçüncüsünü ise boşluk ve doluluğu gösterir.

/* Kaynağı kilitlemeye çalış - sem #0 */
if (semop(semid, &lock_res, 1) == -1){
  perror("semop:lock_res");
}
/* Bir boş yer kilitle - sem #1 / Bir eleman koy - sem #2*/
if (semop(semid, &push, 2) != -1){
  printf("---> İşlem:%d\n", getpid());
}
else{
  printf("---> İşlem:%d  TAMPON DOLU\n", getpid());
}
/* Kaynaktaki kilidi kaldır */
semop(semid, &rel_res, 1);

Y işlemi, kaynağı lock_res fonksiyonunu kullanarak kilitlemeya çalışır.Bunu gerçekleştirdikten sonra kaynağa bir eleman koyar ve bunu ekrana yazar.Eğer bu işlemi yapamazsa ekrana "TAMPON DOLU" iletisini yazar.Daha sonra kaynağı serbest bırakır.

/* Kaynağı kilitlemeyi dene - sem #0 */
    if (semop(semid, &lock_res, 1) == -1){
      perror("semop:lock_res");
    }
    /* Serbest boşluğun kilidini kaldır - sem #1 / Bir eleman al - sem #2 */
    if (semop(semid, &pop, 2) != -1){
      printf("<--- İşlem:%d\n", getpid());
    }
    else printf("<--- İşlem:%d  TAMPON BOŞ\n", getpid());
    /* Kaynağı bırak */
    semop(semid, &rel_res, 1);

O işlemi, az çok Y kadar çalıştırılır:Kaynağı kilitler, bir eleman alır ve kaynağı serbest bırakır.

Bir sonraki  makalede, ileti kuyruklarından, İşlemlerarası İletişimin ve eşzamanlamanın farklı bir yapısından bahsedeceğiz.Bu makaleyi kullanarak yazdığınız programları -basit olsalar bile- bana da gönderin ( isim ve e-posta adreslerinizle birlikte ).Bunları okumaktan memnun olacağım.İyi çalışmalar.
   

Önerilen Kaynaklar

 

Bu yazı için görüş bildiriminde bulunabilirsiniz

Her yazı kendi görüş bildirim sayfasına sahiptir. Bu sayfaya yorumlarınızı yazabilir ve diğer okuyucuların yorumlarına bakabilirsiniz.
 talkback page 

Görselyöre sayfalarının bakımı, LinuxFocus Editörleri tarafından yapılmaktadır
© Leo Giordani, FDL
LinuxFocus.org
Çeviri bilgisi:
en --> -- : Leo Giordani <leo.giordani(at)libero.it>
en --> tr: Özcan Güngör <ozcangungor(at)netscape.net>

2003-02-03, generated by lfparser version 2.35