[LinuxFocus-icon]
<--  | Ana Sayfa  | Erişimdüzeni  | İçindekiler  | Arama

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

[Leonardo]
Leonardo Giordani
<leo.giordani(at)libero.it>

Yazar hakkında:

Milan Politecnico Telekomünikasyon Mühendisliği fakültesinde yeni diploma aldım.Programlama ile ilgileniyorum (çoğunlukla Assembly ve C/C++).1999'dan beri neredeyse sadece Linux/Unix ile çalıştım.



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

İçerik:

 

Eşzamanlı Programlama - İleti Kuyrukları (3)

[paralel olarak çalıştır]

Özet:

Bu makale, eşzamanlı programlama hakkındaki son makaledir: Burada, protokolümüzün ikinci ve son katmanını uygulacağız. Bu katman, önceki makalede geliştirdiğimiz ilk katmanın üzerinde kullanıcı davranışlarını uygulayan fonksiyonları yaratır.

Bu serinin önceki makalelerini okumak faydalı olacaktır:


_________________ _________________ _________________

 

Protokol uygulaması - Katman 2 - Genel

ipcdemo programı, bir kullanıcıdan diğer kullanıcıya ileti gönderen bir anahtarın basit bir uygulaması olarak geliştirildi. Simülasyona biraz eğlence katmak için, "servis" kavramını ekledim: bir servis iletisi (sinyalleme), asıl işlevi kullanıcıdan kullanıcıya veri göndermek değil de kontrol bilgileri göndermek olan bir iletidir. Servis iletileri, kullanıcılar tarafından anahtara, onların canlı oldukları, onlara nasıl ulaşabilecekleri ( IPC kuyruk kimliğini göndererek) ve bağlantıyı kesecekleri iletilerini gönderir. İki servis daha tanımlanmıştır: Bağlantı kesme ve Zamanlama. İlki, anahtar tarafından kullanılır ve kullanıcıya bağlantıyı kesmeleri gerektiğini söyler. İkincisi, kullanıcının yanıt verme süresini ölçer. Daha fazlası kullanıcı ve anahtar bölümünde anlatılacaktır.

Katman 2, servis isteyen, servise cevap veren ve başlangıç işlemlerini yapan yüksek seviyeli fonksiyonları içerir. Bu fonksiyonlar Katman 1'deki fonksiyonlar kullanılarak oluşturulmuştur ve bu yüzden anlaması çok kolaydır. Sadece şuna dikkat edin: layer2.h dosyasında, ileti tiplerini gösteren (kullanıcı iletisi veya servis iletisi) ve farklı servisleri gösteren (örneğin deneyler için iki kullanıcı-tanımlı servis) isimleri tanımladım.

ipcdemo, sadece tanıtım kodudur: optimize edilmemiştir ve farkedeceğiniz gibi, birçok küresel değişken kullandım. Ama bunu okuyucunun koda değil IPC kısmına dikkatini çekmek için yaptım. Herneyse, eğer çok garip birşey bulursanız, bana hemen yazın ve tartışalım.

 

Kullanıcı süreçlerinin Uygulaması

Kullanıcı, sabit olarak anahtarın bir oğul sürecidir ( ya da daha iyisi, anahtar olarak iş yapan baba sürecin). Bu, kullanıcıların anahtar gibi başlatılan bütün değişkenlere sahiptir. Öreneğin, anahtarın kuyruk kimliğini bilir, çünkü işlemi bölmeden önce (forking) anahtar tarfından bir yerel değişkede saklanmaktadır.

Bir kullanıcı hayata geldiğinde, yaptığı ilk iş bir kuyruk yaratmak ve anahtarın ona nasıl ulaşacağını bildirmektir. Bunu yapmak için kullanıcı, iki servis iletisi gönderir: SERV_BIRTH ve SERV_QID.

/* Kuyruğu başlat*/
qid = init_queue(i);

/* Anahtara hayatta olduğumuzu bildir*/
child_send_birth(i, sw);

/* Anahtara bize nasıl ulaşabileceğini bildir*/
child_send_qid(i, qid, sw);
Sonra asıl döngüye girer: Burada kullanıcı bir ileti gönderir, diğre kullanıcalardan gelen iletileri kontrol eder ve anahtarın bir servis isteyip istemediğini kontrol eder.

