[LinuxFocus-icon]
Ev  |  Eriþimdüzeni  |  Ýçindekiler  |  Arama

Duyumlar | Belgelikler | Baðlantýlar | LF Nedir
[an error occurred while processing this directive]
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