|
|
Bu makalenin farklı dillerde bulunduğu adresler: English Castellano Deutsch Francais Russian Turkce |
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)Ö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:
|
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.
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; } }
İ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; } }
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.
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.
|
Görselyöre sayfalarının bakımı, LinuxFocus Editörleri tarafından yapılmaktadır
© Leonardo Giordani, FDL LinuxFocus.org |
Çeviri bilgisi:
|
2003-12-22, generated by lfparser version 2.43