|
|
Bu makalenin farklı dillerde bulunduğu adresler: English Castellano Deutsch Francais Italiano Nederlands Russian Turkce Polish |
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Ö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.
|
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.
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.
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:
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.
|
Görselyöre sayfalarının bakımı, LinuxFocus Editörleri tarafından yapılmaktadır
© Leo Giordani, FDL LinuxFocus.org |
Çeviri bilgisi:
|
2003-02-03, generated by lfparser version 2.35