İleti gönderme hakkındaki karar, olasılık temeline dayanmaktadır: myrand() fonksiyonu, verilen argümana göre normalleştirilmiş bir rastgele sayı üretir, bizim durumumuzda 100. Biz , sadece bu sayı belirlenen olasılıktan büyük ise iletiyi göndeririz. Çünkü kullanıcı, iki döngü arasında 1 saniye uyumaktadır. Bu şu demektir: Kullanıcı az ya da çok her yüz saniyede gönderme olasılığı kadar ileti gönderir. 100'ün, olasılığı gerçeğe çevirmek için yeterli olduğunu varsayıyoruz.Aslında çok azdır. Sadece çok küçük olasılık kullanmamaya dikkat edin yoksa simülasyonunuz yıllarca çalışır.

if(myrand(100) < send_prob){
  dest = 0;

  /* Anahtara, kendine ve önceki iletinin alıcısına*/
  /* ileti gönderme*/
  while((dest == 0) || (dest == i) || (dest == olddest)){
    dest = myrand(childs + 1);
  }
  olddest = dest;

  printf("%d -- U %d kullanıcısına ileti\n", (int) time(NULL), i, dest);
  child_send_msg(i, dest, 0, sw);
}
Diğer kullanıcılardan gelen iletiler aslında, diğer kullanıcıların anahtara ve onunda bize gönderdiği iletilerdir ve TYPE_CONN (bağlantı olarak) işretlenmişlerdir.
/* Basit iletiler için gelen kutusunu kontrol et*/
if(child_get_msg(TYPE_CONN, &in)){
  msg_sender = get_sender(&in);
  msg_data = get_data(&in);
  printf("%d -- U %d kullanıcısından ileti: %d\n",
         (int) time(NULL), i, msg_sender, msg_data);
}
Eğer anahtar bir servis isterse, TYPE_SERV işaretli bir ileti göndeririz ve cevap vermek zorundayız. Bağlantı kesme servisi durumda, anahtara bir bilgi iletisi göndeririz. Böylece anahtar bizi ulaşılamaz yapar ve bize ileti göndermeyi denemez. Sonra bir kalan bütün iletileri okuruz (nazik olmak için söyledim, bu kısmı atlayabiliriz), kuyruğu sileriz ve simülasyona "Gülegüle" deriz. Anahtara servis isteği gönderdiğimiz andaki zaman, o anki zamanı içeren bir iletidir: Anahtar bu zamanı iletinin gönderildiği zamandan çıkarır ve iletinin ne kadar süredir kuyrukta beklediğini çeteleye yazar. Gördüğünüz gibi burada Qos(Quality of Service- Servis Kalitesi) yapıyoruz, simülasyonumuz var olan bir telefon sisteminden daha iyidir.
/* Anahtarın servis isteyip istemediğini kontrol et*/
if(child_get_msg(TYPE_SERV, &in)){
  msg_service = get_service(&in);

  switch(msg_service){
  case SERV_TERM:
    /* Üzgünüz, kapatmak zorundayız*/
    /* ANahtara bilgi gönder*/
    child_send_death(i, getpid(), sw);

    /* Kuyruktaki son iletileri oku*/
    while(child_get_msg(TYPE_CONN, &in)){
      msg_sender = get_sender(&in);
      msg_data = get_data(&in);
      printf("%d -- U %d kullanıcısından ileti: %d\n",
             (int) time(NULL), i, msg_sender, msg_data);
    }

    /* Kuyruğu sil*/
    close_queue(qid);
    printf("%d -- U %d -- Kapatma\n", (int) time(NULL), i);
    exit(0);
    break;
  case SERV_TIME:
    /* İşimiz için süre tutmalıyız*/
    child_send_time(i, sw);
    printf("%d -- U %d -- Süre\n", (int) time(NULL), i);
    break;
  }
}
 

Anahtar sürecinin uygulaması

Ana süreç iki parçaya ayrılmıştır: oğul süreçlerin oluşturulmasından öncesi ve sonrası. İlki sırasında, oğul kuyruk kimliklerinin ve kendi kuyruk kimliğinin saklanması için bir dizi(array) oluşturur. Bu tip bir iş için tabiiki doğru bir yol değildir ama burada dinamik listelerde bahsetek uygun değildir ve tamamen yararsızdır. Herneyse, Bir çok sayıda bağlantı kabul edecek olan bir uygulama geliştirecekseniz, dinamik listeleri ve bellek aytırmalarını (memmory allocation) kullanmalısınız. uyruk kimlikleri, en başta anahtarın kuyruk kmliğine eşitlenir. Bunun anlamı kullanıcının henüz bağlanmadığıdır. Kullanıcının her bağlantıyı kesişinde, kuyruk kimliği orjinal değerine döner.

İkinci kısımda, ana süreç bir anahtar olarak görev yapar. Kullanıcını yaptığı bir döngü içinde bütün kullanıcılar bağlantıyı kesinceye kadar çalışır. Anahtar, kullanıcılardan gelen iletileri kontrol eder ve gitmesi gereken yerlere yönlendirir.

/* Kullanıcının bağlanıp bağlanmadığını kontrol et*/
if(switch_get_msg(TYPE_CONN, &in)){

  msg_receiver = get_receiver(&in);
  msg_sender = get_sender(&in);
  msg_data = get_data(&in);

  /* Eğer iletinin gönderileceği yer bağlı ise*/
  if(queues[msg_receiver] != sw){

    /* İletinin gönderildiği yere ileti gönder (gelecek cevabı bekle)*/
    switch_send_msg(msg_sender, msg_data, queues[msg_receiver]);

    printf("%d -- S -- Gönderen: %d -- Gönderilen yer: %d\n",
           (int) time(NULL), msg_sender, msg_receiver);
  }
  else{
    /* Eğer iletinin gideceği yer bağlı değilse*/
    printf("%d -- S -- Gönderilen yere ulaşılamıyor (Kullanıcı: %d - Gönderilen yer: %d)\n",
          (int) time(NULL), msg_sender, msg_receiver);
  }
Eğer kullanıcı, ahatar aracılığı ile bir ileti gönderiyorsa, bu olasılık temeline dayanan (daha önce çalışıyor olduğu gibi) bir servis isteği nesnesi olabilir. İlk durumda, kullanıcının bağlantıyı kesmesi için zorlarız, ikincisinde ise bir zaman tutma işlemine başlarız: Şu anki zamanı kaydederiz ve kullanıcıyı işaretleriz, böylece zaten bu işi yapan kullanıcı için zaman tutmuş olmayız. Eğer ileti almazsak, büyük ihtimalle bütün kullanıcılar bağlantıyı kesmiştir: Bu durumda oğul süreçlerin gerçekten bitmesini bekleriz (belki son oğul süreç son iletilerini kontrol ediyordur), kuyruğumuzu sileriz ve çıkarız.
  /* Son iletiyi gönderene rastlantısal olarak servis gönder*/
  if((myrand(100)  < death_prob) && (queues[msg_sender] != sw)){
    switch(myrand(2))
      {
      case 0:
    /* Kullanıcı bağlantıyı kesmeli*/
    printf("%d -- S -- %d Bağlantıyı kesmesi için seçilen kullanıcı\n",
          (int) time(NULL), msg_sender);
    switch_send_term(i, queues[msg_sender]);
    break;
      case 1:
    /* Bu kullanıcı için zaman tutup tutmadığımızı kontrol et*/
    if(!timing[msg_sender][0]){
      timing[msg_sender][0] = 1;
      timing[msg_sender][1] = (int) time(NULL);
      printf("%d -- S -- %d kullanıcı zaman tutmak için seçildi\n",
            timing[msg_sender][1], msg_sender);
      switch_send_time(queues[msg_sender]);
    }
    break;
      }
  }
}
else{
  if(deadproc == childs){
    /* Bütün oğul süreçler sonlandı, son ikisini son işlerini bitirmeleri için bekle*/
    waitpid(pid, &status, 0);

    /* Anahtarın kuyruğunu sil*/
    remove_queue(sw);

    /* Programı bitir*/
    exit(0);
  }
}
Şimdi servis iletilerini bekleriz: kullanıcının doğumu, kullanıcının bağlantıyı kesmesi ve zaman tutma servisi hakkında iletiler alabiliriz.
if(switch_get_msg(TYPE_SERV, &in)){
  msg_service = get_service(&in);
  msg_sender = get_sender(&in);

  switch(msg_service)
    {
    case SERV_BIRTH:
      /* Yeni bir kullanıcı bağlandı*/
      printf("%d -- S -- Kullanıcı %d'nin etkinleştirilmesi\n", (int) time(NULL), msg_sender);
      break;

    case SERV_DEATH:
      /* Kullanıcı bağlantıyı kesiyor*/
      printf("%d -- S -- %d kullanıcısı bağlantıyı kesiyor\n", (int) time(NULL), msg_sender);

      /* Listeden onun kuyruğunu sil*/
      queues[msg_sender] = sw;

      /* Kaç kullanıcının bağlantıyı kestiğini bil*/
      deadproc++;
    break;

    case SERV_QID:
      /* Kullanıcı bize kendi kuyruk kimliğini gönderiyor*/
      msg_data = get_data(&in);
      printf("%d -- S -- Kullanıcısının kuyurk kimliği : %d\n",
            (int) time(NULL), msg_sender, msg_data);
      queues[msg_sender] = msg_data;
      break;

    case SERV_TIME:
      msg_data = get_data(&in);

      /* Zaman tutma bilgileri*/
      timing[msg_sender][1] = msg_data - timing[msg_sender][1];

      printf("%d -- S -- %d kullanıcısının zaman tutma işlemi: %d saniye\n",
            (int) time(NULL), msg_sender, timing[msg_sender][1]);
      /* Kullanıcı artık zaman tutma işleminde değil*/
      timing[msg_sender][0] = 0;
      break;
    }
}
 

Son Düşünceler

Eşzamanlı programlama ile ilgili küçük makale serimizin sonuna geldik: Bütün olası durumlar göz önüne alımadı anca IPC kelimesinin neleri ifade ettiğini ve ne gibi sornları çözebileceğini öğrendiniz.Bu makale için yazdığım programı değiştirmenizi ve geliştirmenizi tavsiye ederim. Söylediğim gibi, çok süreçli programlarınhatalarını bulmak zordur. Ama hata bulucular(debuggers) hakkında daha çok bilgi edinmeniz için iyi bir fırsattır( gdb, programlama sırasında sizin en iyi arkadaşınız olacktır). Genelde programlama sırasında ilginç bulacağınız programları makalenin sonunda bulabilirsiniz.

IPC deneyileri hakkında küçük bir öneri. Çoğu zaman porgramlar sizin istediğiniz gibi çalışmayacaktır ( yukarıdaki programı defalarca çalıştı ). Ama programları böldüğünüzde (forking) ctrl-C'ye basmak bütün işelmleri öldürmez. Daha önce kill komutundan bahsetmedim ama kullanma klavuzundan (man page) kolayca anlayabilirsiniz. Programınız öldürüldükten sonra arkasında bırakacağı birkaç şey var: IPC yapıları. Yukarıdaki örnekte, çalışan süreçleri öldürürseniz, ileti kuyruklarını kesinlikle ayırmalarını kaldırmaz (deallocation). Bütün çekirdek belleğini temizlemek için ipcs ve ipcrm komutlarını kullanırız: ipcs o anki bütün ayrılmış IPC kaynaklarını (sadece sizinkini değil, hepsini. Dikkatli olun) gösterir. ipcrm bunlardan istediklerinizi kaldırmanıza yardımcı olur. Eğer ipcrm komutunu hiçbir argüman vermeden çalıştırırsanız ihtiyacınız olan bütün bilgileri size gösterir: ilk denemede önerilen sayılaer "5 70 70".

Projeyi açmak için "tar xvzf ipcdemo-0.1.tar.gz" komutunu çalıştırın. ipcdemo programını derlemek için sadece "make" komutunu projenin dizini içinde çalıştırın. "make clean" yedekleme dosyalarını, "make cleanall" hem yedekleme hem de nesne dosyalarını siler.

 

Sonuç

Bu makalenin geç yayınlanmasından dolayı hepinizden özür diliyorum. Yazılım geliştirme hayattaki tek şey değil. Her zaman olduğu gibi ilerideki makaleler ve makale konuları için sizden yaorumlar bekliyorum: "thread"ler ?  

Önerilen programlar, siteler ve yazılar

Tavsiye edilen kitaplar için eski makalelere bakın. Bu defa size programlama, hata ayıklama (debugging) ve güzerl yazılarla ilgili bazı internet adresleri vereceğim.

Hata ayıklayıcılar (debuggers), geliştiricinin en iyi arkadaşlarıdır, en azından geliştirme sırasında. ddd'den önce gdb'nin nasıl kullanıldığını öğrenin. Çünkü grafiksel araçlar güzeldir ama gerekli değildir.

"Segmentation fault" iletisi aldınız ve nerede hata yaptığınızı mı merak ediyorsunuz. Atılmış bir çekirdek (core) dosyasını gdb ile okumaya ek olarak, valgrind ile programı çalıştırıp onun bellek simülasyonu çerçeveişi (framework) özelliğinden faydalanabilirsiniz.

Farkettiğiniz gibi C'de IPC programı yazmank eğlencelidir ama karmaşıktır. Python bir çözümdür: Süreç bölme (forking) ve diğer işler için tam desteği vardır ve C genişlemesi vardır. Bir bakın, buna değer.

 

Program İndirme

 

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 

<--, Bu sayının ana sayfasına gider

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

2003-12-22, generated by lfparser version 2.43