> Alternatif(İleri) "IO" Modelleri: Şimdiye kadar "read" ve "write" fonksiyonları ile klasik "IO" işlemleri yaptık. Halbuki UNIX türevi sistemlerde, bloke oluştuğunda kullanılabilecek, alternatif "IO" modelleri de mevcuttur. Bunlara "İleri IO(Advanced IO)" modelleri denir. Bu modellere şu tip senaryoda ihtiyaç duyulmaktadır; -> "client-server" bir uygulama yazmak isteyelim. "server" programı, "n" tane "client" programlarından gelen istekleri okusun ve onlara yanıt göndersin. Haberleşme için isimli borular("named pipes") ya da soketler("sockets") kullanılsın. Ancak bu yöntemi kullandığımız zaman, okuma esnasında boruda ya da sokette hiç veri yoksa, bloke oluşacaktır. Örneğin, for (;;) { for(int i = 0; i < N; ++i) { read(...); write(...); } } şeklindeki kontrol sırasında, eğer hiç veri yoksa, bloke oluşacaktır. Dolayısıyla diğer "client" lardan gelen bilgileri göremeyeceğiz. Bu neden dolayı bu döngüyü aşağıdaki gibi güncelleyebiliriz. for (;;) { for(int i = 0; i < N; ++i) { result = read(...); if (result == -1 && errno == EAGAIN) continue; write(...); } } Fakat bu şekildeki yaklaşımda da işlemci sürekli olarak bir döngü içerisinde dönmektedir. Yani "busy-loop" oluşacaktır. Bu problemi gidermek için de "thread" ya da "process" oluşturabiliriz. Böylece her bir "client" için ayrı bir "thread" ya da "process" oluşturulur. O "client" ın istekleri o "thread" ya da "process" tarafından sağlanır. Böylece veri gelmediği zaman sadece o "thread" ya da "process" bloke olacaktır. Tabii "process" oluşturmak daha maliyetli olduğundan, "thread" oluşturmak daha mantıklı gelebilir. Ancak yaklaşım ise ölçeklenebilir("scalable") DEĞİLDİR. Yani "client" sayısı arttıkça çok daha fazla sistem kaynağı kullanılmaya başlanacağından, bazı olumsuzluklar ortaya çıkabilir. İşte "Advanced IO" işlemler problemleri çözmesi için geliştirilmiştir. POSIX sistemlerinde "Advanced IO" işlemler dört ana bölümde incelenir. Bunlar, "Multiplexed IO", "Signal Driven IO", "Asynchronous IO", "Scatter-Gather IO" modülleridir. Bu modellerden, >> "Multiplexed IO" : Bu modelde bir grup betimleyici izlemeye alınır. Bu betimleyicilerde ilgilenilen olay, "read", "write" veya "error", gerçekleşmemişse blokeye yol açar. Ta ki bu betimleyicilerden en az birinde beklenen olay gerçekleşene kadar. Bu durumda bloke çözülür. "Multiplexed IO" için "select" ve "poll" POSIX fonksiyonları kullanılmaktadır. >>> "select" : Bu fonksiyonu bir grup betimleyiciyi takip eder. Eğer beklenen aksiyon takip ettiği betimleyicilerin hiç birisinde yoksa, blokeye yol açar. Ancak en az bir tanesinde beklenen olay gerçekleşirse bloke çözülür. En kaba haliyle bu şekilde çalışır. Fonksiyon tasarımında bazı kusurlar bulunmasına rağmen, en çok kullanılan "Advanced IO" fonksiyonlarındandır. Fonksiyonun prototipi aşağıdaki gibidir. #include int select(int nfds, fd_set * readfds, fd_set * writefds, fd_set * errorfds, struct timeval * timeout); Fonksiyonun parametrelerindeki "fd_set" türü aslında bir "bit" dizisi belirtir ve her bir öğesi bir betimleyiciye karşılık gelir. Bu dizinin, -> Belli bir indeksindeki öğenin değerini "1" yapmak için "FD_SET" -> Belli bir indeksindeki öğenin değerini "0" yapmak için "FD_CLR" -> Bütün öğelerini "0" yapmak için "FD_ZERO" -> Belli bir öğesinin değerini sınamak için "FD_ISSET" makroları kullanılır. Fonksiyonun ikinci parametresine, üçüncü ve dördüncü parametrelerine sırasıyla "read", "write" ve "error/exception" amacıyla izlenecek betimleyicilerin kümesini, ilgili "bit" dizisini doldurduktan sonra, geçeriz. Böylelikle hangi betimleyicileri ne amaçla izleyeceğimizi belirlemiş oluruz. Bu parametrelere "NULL" değeri de geçilebilir, bu durumda ilgili izlemenein yapılmayacağını belirtir. Fonksiyonun birinci parametresi ise yukarıdaki üç kümedeki en yüksek betimleyicinin bir fazla değerini almaktadır. Aslında bu parametre işlemleri hızlandırmak için düşünülmüştür. Olabilecek en yüksek betimleyici değeri ise "FD_SETSIZE" sembolik sabitiyle tanımlanmıştır. Örneğin, "18", "23" ve "47" numaralı betimleyicileri çeşitli sebeplerle izlemek istiyoruz. İşte fonksiyonun birinci parametresine "48" değerini geçeriz. Ancak buraya "FD_SETSIZE" sembolik sabiti geçilse bile bir sorun oluşmayacaktır. Fonksiyonun son parametresi ise bir zaman aşımı belirtir. Zaman aşımı ise en kötü durumda blokenin ne kadar süreceğini belirtir. Örneğin, bir grup betimleyiciyi izlemek isteyelim fakat blokenin maksimum 10 saniye sürmesini, sonrasında çözülmesini isteyelim. İşte bunun için bu parametreyi kullanacağız. Bu parametrede kullanılan "timeval" yapısı ise aşağıdaki biçimde tanımlıdır. struct timeval { time_t tv_sec; // Seconds. suseconds_t tv_usec; // Microseconds. }; Bu yapı mikrosaniye çözünürlüğünde, bir zaman aralığı belirtmek için, kullanılır. Yapının her iki elemanına da "0" değeri geçilebilir, bu durumda "select" fonksiyonu işlevini hemen gerçekleştirip geri döner. Fonksiyonun ikinci, üçüncü, dördüncü ve beşinci parametrelerine "NULL" değeri geçilebilir. Bu durumda o işlev çalıştırılmaz. Fonksiyonun geri dönüş değeri ise şöyledir; -> Başarısızlık durumunda "-1" değerine döner. -> Hiç bir betimleyicide beklenen olay gerçekleşmemiş fakat zaman aşımı dolmuşsa "0" değerine geri döner. -> En az bir betimleyicide beklenen olay gerçekleşmişse, toplam gerçekleşen olay sayısını geri döndürür. Buradaki kritik nokta betimleyici adedi değil, gerçekleşen olayların adedidir. Ancak bu fonksiyonun geri dönüş değeri pek kontrol edilmez. Bu fonksiyonun detaylı kullanımı ise aşağıdaki gibidir: -> Bir grup betimleyiciyi "read" amacıyla izlemek isteyelim. Bunun için ilk olarak ilgili betimleyici kümesini bir "fd_set" nesnesi içerisinde, yukarıdaki makrolar ile, belirtmeli ve bu nesnenin adresini de fonksiyonun ikinci adresine geçmeliyiz. Fonksiyonun birinci parametresine de betimleyici kümesindeki en yüksek betimleyici değerinin bir fazlasını geçmeliyiz. Şöyleki; //... fd_set rset; FD_ZERO(&rset); FD_SET(fdr1, &rset); /* 'fd1 = 18' varsayalım. */ FD_SET(fdr2, &rset); /* 'fd2 = 23' varsayalım. */ FD_SET(fdr3, &rset); /* 'fd3 = 47' varsayalım. */ //... // max_fds = getmax(fd1, fd2, fd3); // 'psudue-code' //... select(max_fds + 1, &rset, NULL, NULL, NULL); Burada "select" fonksiyonu, bizim ona verdiğimiz betimleyicileri "read" amacıyla izler. Eğer ilgili betimleyicilerin hiç birisine bilgi gelmemişse, "select" fonksiyonu akışı bloke eder. Ancak en az birisine bilgi gelmesi durumunda bloke çözülür. Burada "select" fonksiyonunun "read" yapmadığına, sadece "read" yapılabilecek bir durum oluştuğunu tespit ettiğine, DİKKAT EDİNİZ. Tabii programcının, blokenin çözülmesiyle birlikte, hangi betimleyiciye bilgi geldiğini anlaması gerekmektedir. İşte "select" bize bunun bilgisini vermektedir. Dolayısıyla bu fonksiyonu bir kez değil, bir döngü içerisinde(genellikle) kullanılmaktadır. -> Yukarıdaki betimleyicilerin birisinde beklenen olayın gerçekleştiğini varsayalım. Örneğin, "23" numaralı betimleyici. Beklenen olayın gelmesinden dolayı bloke çözümlenecektir ve "select" fonksiyonuna geçtiğimiz "fd_set" nesnesindeki ilgili betimleyiciye karşılık gelen "bit" değeri "set", diğer "bit" değerler ise "reset" edilecektir. Yani "select" fonksiyonuna geçtiğimiz "fd_set" nesnesinin içeriğini bozmakta; beklenen olayın gerçekleştiği betimleyiciye ilişkin "bit" değerini "set", diğer "bit" değerlerini "reset" etmektedir. Dolayısıyla "select" fonksiyonundan çıktıktan sonra hangi "bit" değerlerinde değişiklik yapıldığını tek tek sorgulamalıyız. Böylelikle hangi olayın gerçekleştiğini tespit edebiliriz. Birden fazla "bit" değerinin de "set" edilmesi mümkündür. Bu nedenle kontrolü "if-else" ile değil, ayrık "if" ile yapmalıyız. Şöyleki; //... fd_set rset; FD_ZERO(&rset); FD_SET(fdr1, &rset); /* 'fd1 = 18' varsayalım. */ FD_SET(fdr2, &rset); /* 'fd2 = 23' varsayalım. */ FD_SET(fdr3, &rset); /* 'fd3 = 47' varsayalım. */ //... // max_fds = getmax(fd1, fd2, fd3); // 'psudue-code' //... if (select(max_fds + 1, &rset, NULL, NULL, NULL) == -1) exit_sys("select"); if (FD_ISSET(fdr1, &rset)) { read(frd1, ...); // 'psudue-code' } if (FD_ISSET(fdr2, &rset)) { read(frd2, ...); // 'psudue-code' } if (FD_ISSET(fdr3, &rset)) { read(frd3, ...); // 'psudue-code' } Artık "read" veya "recv" / "recvfrom" fonksiyonları ile okuma yapmamız gerekmektedir. Bu fonksiyonlar sırasyıla "pipe" ve "socket" üzerinden "read" yapan fonksiyonlardır. Bu aşamada "read" fonksiyonları blokeye yol açmayacaktır. -> Eğer çok fazla betimleyici izlenecekse, "select" fonksiyonu çıkışı, onlar için ayrık "if" deyimlerinin yazılması da yorucu gelebilir. Bu durumda iki farklı yol izlenebilir. Bunlardan ilki bütün betimleyicileri bir döngü içerisinde kontrol etmektir. Şöyleki; //... // max_fds = getmax(fd1, fd2, fd3); // 'psudue-code' //... if (select(max_fds + 1, &rset, NULL, NULL, NULL) == -1) exit_sys("select"); for (int i = 0; i <= max_fds; ++i) { if (FD_ISSET(i, &rset)) { read(i, ...); // 'psudue-code' } } Bu yaklaşım ile "rset" nesnesinin "[0, max_fds]" arasındaki "bit" değerlerine bakılmış, "set" edilenler için "read" çağrısı yapılmıştır. İkinci yöntem ise izlenecek betimleyicileri baştan bir diziye yerleştirmektir. Şöyleki; //... int fds[3]; fds[0] = fdr1; /* 'fd1 = 18' varsayalım. */ fds[1] = fdr2; /* 'fd2 = 23' varsayalım. */ fds[2] = fdr3; /* 'fd3 = 47' varsayalım. */ //... fd_set rset; FD_ZERO(&rset); for (int i = 0; i < 3; ++i) { FD_SET(fds[i], &rset); } //... // max_fds = getmax(fds, 3); // 'psudue-code' //... if (select(max_fds + 1, &rset, NULL, NULL, NULL) == -1) exit_sys("select"); for (int i = 0; i < 3; ++i) { if (FD_ISSET(fds[i], &rset)) { read(fds[i], ...); // 'psudue-code' } } Böylece izlenmek istenen betimleyicilere "fds" dizisinden ulaşabiliriz. Ancak "select" fonksiyonunu döngü içerisinde kullanırken ona verdiğimiz betimleyici kümesinin bozulacağını, dolayısıyla döngünün başında onun yeniden yüklenmesi gerektiğine dikkat ediniz. Şöyleki; fd_set rset, orset; //... FD_ZERO(&rset); FD_SET(fd1, &rset); // fd1 = 18 varsayalım FD_SET(fd2, &rset); // fd2 = 23 varsayalım FD_SET(fd3, &rset); // fd3 = 48 varsayalım maxfds = getmax(fd1, fd2, fd3); for (;;) { // Bu iki yapı nesnesi birbirine atanabilir, elemanları karşılıklı olarak birbirine atanacaktır. orset = rset; if (select(maxfds + 1, &orset, NULL, NULL, NULL) == -1) exit_sys("select"); if (FD_ISSET(fd1, &orset)) { read(fd1, ...); } if (FD_ISSET(fd2, &orset)) { read(fd2, ...); } if (FD_ISSET(fd3, &orset)) { read(fd3, ...); } } Böylelikle "select" çıkışında bozulan nesnemiz "orset" olacak, döngünün her başında ise tekrardan orjinal değerlerini kazanacaktır. -> Eğer betimleyici karşı taraf tarafından kapatılırsa, bu durumda "select" fonksiyonu yine okuma yapılmış gibi davranacaktır. Fakat "read" fonksiyonu bu durumda "0" bayt okuyacaktır. Dolayısıyla bizim de kendi tarafımızdan ilgili betimleyiciyi önce kapatmalı, devamında da bunu izleme kümesinden çıkartmalıyız. Şöyleki; if ((result = read(fds[i], buf, BUFFER_SIZE)) == -1) exit_sys("read"); if (result == 0) { close(fds[i]); FD_CLR(fds[i], &rset); } Diğer yandan bu fonksiyonu kullanırken şu noktalara da dikkat etmeliyiz: -> Bu fonksiyonun normal disk dosyaları için çağrılması anlamsızdır çünkü "select" fonksiyonu beklenen olay her zaman gerçekleşmiş gibi davranacaktır. Dolayısıyla uzun süre beklemeye yol açabilecek "shell", "pipe", "socket" gibi aygıtlar için kullanılmalı ve bu aygıtlar da blokeli modda açılmalıdır. -> "select" eşliğinde okuma yapılırken, betimleyicinin karşı tarafça kapatılması durumunda, "select" fonksiyonu sanki "read" işlemi yapılmış gibi davranacaktır. Bu durumda "read" fonksiyonu "0" bayt okuyacaktır. Dolayısıyla ilgili betimleyicinin de takip kümesinden çıkartılması gerekmektedir. -> "select" fonksiyonu ile "write" işlemi için izleme yaparken; normal şartlarda o boruda yer kalmadığında bloke oluşur ancak "select" fonksiyonu ile izleme yaptığımız zaman o boruda yer açıldıkça bloke çözülecektir. Daha sonra o boruda yer varsa, o boruya yazma yapacağız. Tıpkı "read" fonksiyonundaki gibi. Fakat burada şu noktaya dikkat etmeliyiz; "select" fonksiyonu en az bir "byte" kadarlık alan yazmaya müsaitse blokeyi çözmektedir. Fakat "write" ise tüm bilgi boruya yazılana kadar bloke oluşturmaktadır. Tabii okuyan taraf yazılan kadar okuyorsa, sorun oluşmayacaktır. Benzer durum "socket" kavramında da geçerlidir. Ancak "select" fonksiyonunun "write" işlemi için kullanılması seyrek kullanılır. -> "select" fonksiyonunun dördüncü parametresi çok kısıtlı bir kullanım alanına sahiptir, "pipe" mekanizmasında bir etkisi yoktur. "socket" mekanizmasında ise "out of band data" durumunda işlevsellik kazanır. Dolayısıyla önemli bir kullanıma sahip değildir ve genellikle "NULL" değeri geçilir. "out of band data" mevzusuna ileri konularda değinilecektir. -> Anımsanacağı üzere borularda önce yazan tarafın boruyu kapatması gerekmektedir. Eğer okuyan taraf önce kapatırsa ve yazan taraf boruya yazma yaparsa "SIGPIPE" sinyalinin oluşur. "select" fonksiyonunda, "write" için izleme yapılırken, okuyan tarafın boruyu önce kapatması durumunda sanki yazma olayı varmış gibi bir durum oluşur ve bloke çözülür. Eğer boruya yazma işlemi yapılırsa yine "SIGPIPE" sinyali oluşur. Benzer durum "socket" lerde de söz konusudur. -> Eskiden UNIX ve türevi sistemlerde, yüksek çözünürlüklü bekleme yapan "nanosleep" ve "clock_nanosleep" fonksiyonları mevcut değilken, "select" fonksiyonu "sleep" amacıyla kullanılmaktaydı. Bunun için ikinci, üçüncü ve dördüncü parametrelerine "NULL" değeri, beşinci parametresine ise belli bir süre geçilmesi gerekmektedir. Böylece mikrosaniye duyarlılığında "sleep" işlevini gerçekleştirmiş oluruz. -> "FD_SETSIZE" sembolik sabiti Linux sistemlerinde 1024 olarak tanımlanmıştır. Dolayısıyla, prosesimizin dosya betimleyici tablosunun büyüklüğü 1024'ü geçse bile, bizler sadece 1024 adedini takip edebiliriz. Bu problem için çözümler geliştirilmiş olsa da bu çözümler taşınabilir değildir. Şimdi de aşağıdaki açıklayıcı örnekleri inceleyelim: * Örnek 1, #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char* msg); int main(void) { /* Ulya Yuruk Read Value: Ulya Yuruk */ fd_set rset; FD_ZERO(&rset); FD_SET(0, &rset); if ( select(0 + 1, &rset, NULL, NULL, NULL) == -1 ) exit_sys("select"); char buf[BUFFER_SIZE + 1]; ssize_t result; if (FD_ISSET(0, &rset) && (result = read(0, buf, BUFFER_SIZE)) == -1) exit_sys("read"); buf[result] = '\0'; printf("Read Value: %s", buf); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char* msg); int main(void) { /* Ulya Yuruk Read Value: Ulya Yuruk :> */ fd_set rset, orset; FD_ZERO(&rset); FD_SET(0, &rset); char buf[BUFFER_SIZE + 1]; ssize_t result; for (;;) { orset = rset; if ( select(0 + 1, &orset, NULL, NULL, NULL) == -1 ) exit_sys("select"); if (FD_ISSET(0, &orset)) { if ((result = read(0, buf, BUFFER_SIZE)) == -1) exit_sys("read"); if (result == 0) break; } buf[result] = '\0'; printf("Read Value: %s", buf); } return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, Aşağıda yer alan adımlardan ilk önce "Step - I" adımını, daha sonra "Step - II" adımını takip ediniz. İkinci adımdaki program ilk önce blokede bekleyecektir. Daha sonra "Step - III" adımını takip ediniz. İşte çıktıyı, ikinci adımdaki programın terminalinde, görebiliriz. Daha sonra sırasıyla "Step - IV" ve "Step - V" adımlarını takip ettiğimizde, ikinci adımdaki programın terminalinden çıktılarını görebiliriz. Diğer taraftan bizler ilgili boruyu "O_RDONLY" modunda açıyor, karşı tarafta "O_WRONLY" ya da "O_RDWR" modunda açıyor olsun. Açma işlemi tamam olana dek "open" fonksiyonu blokeye yol açacaktır. İşte bu blokenin oluşmaması için ilkin "mkfifo" komutunu kullandık. Tabii aşağıdaki programdaki "Step - III", "Step - IV" ve "Step - V" in kendi iç sırasını da değiştirerek sonucu gözlemlemeliyiz. Diğer yandan programların sonlandırılması her ne kadar "Ctrl+D" ile yapılmış olsa da "Ctrl+C" ile de yapılabilir. Çünkü bir proses herhangi bir şekilde sonlanırsa, ona ait açık betimleyiciler yine kapatılacaktır. // Step - I: Creation of 'named pipes' using 'mkfifo' via 'shell' program: $ mkfifo x y z $ ls -l prw-r--r-- 1 runner27 runner27 0 Mar 4 03:46 x prw-r--r-- 1 runner27 runner27 0 Mar 4 03:46 y prw-r--r-- 1 runner27 runner27 0 Mar 4 03:46 z // Step - II: Listening the 'named pipes': #include #include #include #include #include #define BUFFER_SIZE 4096 #define MAX_SIZE 4096 void exit_sys(const char* msg); int main(int argc, char* argv[]) { /* # Command Line Arguments # x y z */ /* # OUTPUT # Opening named pipes... It may block. // ----->(1) x opened... waiting in select... selami peer closed the descriptor... // ----->(2) y opened... waiting in select... ali peer closed the descriptor... // ----->(3) z opened... waiting in select... ulya peer closed the descriptor... There is no open descriptor, closing the program... */ if (argc == 1) { fprintf(stderr, "too few arguments...\n"); exit(EXIT_FAILURE); } puts("Opening named pipes... It may block."); int fds[MAX_SIZE]; int max_fd = -1; fd_set rset, orset; int count = 0; FD_ZERO(&rset); for (int i = 1; i < argc; ++i) { if ((fds[i] = open(argv[i], O_RDONLY)) == -1) exit_sys("open"); printf("%s is open\n", argv[i]); if (fds[i] > max_fd) max_fd = fds[i]; FD_SET(fds[i], &rset); ++count; } ssize_t result; char buf[BUFFER_SIZE + 1]; for (;;) { printf("waiting in select...\n"); orset = rset; if ( select(max_fd + 1, &orset, NULL, NULL, NULL) == -1 ) exit_sys("select"); for (int i = 0; i <= max_fd; ++i) { if (FD_ISSET(fds[i], &orset)) { if ((result = read(fds[i], buf, BUFFER_SIZE)) == -1) exit_sys("read"); if (result == 0) { printf("peer closed the descriptor...\n"); close(fds[i]); FD_CLR(fds[i], &rset); --count; if (count == 0) goto EXIT; } buf[result] = '\0'; printf("%s\n", buf); } } } EXIT: printf("There is no open descriptor, closing the program...\n"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } // Step - III: Call "cat > x" command on the second 'shell' program: // ----->(1) $ cat > x selami "Ctrl+D" // Step - IV: Call "cat > x" command on the third 'shell' program: // ----->(2) $ cat > y ali "Ctrl+D" // Step - V: Call "cat > x" command on the fourth 'shell' program: // ----->(3) $ cat > z ulya "Ctrl+D" >>> "poll" : "select" fonksiyonunun alternatifi bir fonksiyondur. Her ne kadar aynı amaç için kullanılsalar da "poll" fonksiyonunun imzası ve kullanılış biçimi farklıdır. Bu fonksiyon "select" fonksiyonuna nazaran daha iyi gözükse de kullanımı daha zordur. Fonksiyonun prototipi aşağıdaki gibidir. #include int poll(struct pollfd fds[], nfds_t nfds, int timeout); Fonksiyonun birinci parametresi "pollfd" türünden bir yapı dizisidir. Çünkü "poll" fonksiyonu izlenecek betimleyicileri bir yapı dizisi olarak almaktadır. "struct pollfd" türü aşağıdaki gibi tanımlıdır; struct pollfd { int fd; // İzlenecek betimleyici belirtir. short events; // İzleme amacını belirtir. short revents; // The output event flags (see below). }; Yapının, -> "fd" isimli elemanı izlenecek betimleyiciyi belirtir. Eğer bu betimleyici değeri negatif herhangi bir değer olarak girilirse o betimleyici için izleme yapılmamaktadır. Dolayısıyla "pollfd" dizisinden bir elemanı mantıksal olarak çıkartmak için bu betimleyici negatif bir değere çekilebilir. -> "events" isimli elemanı ise izleme amacını belirtir. -> "revents" isimli eleman ise, fonksiyon sonlandığında, oluşan olay hakkında bilgi verir. Programcı yapının "fd" ve "events" elemanlarına değer atadıktan sonra "poll" fonksiyonunu çağırmalıdır. Yapının "revents" isimli elemanı ise fonksiyon tarafından "set" edilir. Fonksiyonun ikinci parametresi ise bu yapı dizisinin uzunluk bilgisini alır. Son parametre ise zaman aşımı belirtir ve milisaniye çözünürlülüğe sahiptir. Eğer bu parametreye, -> "-1" değeri geçilirse, zaman aşımı uygulanmaz. -> "0" değeri geçilirse fonksiyon, betimleyicilerin durumuna bakar ve hemen geri döner. "pollfd" yapısının "events" ve "revents" elemanları bir takım sembolik sabitler alırlar. Bunlar, "POLLIN", "POLLRDNORM", "POLLRDBAND", "POLLPRI", "POLLOUT", "POLLWRNORM", "POLLWRBAND", "POLLERR", "POLLHUP", "POLLNVAL" sembolik sabitlerinden birisini ya da birkaçını alır. Birkaçını alması durumunda "bit-wise OR" kullanılır. Bunlardan, -> "POLLIN" : Okuma amaçlı izlemeyi belirtir. Boruda ya da sokette okunacak bilgi oluştuğunda fonksiyon tarafından bu bayrak "set" edilmektedir. Soketlerde "accept" yapan tarafta bir bağlantı istedği oluştuğunda da "POLLIN" bayrağı "set" edilmektedir. Aynı zamanda soketlerde karşı taraf soketi kapattığında da "POLLIN" bayrağı "set" edilmektedir. -> "POLLOUT" : Yazma amaçlı izlemeyi belirtir. Boruya ya da sokete yazma durumu oluştuğunda (yani boruda ya da "network" tamponunda yazma için yer açıldığında) fonksiyon tarafından bu bayrak "set" edilmektedir. Aynı zamanda soketlerde karşı taraf soketi kapattığında da "POLLOUT" bayrağı "set" edilmektedir. -> "POLLERR" : Hata amaçlı izlemeyi belirtir. Bu bayrak yapının "events" elemanında "set" edilmez, fonksiyon tarafından yapının "revents" elemanında "set" edilmektedir. Bu bayrak borularda okuma yapan tarafın boruyu kapatmasıyla yazma yapan tarafta "set" edilmektedir. (Normal olarak okuyan tarafın boruyu kapattığı durumda boruya yazma yapıldığında "SIGPIPE" sinyalinin oluştuğunu anımsayınız.) Eğer okuyan taraf boruyu kapattığında boruya yazma için yer varsa yazma yapan tarafta aynı zamanda "POLLOUT" bayrağı da "set" edilmektedir. "POLLERR" bayrağı soketlerde kullanılmamaktadır. -> "POLLHUP" : Boruya yazan tarafın boru betimleyicisini kapattığında okuma yapan tarafta bu bayrak "set" edilmektedir. Bu bayrak yapının "events" elemanında "set" edilmez, fonksiyon tarafından yapının "revents" elemanında "set" edilmektedir. ("HUP", "hang up" anlamına gelmektedir.) Eğer boruya yazma yapan taraf boruyu kapattığında hala boruda okunacak bilgi varsa okuma yapan tarafta aynı zamanda "POLLIN" bayrağı da "set" edilmektedir. "POLLHUP" bayrağı soketlerde kullanılmamaktadır. -> "POLLRDHUP" : Soketlerde karşı taraf soketi kapattığında ya da "shutdown" fonksiyonu "SHUT_WR" argümanıyla çağrıldığında oluşur. -> "POLLNVAL" : Bu bayrak yapının "events" elemanında "set" edilmez. Fonksiyon tarafından eğer izlenen bir betimleyici kapalıysa yapının "revents" elemanında fonksiyon tarafından "set" edilmektedir. Bu sembolik sabitlerin anlamları için "The Linux Programming Interface" kitabından şu tabloları da vermek istiyoruz: -> Boruda bilgi yok ve yazan tarafın betimleyicisi kapalı ===> Okuyan tarafta "POLLHUP" -> Boruda bilgi var ve yazan tarafın betimleyicisi kapalı ===> Okuyan tarafta "POLLIN|POLLHUP" -> Boruda bilgi var ve yazan tarafın betimleyicisi açık ===> Okuyan tarafta "POLLIN" -> Boruda yazma için yer yok ve okuyan tarafın betimleyicisi kapalı ===> Yazan tarafta "POLLERR" -> Boruda yazma için yer var ve okuyan tarafın betimleyicisi kapalı ===> Yazan tarafta "POLLOUT|POLLERR" -> Boruda yazma için yer var ve okuyan tarafın betimleyicisi açık ===> Yazan tarafta "POLLOUT" -> Sokette bilgi var ===> Okuyan tarafta "POLLIN" -> Network tamponunda yazacak yer var ===> Yazan tarafta "POLLOUT" -> accept yapan tarafta bağlantı isteği oluştuğunda ===> accept yapan tarafta "POLLIN" -> Karşı taraf soketi kapattığında ===> karşı tarafta "POLLIN|POLLOUT|POLLRDHUP" "poll" fonksiyonunun geri dönüş değeri başarı durumunda "non-negative" değer döndürür. Eğer bu değer, -> "0" ise zaman aşımından dolayı sonlanmış demektir. Eğer zaman aşımı parametresine "0" geçilmiş ancak yine de hiç bir olay gerçekleşmemişse, yine "0" değeri ile döner. -> Pozitif ise fonksiyonun ilk parametresinde belirtilen dizideki, olayı gerçekleşen, eleman sayısına döner. Toplam olay sayısına dönmez. Ancak fonksiyon hata durumunda "-1" ile geri döner ve "errno" uygun değere çekilir. Diğer yandan bu fonksiyonu kullanırken şu noktalara da dikkat etmeliyiz: -> Fonksiyonu yine bir döngü içerisinde çağırmalı, birinci parametresindeki dizinin her bir elemanını beklenen olaya karşın kontrol etmeliyiz. Bunun için yukarıdaki yapının "revents" elemanını kullanmalıyız. Eğer bu eleman "0" değerinde ise beklenen olay gerçekleşmemiş demektir. Tabii bu kontrollerleri yine ayrık "if" deyimleriyle gerçekleştirmeliyiz. //... struct pollfd pfds[1]; pfds[0].fd = 0; pfds[0].events = POLLIN; // Okuma yapacağımızı belirtiyoruz. //... for (;;) { if (poll(pfds, 1, -1) == -1) exit_sys("poll"); if (pfds[0].revents & POLLIN) { read(pfds[0].fd, ...); // 'psudue-code' //... } } //... -> Borular ve soketler kapatıldığında hem "POLLIN" hem de "POLLHUP" olayları gerçekleşebilir. Dolayısıyla bu olayların takibini ayrık "if" deyimleri ile değil "if-else if" deyimleri ile kontrol edilmeleri daha uygun olur. Çünkü karşı taraf boruya yada sokete bilgi yazıp hemen ardından boruyu yada soketi kapattığında "POLLIN" ve "POLLHUP" birlikte oluşabilir. Bu durumda, ayrık "if" deyimleri kullanılırsa ve "POLLIN" kontrolünde boru yada sokettekilerin hepsi okunmazsa, eksik okuma yapılmış olacağız. Eğer "if-else if" yaparsak, eksik okusak bile döngünün bir sonraki turunda yine "POLLIN" ve "POLLHUP" oluşacağından, okumaya devam edebileceğiz. Ta ki borudakilerin hepsi okunana kadar. Boruda bir şey kalmadığında sadece "POLLHUP" oluşacaktır. Çünkü "POLLHUP" yalnızda bir defa oluşmamakta, kapatılan boru ya da soket üzerinde "poll" çağrısı yapılırsa, tekrar tekrar oluşmaktadır. -> Eğer ilgili betimleyicinin değeri eksi ise "poll" fonksiyonu o betimleyici ile ilgilenmeyecektir. Örneğin, "-1" değerini atayabiliriz. Şimdi de aşağıdaki açıklayıcı örnekleri inceleyelim: * Örnek 1, Aşağıdaki örnekte "shell" aygıtı kullanılmıştır. #include #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char* msg); int main(int argc, char* argv[]) { ssize_t result; char buf[BUFFER_SIZE + 1]; struct pollfd pfds[1]; pfds[0].fd = 0; pfds[0].events = POLLIN; // Okuma yapacağız. for (;;) { if (poll(pfds, 1, -1) == -1) exit_sys("poll"); if (pfds[0].revents & POLLIN) { if ((result = read(pfds[0].fd, buf, BUFFER_SIZE)) == -1) exit_sys("read"); buf[result] = '\0'; printf("%s", buf); } } return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2.0.0, Aşağıdaki örnekte ise borular kullanılmıştır. Toplam açılan boru adedince döngü ilerletilmiş, bir diğer yandan da kapatılan boruların adedi sayılmıştır. Böylelikle diziden mantıksal çıkartma yapılmış oldu, kapatılan betimleyiciler açısından. #include #include #include #include #include #define BUFFER_SIZE 4096 #define MAX_SIZE 128 void exit_sys(const char *msg); int main(int argc, char *argv[]) { /* # Command Line Arguments # x y z */ if (argc == 1) { fprintf(stderr, "too few arguments!...\n"); exit(EXIT_FAILURE); } printf("opens named pipes... it may block...\n"); struct pollfd pfds[MAX_SIZE]; // "count" ile açılan toplam boruların adedini // "tcount" ile kapatılacak boruların sayısını tutmuş olacağız. int tcount, count = 0; for (int i = 1; i < argc; ++i) { if (count >= MAX_SIZE) { fprintf(stderr, "too many arguments, last arguments ignored!...\n"); break; } if ((pfds[i - 1].fd = open(argv[i], O_RDONLY)) == -1) exit_sys("open"); pfds[i - 1].events = POLLIN; // Okuma yapacağız. printf("%s opened...\n", argv[i]); ++count; } // "count" ile aslında esas dizimizi baştan sona dolaşmış olacağız. // "tcount" ile kapatılan boruların sayısını tutmuş olacağız. tcount = count; ssize_t result; char buf[BUFFER_SIZE + 1]; // Buradaki "+1", yazının sonuna '\0' koyabilmek içindir. for (;;) { printf("waiting at poll...\n"); if (poll(pfds, count, -1) == -1) exit_sys("poll"); for (int i = 0; i < count; ++i) { if (pfds[i].revents & POLLIN) { printf("POLLIN occured...\n"); if ((result = read(pfds[i].fd, buf, BUFFER_SIZE)) == -1) exit_sys("read"); buf[result] = '\0'; printf("%s\n", buf); } else if (pfds[i].revents & POLLHUP) { // İlgili boru kapatıldığında, "POLLHUP" oluşacaktır. printf("POLLHUP occured...\n"); // Dolayısıyla o boruya ilişkin betimleyiciyi kapatıyoruz. close(pfds[i].fd); // Dizinin ilgili indisindeki yapının "fd" değerini "-1" e çekiyoruz. // "-1" olduğu için artık "poll" fonksiyonu bu betimleyici ile ilgilenmeyecektir. // Dolayısıyla dizimide yer değişikliği yapmak zorunda değiliz. pfds[i].fd = -1; // En sonunda da sayacı bir azaltıyoruz. Çünkü kapatılan bir adet betimleyici mevcut. --tcount; } } // Eğer en bütün betimleyiciler kapatılmışsa da döngüden çıkıyoruz. if (tcount == 0) break; } printf("there is no descriptor open, finishes...\n"); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2.0.1, Pekala yukarıdaki örnekte "-1" değerini vererek diziden mantıksal çıkartma yapmak yerine, kapatılanı diziden gerçekten çıkartabiliriz. #include #include #include #include #include #define BUFFER_SIZE 4096 #define MAX_SIZE 128 void exit_sys(const char *msg); int main(int argc, char *argv[]) { /* # Command Line Arguments # x y z */ char buf[BUFFER_SIZE + 1]; struct pollfd pfds[MAX_SIZE]; ssize_t result; int tcount, count; if (argc == 1) { fprintf(stderr, "too few arguments!...\n"); exit(EXIT_FAILURE); } printf("opens named pipes... it may block...\n"); count = 0; for (int i = 1; i < argc; ++i) { if (count >= MAX_SIZE) { fprintf(stderr, "too many arguments, last arguments ignored!...\n"); break; } if ((pfds[i - 1].fd = open(argv[i], O_RDONLY)) == -1) exit_sys("open"); pfds[i - 1].events = POLLIN; printf("%s opened...\n", argv[i]); ++count; } tcount = count; for (;;) { printf("waiting at poll...\n"); if (poll(pfds, count, -1) == -1) exit_sys("poll"); for (int i = 0; i < count; ++i) { if (pfds[i].revents & POLLIN) { printf("POLLIN occured...\n"); if ((result = read(pfds[i].fd, buf, BUFFER_SIZE)) == -1) exit_sys("read"); buf[result] = '\0'; printf("%s\n", buf); } else if (pfds[i].revents & POLLHUP) { printf("POLLHUP occured...\n"); close(pfds[i].fd); pfds[i] = pfds[tcount - 1]; --tcount; } } count = tcount; if (count == 0) break; } printf("there is no descriptor open, finishes...\n"); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2.1, Aşağıdaki örnekte de borular kullanılmıştır. Ancak yukarıdakine nazaran tek bir sayaç kullanılmıştır. Dolayısıyla döngü daha kısa sürede tamamlanabilir. #include #include #include #include #include #define BUFFER_SIZE 4096 #define MAX_SIZE 128 void exit_sys(const char *msg); int main(int argc, char *argv[]) { /* # Command Line Arguments # x y z */ if (argc == 1) { fprintf(stderr, "too few arguments!...\n"); exit(EXIT_FAILURE); } printf("opens named pipes... it may block...\n"); struct pollfd pfds[MAX_SIZE]; // "count" açılan ve kapatılan boruların takibini yapacağız. int count = 0; for (int i = 1; i < argc; ++i) { if (count >= MAX_SIZE) { fprintf(stderr, "too many arguments, last arguments ignored!...\n"); break; } if ((pfds[i - 1].fd = open(argv[i], O_RDONLY)) == -1) exit_sys("open"); pfds[i - 1].events = POLLIN; printf("%s opened...\n", argv[i]); ++count; } // Bu noktada açılan boruların adedini bilmekteyiz. ssize_t result; char buf[BUFFER_SIZE + 1]; // Buradaki "+1", yazının sonuna '\0' koyabilmek içindir. for (;;) { printf("waiting at poll...\n"); if (poll(pfds, count, -1) == -1) exit_sys("poll"); for (int i = 0; i < count; ++i) { if (pfds[i].revents & POLLIN) { printf("POLLIN occured...\n"); if ((result = read(pfds[i].fd, buf, BUFFER_SIZE)) == -1) exit_sys("read"); buf[result] = '\0'; printf("%s\n", buf); } else if (pfds[i].revents & POLLHUP) { // İlgili boru kapatıldığında, "POLLHUP" oluşacaktır. printf("POLLHUP occured...\n"); // Dolayısıyla o boruya ilişkin betimleyiciyi kapatıyoruz. close(pfds[i].fd); // Kapatılan boruya ilişkin betimleyiciye sahip nesnemiz, dizinin // son indisindeki nesne ile yer değiştirilmşitir. Aslında o indis // kapatıldığı için, dizinin sonundaki onun yerini almıştır. pfds[i] = pfds[count - 1]; // En sonunda da sayacı bir azaltıyoruz. Döngünün her turunda --count; } } if (count == 0) break; } printf("there is no descriptor open, finishes...\n"); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } Pekiyi "select" ve "poll" fonksiyonlarını karşılaştırırsak; -> "select" fonksiyonunun kullanımı, "poll" fonksiyonuna nazaran, görece daha kolaydır. -> "select" fonksiyonu azami "FD_SETSIZE" sembolik sabitinin değeri kadar betimleyici desteklerken, "poll" fonksiyonu için böyle bir azami sınır yoktur. Dolayısıyla "poll" fonksiyonu ile istediğimiz kadar betimleyiciyi takip edebiliriz. İşte "poll" fonksiyonunun en büyük avantajı da bu özelliğidir. -> Her iki fonksiyonda da betimleyici sayısı arttıkça, performans düşebilir. -> "poll" fonksiyonunu kullanabilmek için "pollfd" türünden bir yapı dizisinin oluşturulması gerekir. Ancak "select" fonksiyonundaki "fd_set" veri yapısı "bit" düzeyinde olduğu için daha az yer kaplama eyilimdedir. -> "select" fonksiyonunu kullanırken, fonksiyona verdiğimiz kümeler fonksiyon tarafından güncellendiği için, fonksiyonun her çağrılmasında kümelerin orjinal değerini saklayarak kullanmalıyız. -> "select" fonksiyonundaki zaman aşımı duyarlılığı mikrosaniye, "poll" fonksiyonundaki ise "milisaniye" düzeyindedir. Bu iki fonksiyonun ortak dezavantajı da betimleyici sayısının artması ile performanslarının düşme eyiliminde olmasıdır. İşte Linux sistemleri, bunu telafi etmek için, "epoll" isimli bir fonksiyon geliştirmişlerdir. Ancak bu fonksiyon, diğer iki fonksiyonun aksine bir POSIX fonksiyonu değil, bir Linux fonksiyonudur. >>> "epoll" Fonksiyonları : Bu fonksiyonlar Linux sistemlerinde, "poll" ve "select" fonksiyonuna nazaran, en iyi performansı veren mekanizmadır. Ancak POSIX olmadığı için taşınabilir değildir. Bu fonksiyonları, temelde üç fonksiyondan oluşur. Bu fonksiyonlar, "epoll_create", "epoll_create1", "epoll_ctl" ve "epoll_wait" isimli fonksiyonlardır. Bu fonksiyonlardan, >>>> "epoll_create" : Fonksiypnun prototipi aşağıdaki gibidir. #include int epoll_create(int size); Fonksiyonun parametresi kaç adet betimleyicinin izleneceğine dair bir ip ucu belirtmektedir. Fakat programcı bu parametre için kullandığı argümandan daha fazlasını da izleyebilir. Daha sonra bu parametre tasarımcıları rahatsız etmiş ve "epoll_create1" isimli fonksiyon geliştirilmiştir. Fonksiyon başarı durumunda bir "handler" işlevi gören nesneye, hata durumunda "-1" değerine geri döner. >>>> "epoll_create1" : Fonksiypnun prototipi aşağıdaki gibidir. #include int epoll_create1(int flags); Fonksiyon parametre olarak ya "0" ya da "EPOLL_CLOEXEC" bayrağını alır. Fonksiyon başarı durumunda bir "handler" işlevi gören nesneye, hata durumunda "-1" değerine geri döner. >>>> "epoll_ctl" : Fonksiyonun prototipi aşağıdaki gibidir. #include int epoll_ctl(int epfd, int op, int fd, struct epoll_event *_Nullable event); Fonksiyonun birinci parametresi, "epoll_create" veya "epoll_create1" ile elde ettiğimiz "handle" değeridir. İkinci parametre ise "EPOLL_CTL_ADD", "EPOLL_CTL_MOD" veya "EPOLL_CTL_DEL" değerlerinden birisini alır. Bu değerlerden, -> "EPOLL_CTL_ADD" : İzlemek istediğimiz betimleyiciyi mekanizmaya dahil etmek için kullanılırız. -> "EPOLL_CTL_MOD" : İzlemek istediğimiz betimleyiciyle ilgili izleme değişikliği için kullanırız. -> "EPOLL_CTL_DEL" : İzlemek istediğimiz betimleyiciyi mekanizmadan çıkartmak için kullanırız. Fonksiyonun üçüncü parametresi ise izlenecek betimleyiciyi, son parametre ise izlenecek olayı belirtir. Son parametrenin türü olan "struct epoll_event" türü şöyle tanımlıdır: struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; Bu yapının "events" isimli elemanı izlenecek olayıları anlatır. Bu elemanın alabileceği tipik değerler, "EPOLLIN", "EPOLLOUT", "EPOLLERR", "EPOLLHUP", "EPOLLET", ... değerleri veya bu değerlerin "bit-wise OR" ile birleştirilmiş hali olabilir. Bu değerlerden, -> "EPOLLIN" : Okuma amaçlı izlemeyi belirtir. Boruda ya da sokette okunacak bilgi oluştuğunda "epoll_wait" tarafından bu bayrak "set" edilir. Soketlerde "accept" yapan tarafta bir bağlantı istedği oluştuğunda da "EPOLLIN" bayrağı "epoll_wait" tarafından "set" edilmektedir. "EPOLLIN" bayrağı aynı zamanda karşı taraf soketi kapattığında da oluşmaktadır. -> "EPOLLOUT" : Yazma amaçlı izlemeyi belirtir. Boruya ya da sokete yazma durumu oluştuğunda (yani boruda ya da "network" tamponunda yazma için yer açıldığında) fonksiyon tarafından bu bayrak "set" edilmektedir. "EPOLLOUT" bayrağı aynı zamanda karşı taraf soketi kapattığında da oluşmaktadır. -> "EPOLLERR" : Hata amaçlı izlemeyi belirtir. Bu bayrak "epoll_ctl" fonksiyonunda "set" edilmez, "epoll_wait" fonksiyonu tarafından "set" edilmektedir. Bu bayrak borularda okuma yapan tarafın boruyu kapatmasıyla yazma yapan tarafta "set" edilmektedir. (Normal olarak okuyan tarafın boruyu kapattığı durumda boruya yazma yapıldığında "SIGPIPE" sinyalinin oluştuğunu anımsayınız.) Eğer okuyan taraf boruyu kapattığında boruya yazma için yer varsa yazma yapan tarafta aynı zamanda "EPOLLOUT" bayrağı da "set" edilmektedir. "EPOLLERR" bayrağı soketlerde kullanılmamaktadır. -> "EPOLLHUP" : Boruya yazan tarafın boru betimeleyicisini kapattığında okuma yapan tarafta bu bayrak "set" edilmektedir. Bu bayrak "epoll_ctl" fonksiyonunda set edilmez, "epoll_wait" tarafından yapının "set" edilmektedir. ("HUP", "hang up" anlamına gelmektedir.) Eğer boruya yazma yapan taraf boruyu kapattığında hala boruda okunacak bilgi varsa okuma yapan tarafta aynı zamanda "EPOLLIN" bayrağı da set edilmektedir. "EPOLLHUP" bayrağı soketlerde kullanılmamaktadır. -> "EPOLLET" : "epoll" fonksiyonunun "Edge Triggered" mı "Level Triggered" mı çalışacağına karar verir. -> ... Daha önce "poll" bayrakları için verdiğimiz tabloyu "epoll" bayrakları için de benzer biçimde vermek istiyoruz: -> Boruda bilgi yok ve yazan tarafın betimleyicisi kapalı ===> Okuyan tarafta "EPOLLHUP" -> Boruda bilgi var ve yazan tarafın betimleyicisi kapalı ===> Okuyan tarafta "EPOLLIN|EPOLLHUP" -> Boruda bilgi var ve yazan tarafın betimleyicisi açık ===> Okuyan tarafta "EPOLLIN" -> Boruda yazma için yer yok ve okuyan tarafın betimleyicisi kapalı ===> Yazan tarafta "EPOLLERR" -> Boruda yazma için yer var ve okuyan tarafın betimleyicisi kapalı ===> Yazan tarafta "EPOLLOUT|EPOLLERR" -> Boruda yazma için yer var ve okuyan tarafın betimleyicisi açık ===> Yazan tarafta "EPOLLOUT" -> Sokette bilgi var ===> Okuyan tarafta "EPOLLIN" -> Network tamponunda yazacak yer var ===> Yazan tarafta "EPOLLOUT" -> accept yapan tarafta bağlantı isteği oluştuğunda ===> accept yapan tarafta "EPOLLIN" -> Karşı taraf soketi kapattığında ===> karşı tarafta "EPOLLIN|EPOLLOUT|EPOLLRDHUP" Yapının "data" elemanı ise bir "union" olarak, typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; }epoll_data_t; biçimde tanımlanlanmıştır. Bu yapı türü bir nevi bilgi almak için, kullanılır. Eğer programcı, daha fazla bilgi almak istiyorsa, o bilgileri bir yapı içerisinde tanımlar ve yapının adresini de "epoll_data_t" türünün "ptr" elemanına atar. "epoll_ctl" fonksiyonu başarı durumunda "0", hata durumunda "-1" değerine geri döner ve "errno" değişkenini uygun değere çeker. Pekiyi "epoll_event" yapısının "events" elemanında kullanılan "Edge Triggered" ve "Level Triggered" kavramları da nelerdir? Aslında bu kavramlar "select", "poll" ve "epoll" fonksiyonlarının arka planında gerçekleşen elektronik olayları betimlerler. Bu kavramlardan, >>>>> "Level Triggered" : Düzey Tetikleme olarak Türkçeleştirilebilir. Elektronik devrelerdeki Kare Dalga'ya benzer. Bir olayın gerçekleşmesi için yine bir dalganın gelmesine ihtiyaç duyulur ancak dalga kesildiğinde o olay devam eder. "select" ve "poll" fonksiyonları ile "epoll" fonksiyonunun varsayılan hali "Level Triggered" olarak çalışmaktadır. Yani bir boruya bilgi geldiği zaman ve bu fonksiyonlar çağrıldığında, bu fonksiyonlar durumu bize bildirirler. Gelen o bilgi borudan okunmadığı sürece, bu fonksiyonları tekrar çağırmamız durumunda, bu fonksiyonlar durumun yine bize bildirecektir. Örneğin, "stdin" dosyasını izliyorsak ve klavyeden bir giriş yapıldıysa bu fonksiyonlar blokeyi çözer. Eğer biz "read" ile okuma yapmadan bu fonksiyonları çağırırsak, artık bloke oluşmaz. Çünkü bir kez tetiklenme gerçekleşmiş, ona ilişkin aksiyon alınmıştır. Yani işlemcinin o ayağında 5V bulunduğu sürece, bir işlem yapılacaktır. >>>>> "Edge Triggered" : Kenar Tetikleme olarak da Türkçeleştirilebilir. Elektronik devrelerdeki Kare Dalga tipik örnektir. Bir olayın gerçekleşmesi için bir dalganın gelmesine ihtiyaç duyulmasıdır. Dalga kesildiğinde de olay son bulacaktır. Dalga geldikçe o olay gerçekleşecektir. "epoll" fonksiyonunun son parametresi olan "epoll_event" yapısının "events" elemanına "EPOLLET" eklenirse, "Edge Triggered" olarak çalışır. Örneğin, "stdin" dosyasını izliyorsak ve klavyeden bir giriş yapıldıysa bu fonksiyonlar blokeyi çözer. Ancak okuma yapmazsak, tekrar bloke oluşur. Çünkü bir kez tetikleme gerçekleşmiş ancak ona ilişkin aksiyon alınmamıştır. Yani işlemcinin o ayağındaki volt yalnızca sıfırdan beşe yükselmesi durumunda bir işlem yapılacaktır. >>>> "epoll_wait ": Tıpkı "select" ve "poll" fonksiyonlarında olduğu gibi, eğer izlenen betimleyicilerde beklenen olay gerçekleşmezse, blokeye yol açar. Ancak en az bir betimleyicide beklenen olay gerçekleşmişse, blokeyi çözer. Fonksiyonun prototipi aşağıdaki gibidir. #include int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); Fonksiyonun birinci parametresi, "epoll_ctl" fonksiyonunun birinci parametresine geçtiğimiz, "handle" değeridir. İkinci parametre ise oluşan olayların saklanacağı dizi adresidir. Üçüncü parametre ise ikinci parametresine başlangıç adresini geçtiğimiz dizinin uzunluğudur. Son parametre ise zaman aşımı parametresidir. "-1" değeri zaman aşımının kullanılmayacağını, "0" ise betimleyicilere hemen bakılıp çıkılacağı anlamındadır. Fonksiyon, -> Başarı durumunda, ikinci parametresine başlangıç adresini geçtiğimiz, diziye eklediği eleman sayısına. -> Başarısızlık durumunda "-1" ile geri döner. -> "0" ile geri dönmesi ise zaman aşımına takıldığı anlamındadır. Bu fonksiyonları ise şu şekilde kullanmalıyız; -> "epoll_create" veya "epoll_create1" fonksiyonu ile bizler bir "handle" elde ederiz. -> "struct epoll_event" türünden bir nesne tanımlayıp, içini amacımıza uygun biçimde doldurmalıyız. Sonrasında takip edeceğimiz betimleyicileri ve az evvel oluşturduğumuz "struct epoll_event" türünden nesneyi, "epoll_ctl" fonksiyonu ile, "epoll" mekanizmasına dahil ederiz. Burada her bir betimleyici için ayrı ayrı "epoll_ctl" fonksiyon çağrısını yapmak durumundayız. -> "epoll_wait" fonksiyonunu yine bir döngü içerisinde çağırıp, gerçekleşen olayları işleyeceğiz. Yine "EPOLLHUP" ve "EPOLLIN" olaylarını ayrık "if" deyimleri ile DEĞİL, "if-else if" deyimi ile kontrol etmeliyiz. -> En sonunda "epoll_create" ları ile elde ettiğimiz "handler" nesnesini "close" fonksiyonu ile kapatmalıyız. Böylelikle bütün mekanizma da sonlanmış olacaktır. Her ne kadar programın sonlanması ile bu "handler" değeri de yok edilse bile, erkenden mekanizmayı sonlandırmak isteyedebiliriz. Diğer yandan "epoll" fonksiyonlarını kullanırken şu noktalara da dikkat etmeliyiz; -> Belli bir betimleyiciyi izleme kümesinden çıkartmak için, normal olarak, "epoll_ctl" fonksiyonuna "EPOLL_CTL_DEL" sembolik sabitini geçeriz. Ancak "epoll" fonksiyonlarında buna çoğu kez gerek kalınmaz. Bir dosyaya ilişkin son betimleyici de kapatılırsa, o betimleyici izleme kümesinden otomatik olarak çıkartılır. -> "epoll_wait" fonksiyonunun "maxevents" isimli elemanına geçtiğimiz argüman aslında bizim takip etmek istediğimiz olayların adedini belirtir. Çünkü bu olayları bizler ikinci parametresi olan "events" dizisi üzerinden takip edeceğiz. Ancak bu demek değildir ki mekanizmanın çalışması sırasında "maxevents" e geçtiğimiz argüman kadar olay gerçekleşecek. Örneğin, "maxevents" değişkenine "5" değerini geçelim. Dizimizin boyutu da aslında "10" elemanlı olsun. İşte fonksiyonumuz maksimum "5" adet olayı takip edip, "events" dizisine işleyecektir. Pekala o an "15" adet olay da gerçekleşmiş olabilir. "15" olaydan "5" tanesini takip etmiş olacağız. Eğer "maxevents" için "10" değerini kullansaydık ve sistemimizde yine "15" tane olay olsaydı, "10" tanesinin takibini yapabilmiş olacaktık. Özetle "maxevents" ile dizimizin boyutunu belirtir, fonksiyonun geri dönüş değeriyle de dizinin ilk kaç elemanının doldurulduğunu tespit ederiz. Şimdi de aşağıdaki örnekleri inceleyelim: * Örnek 1, Aşağıdaki örnekte "shell" programı kullanıldığı için "EPOLLHUP" için kontrol yapılmamıştır. "0" nolu betimleyiciden okuma işlemi yapılmıştır. #include #include #include #include #define MAX_EVENTS 1 #define BUFFER_SIZE 1024 void exit_sys(const char *msg); int main(int argc, char *argv[]) { int epfd; if ((epfd = epoll_create(1)) == -1) exit_sys("epoll_create"); struct epoll_event ee; ee.events = EPOLLIN; ee.data.fd = 0; if (epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &ee) == -1) exit_sys("epoll_ctl"); // Örneğimizde "0" numaralı betimleyiciyi kullanacağımız için, dizinin boyutunu "1" yaptık. struct epoll_event ee_result[MAX_EVENTS]; // Gerçekleşen olayın adedini tutacaktır, o olayın gerçekleştiği betimleyici adetlerini değil. int n_events; ssize_t result; // Ne kadarlık okuma yapıldığı bilgisini tutmaktadır. char buf[BUFFER_SIZE + 1]; // Okunanların yazılacağı dizi. for (;;) { if ((n_events = epoll_wait(epfd, ee_result, MAX_EVENTS, -1)) == -1) exit_sys("epoll_wait"); for (int i = 0; i < n_events; ++i) { if (ee_result[i].events & EPOLLIN) { if ((result = read(ee_result[i].data.fd, buf, BUFFER_SIZE)) == -1) exit_sys("read"); if (result == 0) goto EXIT; buf[result] = '\0'; printf("%s", buf); } } } EXIT: close(epfd); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, Aşağıdaki örnekte ise "x", "y" ve "z" isimli boruları önce oluşturmalıyız ve ayrı "shell" programları ile bu boruları açmalıyız. Daha sonra bu boruları kullanarak programımızı çalıştırabilir, diğer terminallerden veri gönderebilir, programımız üzerinden de bunları okuyabiliriz. Borular kullandığımız için de "EPOLLHUP" oluşmaktadır. #include #include #include #include #include #define BUFFER_SIZE 4096 #define MAX_SIZE 128 #define MAX_EVENTS 5 void exit_sys(const char *msg); int main(int argc, char *argv[]) { /* # Command Line Arguments # x y z */ if (argc == 1) { fprintf(stderr, "too few arguments!...\n"); exit(EXIT_FAILURE); } int epfd; // "handler" değerini tutan değişken. if ((epfd = epoll_create(1)) == -1) exit_sys("epoll_create"); printf("opens named pipes... it may block...\n"); int fd; // Açılan boruya ilişkin betimleyici. struct epoll_event ee; int count = 0; // Toplam betimleyici sayısı. for (int i = 1; i < argc; ++i) { if (count >= MAX_SIZE) { fprintf(stderr, "too many arguments, last arguments ignored!...\n"); break; } // Komut satırı argümanlarında belirtilen boruları açıyoruz. if ((fd = open(argv[i], O_RDONLY)) == -1) exit_sys("open"); // Yapının içini dolduruyoruz. ee.events = EPOLLIN; ee.data.fd = fd; // Her bir betimleyici için bu çağrıyı yapmak durumundayız. if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ee) == -1) exit_sys("epoll_ctl"); printf("%s opened...\n", argv[i]); ++count; } int nevents; // Beklenen kaç adet olayın gerçekleştiğini sayıyoruz. ssize_t result; // Borudan yapılan okuma miktarı. char buf[BUFFER_SIZE + 1]; // Borudan okunanların yazılacağı dizi. struct epoll_event ree[MAX_EVENTS]; // Beklenen eylemlerin sonuçlarının yazılacağı dizi. for (;;) { printf("waiting at epoll_wait...\n"); // (Varsa)Gerçekleşen olayıları "get" ediyoruz. if ((nevents = epoll_wait(epfd, ree, MAX_EVENTS, -1)) == -1) exit_sys("epoll_wait"); // Sonuçları irdeliyoruz. for (int i = 0; i < nevents; ++i) { if (ree[i].events & EPOLLIN) { // Karşı taraf boruya yazmışsa, "EPOLLIN"; printf("EPOLLIN occured...\n"); if ((result = read(ree[i].data.fd, buf, BUFFER_SIZE)) == -1) exit_sys("read"); buf[result] = '\0'; printf("%s\n", buf); } else if (ree[i].events & EPOLLHUP) { // Karşı taraf boruyu kapatmışsa, "EPOLLHUP"; printf("EPOLLHUP occured...\n"); close(ree[i].data.fd); --count; // Kapatılan borular için sayacı azaltıyoruz. Böylelikle dizimiz daha erken sonlanacaktır. } } if (count == 0) break; } printf("there is no descriptor open, finishes...\n"); close(epfd); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } Linux sistemlerindeki "epoll" fonksiyonları ile "POSIX" fonksiyonu olan "select" ve "poll" fonksiyonlarını performans açısından, saniye cinsinden, karşılaştırmak istersek; Number of descriptors poll() CPU time select() CPU time epoll CPU time 10 0.61 0.73 0.41 100 2.9 3.0 0.42 1000 35 35 0.53 10000 990 930 0.66 Bu sonuçlar "The Linux Programming Environment by Michale Kerrisk" kitabından alınmıştır. Görüldüğü üzere Linux sistemleri söz konusu olduğunda "epoll" fonksiyonu daha performanslı çalışmaktadır ancak bu fonksiyonun "POSIX" uyumlu OLMADIĞINA DİKKAT EDİNİZ. >> "Signal Driven IO" : Bu modelde bir grup betimleyici izlemeye alınır. Bu betimleyicilerde ilgilenilen olay, "read", "write" veya "error", gerçekleşmemişse blokede beklenmez. Beklenen olay gerçekleştiğinde "SIGIO" sinyali prosese gönderilir. Programcı da bu blokeye maruz kalmadan "read" ve/veya "write" yapabilir. Güncel POSIX standartlarında bulunmamaktadır. Linux ve bazı UNIX türevi sistemler desteklemektedir. Bu modeli şu aşamaları takip ederek kullanabiliriz: -> Betimleyici(ler) "open" fonksiyonuyla açılırlar. Dolayısıyla maksadımızın ne olduğuna bağlı olarak, "open" fonksiyonuna ilgili bayrakları da geçmeliyiz. Örneğin, "O_RDONLY" bayrağını kullanmamız sadece "read", "O_WRONLY" kullanmamız yalnızca "write", "O_RDWR" kullanmamız ise hem "read" hem "write" amacını taşıdığını belirtir. Eğer "socket" söz konusu ise ilgili fonksiyonlar kullanılarak açılır. -> "SIGIO" sinyaline ilişkin bir "signal handler" fonksiyon "set" edilir. -> İlgili betimleyicide beklenen olay oluştuğunda, hangi prosese sinyal gönderileceğini, "fcntl" fonksiyonu üzerinden "set" ederiz. Fakat genel olarak sinyalin kendi prosesimize gönderilmesini isteriz. Dolayısıyla "fcntl" fonksiyonunun ikinci parametresine "F_SETOWN", üçüncü parametresine ise "getpid()" çağrısını ekleriz. Böylece ilgili olay gerçekleştiğinde, kendimize ilgili sinyali göndermiş oluruz. Eğer üçüncü parametreye negatif bir değer geçersek, o değerin mutlak değerindeki ID değerine sahip proses grubu, hedef alınır. -> "fcntl" fonksiyonu ile ilgili betimleyici blokesiz moda sokulmalıdır. Ancak bunu yaparkan, ilk önce kullanılan bayrakları, "F_GETFL" ile "get" etmeliyiz. Daha sonra elde ettiğimiz bayrak bilgilerini, "F_SETFL" komutunu da kullanarak, "O_NONBLOCK | O_ASYNC" bayraklarıyla "bit-wise OR" ile birleştirmeliyiz. Ancak "O_ASYNC" bayrağının da POSIX standartlarında bulunmadığına dikkat ediniz. -> Artık akış normal biçimde devam eder. İlgilenilen olay gerçekleştiğinde, yukarıda bahsedilen sinyal oluşacaktır. Bu modülü kullanırken şu noktalara dikkat etmeliyiz; -> "Signal Driven IO" modülü "Edge Triggered" biçimde çalışmaktadır. Yani takip ettiğimiz olaylara ilişkin sinyal oluşumu o boruda bilgi olduğu sürece değil, o boruya her bilgi geldiğinde yapılacaktır. Dolayısıyla borudan azar azar okumak yerine, yazılanların hepsini okumalıyız. Bir diğer deyişle sinyal oluştuğunda, bir döngü içerisinde okuma/yazma işlemi başarısız olana kadar, okuma yapmaya çalışmalıyız. Örneğin, boruya 100 bayt gelmiş olsun. Bu durumda sinyal oluşur. Eğer biz yüz bayttan daha az bayt okursak, boruya ya da sokete yeni bilgi gelene kadar yeni bir sinyal oluşmayacağından, boruda kalanları okuma şansımız kalmayacaktır. Bu nedenle o zaman kadar gelmiş bütün bilgileri bir döngü içerisinde okumalıyız. -> Sinyal oluştuğunda boruda okuma/yazma işlemlerinin nasıl yapılacağı da bir problemdir. Akla ilk gelen yöntem, ilgili "signal_handler" fonksiyonu içerisinde bu işlemin yapılmasıdır. Ancak genellikle iyi bir teknik değildir. Çünkü ilgili "signal_handler" içerisinde uzun sürecek işlemler yapmak tavsiye edilmez. "signal_handler" içerisinde aynı sinyalin tekrar oluşması durumunda, o sinyaller bloke oluşacaktır. Blokenin çözülmesiyle birlite, bloke edilenlerden sadece bir tanesini "handle" edebileceğiz. "SIGIO" sinyali de gerçek zamanlı bir sinyal olmadığından, kuyruklanmayacaktır. Diğer yandan "signal_handler" içerisinde yalnızca "signal safe" fonksiyonları çağırabiliriz. Fakat okuma/yazma işlemi ve sonrasında pek çok "signal safe" OLMAYAN FONKSİYONLARIN ÇAĞRILMASI gerekebilir. İşte "signal_handler" içerisinde bir "flag" değişkeni "set" edilir ve ilgili işlemler sinyal fonksiyonu dışında gerçekleştirilir. -> Birden fazla betimleyici üzerinde izleme yaparken, "SIGIO" yerine, artık Gerçek Zamanlı bir sinyal kullanmalıyız. Bu sinyaller hem kuyruklanmakta hem de ek bilgi yerleştirmek mümkündür. Anımsanacağı üzere Gerçek Zamanlı sinyalleri kullanırken, ilgili "signal_handler" fonksiyonunun "siginfo_t" türünden parametresine, "siginfo_t" türünden bir yapı nesnesinin adresini geçiyor ve "signal_handler" fonksiyon ise bu yapı nesnesinin içini dolduruyordu. Bu yapı Linux sistemlerince aşağıdaki gibi tanımlanmıştır: siginfo_t { int si_signo; // Oluşan sinyalin numarası. int si_errno; /* An errno value */ int si_code; // Sinyalin neden oluştuğu bilgisi. "bit-mask" DEĞİLDİR. Hangi değerleri alacağı hususunda; "https://man7.org/linux/man-pages/man2/sigaction.2.html". int si_trapno; /* Trap number that caused hardware-generated signal (unused on most architectures) */ pid_t si_pid; // Sinyali oluşturan prosesin ID değeri. uid_t si_uid; /* Real user ID of sending process */ int si_status; /* Exit value or signal */ clock_t si_utime; /* User time consumed */ clock_t si_stime; /* System time consumed */ union sigval si_value; /* Signal value */ int si_int; /* POSIX.1b signal */ void *si_ptr; /* POSIX.1b signal */ int si_overrun; /* Timer overrun count; POSIX.1b timers */ int si_timerid; /* Timer ID; POSIX.1b timers */ void *si_addr; /* Memory location which caused fault */ long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */ int si_fd; // Sinyale yol açan betimleyici. short si_addr_lsb; /* Least significant bit of address (since Linux 2.6.32) */ void *si_lower; /* Lower bound when address violation occurred (since Linux 3.19) */ void *si_upper; /* Upper bound when address violation occurred (since Linux 3.19) */ int si_pkey; /* Protection key on PTE that caused fault (since Linux 4.6) */ void *si_call_addr; /* Address of system call instruction (since Linux 3.5) */ int si_syscall; /* Number of attempted system call (since Linux 3.5) */ unsigned int si_arch; /* Architecture of attempted system call (since Linux 3.5) */ } -> Bu modülü senkron biçimde de kullanabiliriz. Bunun için ilk olarak beklemek istediğimiz sinyalleri "sigprocmask" üzerinden prosesimize karşı bloke etmeli, o sinyalin oluşmasından sonra "sigwaitinfo" fonksiyonu ile o sinyali işlemeli, en sonunda da yine "sigprocmask" üzerinden blokeyi kaldırmalıyız. Sinyalin işlenmesi için "sigwaitinfo" kullanıldığından, herhangi bir "signal_handler" fonksiyona ihtiyaç duyulmamıştır. -> Linux sistemlerinde performans sıralaması, "epoll" > "'Signal Based IO' module w/ synchronized signal" > "select" & "poll" biçiminde olacaktır. Şimdi de aşağıdaki örnekleri inceleyelim: * Örnek 1.0, Aşağıdaki örnekte ilgili "signal_handler" içerisindeki "printf" çağrısında "\n" kullanmadığımız için tampon boşaltılmayacağından, ekrana bir şey yazılmayacaktır. #include #include #include #include #include void signal_handler_function(int signo); void exit_sys(const char *msg); int main(void) { struct sigaction sa; sa.sa_handler = signal_handler_function; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGIO, &sa, NULL) == -1) exit_sys("sigaction"); if (fcntl(STDIN_FILENO, F_SETOWN, getpid()) == -1) exit_sys("fcntl"); if (fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK | O_ASYNC) == -1) exit_sys("fcntl"); for (;;) pause(); return 0; } void signal_handler_function(int signo) { printf("signal_handler_function"); /* UNSAFE */ } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 1.1, #include #include #include #include #include #include #define BUFFER_SIZE 1024 void signal_handler_function(int signo); void exit_sys(const char *msg); int main(void) { /* # INPUT # Ulya */ /* # OUTPUT # [29] - signal_handler_function */ struct sigaction sa; sa.sa_handler = signal_handler_function; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGIO, &sa, NULL) == -1) exit_sys("sigaction"); if (fcntl(STDIN_FILENO, F_SETOWN, getpid()) == -1) exit_sys("fcntl"); if (fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK | O_ASYNC) == -1) exit_sys("fcntl"); for (;;) pause(); return 0; } void signal_handler_function(int signo) { printf("[%d] - signal_handler_function\n", signo); /* UNSAFE */ } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2.0, Aşağıdaki yöntem de kötü bir tekniktir. #include #include #include #include #include #include #define BUFFER_SIZE 1024 void signal_handler_function(int signo); void exit_sys(const char *msg); int main(void) { /* # INPUT # Ulya */ /* # OUTPUT # [29] - signal_handler_function: Ulya */ struct sigaction sa; sa.sa_handler = signal_handler_function; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGIO, &sa, NULL) == -1) exit_sys("sigaction"); if (fcntl(STDIN_FILENO, F_SETOWN, getpid()) == -1) exit_sys("fcntl"); if (fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK | O_ASYNC) == -1) exit_sys("fcntl"); for (;;) pause(); return 0; } void signal_handler_function(int signo) { printf("[%d] - signal_handler_function: ", signo); /* UNSAFE */ char buf[BUFFER_SIZE + 1]; ssize_t result; for (;;) { result = read(STDIN_FILENO, buf, BUFFER_SIZE); if (result == -1) { if (errno == EAGAIN) break; exit_sys("read"); } buf[result] = '\0'; printf("%s\n", buf) ; /* UNSAFE */ } } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2.1, İşte daha iyi bir çözüm aşağıdaki gibidir. "g_sigio_flag" bayrağı ilgili sinyali takip etmek için kullanılmıştır. #include #include #include #include #include #include #define BUFFER_SIZE 1024 void signal_handler_function(int signo); void exit_sys(const char *msg); volatile sig_atomic_t g_sigio_flag; int main(void) { /* # INPUT # Ulya */ /* # OUTPUT # [29] - signal_handler_function: Ulya */ struct sigaction sa; sa.sa_handler = signal_handler_function; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGIO, &sa, NULL) == -1) exit_sys("sigaction"); if (fcntl(STDIN_FILENO, F_SETOWN, getpid()) == -1) exit_sys("fcntl"); if (fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK | O_ASYNC) == -1) exit_sys("fcntl"); char buf[BUFFER_SIZE + 1]; ssize_t result; for (;;) { pause(); if (g_sigio_flag) { for (;;) { result = read(STDIN_FILENO, buf, BUFFER_SIZE); if (result == -1) { if (errno == EAGAIN) break; exit_sys("read"); } buf[result] = '\0'; printf("%s\n", buf) ; } g_sigio_flag = 0; } } return 0; } void signal_handler_function(int signo) { printf("[%d] - signal_handler_function: ", signo); /* UNSAFE */ g_sigio_flag = 1; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, Gerçek Zamanlı sinyal kullanımına ilişkin bir örnektir. #define _GNU_SOURCE // For 'F_SETSIG' flag. #include #include #include #include #include #include #define BUFFER_SIZE 1024 void signal_handler_function(int signo, siginfo_t* info, void* context); void exit_sys(const char *msg); sig_atomic_t g_sigio_flag; int main(void) { /* # INPUT # Ulya Yuruk */ /* # OUTPUT # [34] - signal_handler_function: Ulya Yuruk */ struct sigaction sa; sa.sa_sigaction = signal_handler_function; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART | SA_SIGINFO; if (sigaction(SIGRTMIN, &sa, NULL) == -1) exit_sys("sigaction"); if (fcntl(STDIN_FILENO, F_SETOWN, getpid()) == -1) exit_sys("fcntl"); if (fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK | O_ASYNC) == -1) exit_sys("fcntl"); if (fcntl(STDIN_FILENO, F_SETSIG, SIGRTMIN) == -1) exit_sys("fcntl"); char buf[BUFFER_SIZE + 1]; ssize_t result; for (;;) { pause(); if (g_sigio_flag) { for (;;) { result = read(STDIN_FILENO, buf, BUFFER_SIZE); if (result == -1) { if (errno == EAGAIN) break; exit_sys("read"); } buf[result] = '\0'; printf("%s\n", buf) ; } g_sigio_flag = 0; } } return 0; } void signal_handler_function(int signo, siginfo_t* info, void* context) { printf("[%d] - signal_handler_function: ", signo); /* UNSAFE */ g_sigio_flag = 1; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 4, Yukarıdaki örneklere nazaran sinyalin beklenmesi senkron olarak gerçekleştirilebilir. Aşağıdaki örnekte programın çalıştırıldığı "shell" programına ilaveten üç tane daha çalıştırılmalı, sonrasında "mkfifo x y z" komutu ile ilgili isimlerde üç adet borunun oluşturulması ve son olarak ilave oluşturulan "shell" programlarında sırasıyla "cat > x", "cat > y" ve "cat > z" komutlarını çalıştırmalıyız. Artık bu komutları çalıştırdığımız "shell" programlarından gönderdiklerimizi, ilk baştaki "shell" programından okuyabiliriz. Herhangi bir "signal_handler" fonksiyon kullanılmamıştır. #define _GNU_SOURCE #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 #define MAX_SIZE 128 void exit_sys(const char *msg); int main(int argc, char *argv[]) { /* # Command Line Argumnts # ./main x y z */ /* # OUTPUT # opens named pipes... it may block... x opened... y opened... z opened... Ulya Yuruk Uskudar Istanbul Saglik Bilimleri Universitesi there is no descriptor open, finishes... */ if (argc == 1) { fprintf(stderr, "too few arguments!...\n"); exit(EXIT_FAILURE); } printf("opens named pipes... it may block...\n"); int fd; int count = 0; for (int i = 1; i < argc; ++i) { if (count >= MAX_SIZE) { fprintf(stderr, "too many arguments, last arguments ignored!...\n"); break; } if ((fd = open(argv[i], O_RDONLY)) == -1) exit_sys("open"); if (fcntl(fd, F_SETOWN, getpid()) == -1) exit_sys("fcntl"); if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)|O_NONBLOCK|O_ASYNC) == -1) exit_sys("fcntl"); if (fcntl(fd, F_SETSIG, SIGRTMIN) == -1) exit_sys("fcntl"); printf("%s opened...\n", argv[i]); ++count; } sigset_t sset; sigaddset(&sset, SIGRTMIN); // İlgili sinyali bloke etmeliyiz. if (sigprocmask(SIG_BLOCK, &sset, NULL) == -1) exit_sys("sigprocmask"); // Akış senkron olduğundan ve yukarıda bloke ettiğimizden, // ilgili sinyal oluştuğunda aşağıdaki kodlar çalıştırılacaktır. char buf[BUFFER_SIZE + 1]; ssize_t result; siginfo_t sinfo; for (;;) { if ((sigwaitinfo(&sset, &sinfo)) == -1) exit_sys("sigwaitinfo"); if (sinfo.si_code & POLL_IN) { // Borularda karşı tarafın kapatması durumunda da "POLL_IN" oluşur. for (;;) { result = read(sinfo.si_fd, buf, BUFFER_SIZE); if (result == -1) { if (errno == EAGAIN) break; exit_sys("read"); } if (result == 0) { --count; close(sinfo.si_fd); break; } buf[result] = '\0'; printf("%s", buf); } if (count == 0) break; } } // Akış buraya geldiyse, işimiz bitmiş demektir. // Böylelikle blokeyi kaldırmalıyız. if (sigprocmask(SIG_UNBLOCK, &sset, NULL) == -1) exit_sys("sigprocmask"); printf("there is no descriptor open, finishes...\n"); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } >> "Asynchronous IO" : Bu modelde "read"/"write" işlemleri başlatılır, ancak arka planda çekirdek tarafından asenkron bir biçimde sürdürülür.İşlem tamamlandığında ise programcı haberdar edilir. Yine bloke gerçekleşmez. Bu modül POSIX standartları tarafından destekenir. Bu modülde kullanılan fonksiyonlar "aio" ön eki alırlar. Kullanılacak başlık dosyasını ismi "aio.h" şeklindedir. Tipik olarak bu modülü kullanmak için şu aşama aşamaları sırayla takip etmemiz gerekmektedir: -> "aiocb" yapı türünden bir nesne tanımlanır ve içi biz programcılar tarafından doldurulur. POSIX standartlarınca bu yapı aşağıdaki gibi tanımlıdır. struct aiocb { int aio_fildes; // File descriptor. off_t aio_offset; // File offset. volatile void *aio_buf; // Location of buffer. size_t aio_nbytes; // Length of transfer. int aio_reqprio; // Request priority offset. struct sigevent aio_sigevent; // Signal number and value. int aio_lio_opcode; // Operation to be performed. }; Yapının "aio_fildes" isimli eleman okuma/yazma yapmak istediğimiz betimleyiciyi belirtir. Yapının "aio_offset" isimli elemanı ise okuma/yazma işleminin dosyanın neresinden itibaren yapılacağını belirtir. Asenkron okuma/yazma işlemleri, dosya göstericisinin gösterdiği konum itibariyle yapılmamaktadır (Eğer dosyamız "O_APPEND" modunda açılmışsa ve yazma işlemi yapacaksak, bu elemana yazacaklarımız dikkate alınmaz. Ayrıca "seekable" olmayan aygıtlar söz konusu olduğunda, bu elemana "0" değeri geçebiliriz). Yapının "aio_buf" isimli elemanı ise okuma/yazma işlemi sırasında kullanılacak tampon bölgeyi belirtir ve işlemler sırasında bu bölgenin hayatta olması gerekmektedir. Yapının "aio_nbytes" isimli elemanı ise aslında yapının "aio_buf" elemanı ile belirttiğimiz tampon bölgenin uzunluğunu belirtir. Yapının "aio_reqprio" isimli elemanı ise yapılacak okuma/yazma işlemi için bir ipucu belirtir. Tabii işletim sisteminin bunu kullanacağı kesin DEĞİLDİR. "0" değeri geçilebilir. Yapının "aio_sigevent" isimli elemanı ise işlemler bittiğinde yapılacak bildirime ilişkin bilgileri barındırır. Bu eleman "sigevent" yapı türünden olup, struct sigevent { int sigev_notify; // Notification type. int sigev_signo; // Signal number. union sigval sigev_value; // Signal value. void (*sigev_notify_function)(union sigval); // Notification function. pthread_attr_t *sigev_notify_attributes; // Notification attributes. }; biçimde tanımlanmıştır. Yine bu "sigevent" yapısının elemanlarını da doldurmamız gerekmektedir. "sigevent" yapısının "sigevent" isimli elemanı işlemler bittiğinde yapılacak bildirimi belirtir. Bu eleman, "SIGEV_NONE", "SIGEV_SIGNAL", "SIGEV_THREAD" değerlerinden birisini alır. Bu değerlerse sırasıyla bir bildirimin yapılmayacağını, bildirimin sinyal ile yapılacağını ve bildirimin "thread" ile ki bu "thread" "kernel" tarafından oluşturulur, yapılacağını belirtir. "sigevent" yapısının "sigev_signo" isimli elemanı, eğer bildirimde sinyaller kullanılacaksa, kullanılacak sinyalin numarasını belirtir. "sigevent" yapısının "sigev_value" isimli elemanı ise ilgili "signal_handler" ya da "thread" e verdiğimiz fonksiyona gönderilecek bilgiyi temsil eder. Bu eleman "union sigval" türünden olup, union sigval { int sival_int; void* sival_ptr; }; biçiminde tanımlanmıştır. "sigevent" yapısının "sigev_notify_function" isimli elemanı, eğer "thread" üzerinden bildirim yapılacaksa, "thread" tarafından kullanılacak fonksiyonun ne olduğunu belirtir. "sigevent" yapısının "sigev_notify_attributes" isimli elemanı ise kullanılacak "thread" in özelliklerini belirtir, "NULL" değerini geçebiliriz. "aiocb" yapısının "aio_lio_opcode" isimli elemanına daha sonra değineceğiz. -> Şimdi okuma/yazma işlemlerini "aio_read"/"aio_write" fonksiyonları ile başlatmamız gerekmektedir. Artık bu çağrılar sonrasında kendi akışımız da akmaya devam edecektir. İşlemler bittiğinde bize bildirim yapılacaktır. Fonksiyonların prototipi aşağıdaki gibidir. #include int aio_read(struct aiocb *aiocbp); int aio_write(struct aiocb *aiocbp); Fonksiyonlara argüman olarak yukarıda tanımladığımız "aiocb" türünden nesnenin adresini geçeriz. Unutmamalıyız ki adresini geçtiğimiz nesnemiz, çalışma boyunca hayatta kalmalıdır. Fonksiyonlar başarı durumunda "0", hata durumunda "-1" ile geri dönerler ve "errno" değişkenini uygun değere çekerler. Bu fonksiyonlar sadece bir defalık mekanizmayı çalıştırmaktadır. Bu fonksiyonlar "signal-safe" DEĞİLDİR. -> Anımsanacağı üzere "aiocb" yapısının "aio_nbytes" isimli elemanında kullanılan tampon bölgenin büyüklüğünü belirtmiştik. Ancak bu büyüklüğün tamamının kullanılmadığı senaryolar da olabilir. Pekiyi ne kadarlık bir büyüklüğün kullanıldığını nasıl tespit ederiz? İşte bunun için "aio_return" fonksiyonunu kullanırız. Fonksiyonun prototipi aşağıdaki gibidir. #include ssize_t aio_return(struct aiocb *aiocbp); Fonksiyon argüman olarak yine "aio_read"/"aio_write" fonksiyonlarına geçtiğimiz adres değerini alır. Başarı durumunda kullanılan büyüklük bilgisine, hata durumunda "-1" değerine geri döner ve "errno" değişkenini uygun değere çeker. Eğer işlemler tamamlanmadan bu fonksiyonu çağırırsak, "Tanımsız Davranış" oluşur. Tabii işlemler sonucunda ne kadarlık büyüklük bilgisinin kullanıldığını öğrenmek için, fonksiyona argüman olarak geçtiğimiz "aiocb" türünden nesnenin "sigev_value" isimli elemanının "sival_ptr" isimli elemaını, ilk başta "aiocb" türünden nesnenin içini doldururken kullanmalıyız. Bu fonksiyon "signal-safe" dir. -> En sonunda da mekanizmayı tekrar çalıştırmak için "aio_read"/"aio_write" çağrılarını tekrarlamalıyız. -> Mekanizma işlerken anlık bilgi almak için "aio_error" isimli fonksiyonu kullanırız. Fonksiyonun prototipi aşağıdaki gibidir. #include int aio_error(const struct aiocb *aiocbp); Fonksiyon argüman olarak durumunu merak ettiğimiz "aiocb" yapısının adresini alır. Fonksiyonun kendisi başarısız olursa "-1" ile geri döner ve "errno" değişkenini uygun değere çeker. Eğer "EINPROGRESS" koduna geri dönerse mekanizmanın henüz tamamlanmadığını, "ECANCELED" ile geri dönerse mekanizmanın iptal edildiği("Linux specific" ), "0" ile geri dönerse mekanizmanın başarılı biçimde tamamlandığını, diğer hata kodları ile geri dönerse de o koda ilişkin hata olduğunu belirtir. -> Başlatılan bir mekanizmayı iptal etmek istersek, "aio_cancel" fonksiyonunu kullanmalıyız. Fonksiyonun prototipi aşağıdaki gibidir. #include int aio_cancel(int fildes, struct aiocb *aiocbp); Fonksiyonun ikinci parametresi, iptal etmek istediğimiz mekanizma için kullanılan "aiocb" yapı nesnesinin adres değeridir. Fonksiyonun birinci parametresi ise dosya betimleyicisini belirtir. "NULL" değerinin geçilmesi durumunda, o betimleyiciyle ilişkili bütün "Asynchronous IO" mekanizması iptal edilir. Fonksiyonun kendisi başarısız olursa "-1" değerine geri döner ve "errno" değişkenini uygun değere çeker. Eğer "AIO_CANCELED" değerine geri dönerse iptal isteği başarılı, "AIO_NOTCANCELED" değerine geri dönerse iptal isteğinin başarısız olduğu, "AIO_ALLDONE" değerine geri dönerse mekanizmanın zaten tamamlandığı anlamına gelir. Şimdi de aşağıdaki örnekleri inceleyelim: * Örnek 1, Aşağıda "thread" kullanılmıştır. "0" nolu betimleyiciye yazılanlar işlendikten sonra ekrana basılmıştır. #include #include #include #include #define BUFFER_SIZE 4096 void io_proc(union sigval sval); void exit_sys(const char *msg); int main(void) { /* # INPUT # Ulya Yuruk Uskudar Istanbul Ctrl+d */ /* # OUTPUT # waiting at pause, press Ctrl+C to exit... IO occured: Ulya Yuruk IO occured: Uskudar Istanbul Ctrl+d pressed... */ struct aiocb cb; cb.aio_fildes = STDIN_FILENO; // "0" nolu betimleyici izlenecektir. cb.aio_offset = 0; char buf[BUFFER_SIZE + 1]; // Tampon bölge. cb.aio_buf = buf; cb.aio_nbytes = BUFFER_SIZE; cb.aio_reqprio = 0; cb.aio_sigevent.sigev_notify = SIGEV_THREAD; // "thread" kullanılacaktır. cb.aio_sigevent.sigev_value.sival_ptr = &cb; // Bilgi olarak "cb" nesnesinin kendisini kullanacağız. cb.aio_sigevent.sigev_notify_function = io_proc; // "thread" tarafından kullanılacak fonksiyon. cb.aio_sigevent.sigev_notify_attributes = NULL; // Artık arka planda "0" nolu betimleyici "read" amacıyla izlenecektir. // Yani asenkron bir biçimde. Eğer herhangi bir şey yazılırsa, bize // bildirilecektir. if (aio_read(&cb) == -1) exit_sys("aio_read"); // Fakat "main" akış buraya geçecektir. printf("waiting at pause, press Ctrl+C to exit...\n"); // Bu örnek nezdinde "main" akışı bekletiyoruz fakat // başka şeyler de yapabilirdik. pause(); return 0; } void io_proc(union sigval sval) { // Bize iliştirilen nesneye ulaşmış olduk. Eğer // nesnemiz "global" olsaydı, bu dönüşüme gerek // kalmayacaktır. struct aiocb *cb = (struct aiocb *)sval.sival_ptr; ssize_t result; if ((result = aio_return(cb)) == -1) exit_sys("aio_return"); // Arka plandaki mekanizma sonlandırılmıştır. if (result == 0) { printf("Ctrl+d pressed...\n"); // Bu örnek nezdinde betimleyiciyi kapatmak istemeyebiliriz. // close(cb->aio_fildes); return; } // Bize iliştirilen nesnenin tampon bölgesinde // ulaşmış olduk. Artık onu işleyebiliriz. // Eğer tampon bölgesi "global" olsaydı, bu şekilde // bir dönüşüme gerek kalmayacaktır. char *buf = (char *)cb->aio_buf; buf[result] = '\0'; printf("IO occured: %s", buf); // Mekanizmayı tekrardan kuruyoruz. Aksi halde // bir defalık çalıştırılmış olacaktı. if (aio_read(cb) == -1) exit_sys("aio_read"); } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2.0, Aşağıda ise "malloc" kullanılarak tahsilat yapılmıştır. Artık boru mekanizması kullanılmıştır. Dolayısıyla ilk başta üç adet "x", "y" ve "z" isimli boruyu "mkfifo x y z" kabuk komutu ile oluşturmalıyız. Daha sonra üç adet daha "shell" programı çalıştırıp, her birinde sırasıyla, "cat > x", "cat > y" ve "cat > z" komutlarını çalıştırmalıyız. Bu aşamadan sonra yeni oluşturduğumuz üç terminal programından gönderdiklerimizi kendi programımızın ekranından görebiliriz. #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 #define MAX_SIZE 128 volatile atomic_int g_count; void io_proc(union sigval sval); void exit_sys(const char *msg); int main(int argc, char** argv) { /* # Command Line Arguments # x y z */ /* # OUTPUT # x opened... y opened... z opened... Waiting @pause: Press Ctrl+C to exit. :> */ if (argc == 1) { fprintf(stderr, "too few arguments!..\n"); exit(EXIT_FAILURE); } printf("Opening named pipes...It may cause block...\n"); int fd; struct aiocb* cb; for (int i = 1; i < argc; ++i) { if (g_count > MAX_SIZE) { fprintf(stderr, "too many arguments! last ones ignored!..\n"); break; } if ((fd = open(argv[i], O_RDONLY)) == -1) exit_sys("open"); printf("%s opened\n", argv[i]); if ((cb = (struct aiocb*)malloc(sizeof(struct aiocb))) == NULL) { fprintf(stderr, "cannot allocate memory!..\n"); exit(EXIT_FAILURE); } cb->aio_fildes = fd; cb->aio_offset = 0; if ((cb->aio_buf = malloc(BUFFER_SIZE + 1)) == NULL) { fprintf(stderr, "cannot allocate memory!..\n"); exit(EXIT_FAILURE); } cb->aio_nbytes = BUFFER_SIZE; cb->aio_reqprio = 0; cb->aio_sigevent.sigev_notify = SIGEV_THREAD; cb->aio_sigevent.sigev_value.sival_ptr = cb; cb->aio_sigevent.sigev_notify_function = io_proc; cb->aio_sigevent.sigev_notify_attributes = NULL; if (aio_read(cb) == -1) exit_sys("aio_read); ++g_count; } printf("Waiting @pause: Press Ctrl+C to exit.\n"); pause(); return 0; } void io_proc(union sigval sval) { struct aiocb *cb = (struct aiocb *)sval.sival_ptr; ssize_t result; char *buf = (char *)cb->aio_buf; if ((result = aio_return(cb)) == -1) exit_sys("aio_return"); if (result == 0) { printf("pipe closed...\n"); close(cb->aio_fildes); free(buf); free(cb); --g_count; if (g_count == 0) { // Kendimize "SIGINT" sinyali gönderiyoruz. // Ana akış "pause" dan çıkacaktır. if (raise(SIGINT) != 0) exit_sys("raise"); } return; } buf[result] = '\0'; printf("IO occured: %s", buf); if (aio_read(cb) == -1) exit_sys("aio_read"); } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2.1, Yukarıdaki örnekte iki defa "malloc" ile dinamik bellek tahsisatı gerçekleştiriyorduk. İşte böyle yapmak yerine, "aiocb" yapısı ile bu yapının elemanı tarafından gösterilen alanı birleştirip, tek hamlede tek tahsilat yapabiliriz. #include #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 #define MAX_SIZE 128 typedef struct { struct aiocb cb; char buf[BUFFER_SIZE + 1]; } IOCB_INFO; void io_proc(union sigval sval); void exit_sys(const char *msg); volatile atomic_int g_count; int main(int argc, char *argv[]) { /* # Command Line Arguments # x y z */ if (argc == 1) { fprintf(stderr, "too few arguments!...\n"); exit(EXIT_FAILURE); } printf("opens named pipes... it may block...\n"); g_count = 0; IOCB_INFO *ioinfo; int fd; for (int i = 1; i < argc; ++i) { if (g_count >= MAX_SIZE) { fprintf(stderr, "too many arguments, last arguments ignored!...\n"); break; } if ((fd = open(argv[i], O_RDONLY)) == -1) exit_sys("open"); printf("%s opened...\n", argv[i]); if ((ioinfo = (IOCB_INFO *)malloc(sizeof(IOCB_INFO))) == NULL) { fprintf(stderr, "cannot allocate memory!...\n"); exit(EXIT_FAILURE); } ioinfo->cb.aio_fildes = fd; ioinfo->cb.aio_offset = 0; ioinfo->cb.aio_buf = ioinfo->buf; ioinfo->cb.aio_nbytes = BUFFER_SIZE; ioinfo->cb.aio_reqprio = 0; ioinfo->cb.aio_sigevent.sigev_notify = SIGEV_THREAD; ioinfo->cb.aio_sigevent.sigev_value.sival_ptr = ioinfo; ioinfo->cb.aio_sigevent.sigev_notify_function = io_proc; ioinfo->cb.aio_sigevent.sigev_notify_attributes = NULL; if (aio_read(&ioinfo->cb) == -1) exit_sys("aio_read"); ++g_count; } printf("waiting at pause, press Ctrl+C to exit...\n"); pause(); free(ioinfo); return 0; } void io_proc(union sigval sval) { ssize_t result; IOCB_INFO *ioinfo = (IOCB_INFO *)sval.sival_ptr; if ((result = aio_return(&ioinfo->cb)) == -1) exit_sys("aio_return"); if (result == 0) { printf("pipe closed...\n"); close(ioinfo->cb.aio_fildes); free(ioinfo); --g_count; if (g_count == 0 && raise(SIGINT) != 0) return; } ioinfo->buf[result] = '\0'; printf("%s", ioinfo->buf); if (aio_read(&ioinfo->cb) == -1) exit_sys("aio_read"); } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } Şimdiye kadar görmüş olduğumuz IO modellerinin kullanımları ve performansları hakkında şunları söyleyebiliriz: -> "'Multiplexed IO' using 'select' and 'poll'" ve "Asynchronous IO" modelleri POSIX standartlarında bulunan taşınabilir modellerdir. -> "'Multiplexed IO' using 'epoll'" ve "Signal Driven IO" modeli ise Linux sistemlerine özgüdür. Dolayısıyla taşınabilir değildir. -> Linux sistemlerinde performansı en yüksek model "'Multiplexed IO' using 'epoll'" modeli olup, perfomans sıralaması iyiden kötüye doğru "'Multiplexed IO' using 'epoll'" > "Signal Driven IO" == "Asynchronous IO" > "'Multiplexed IO' using 'select' and 'poll'" şeklindedir. >> "Scatter-Gather IO" : Bu modül, diğer modüllere nazaran daha az karmaşıktır. Peşisıra birden fazla "read" işlemi yapmak veya birden fazla kaynaktan peşpeşe "write" işlemi yapmak isteyelim. Dolayısıyla ilgili işler için peşpeşe "read"/"write" fonksiyonlarını çağırmamız gerekir. İşte böyle yapmak yerine bilgilerimizi önce bir "buffer" alanında biriktirip, bu "buffer" alanını kullanarak "read"/"write" işlemi yaparsak, daha az "kernel mode" a "switch" işlemi yapılacağından, zamandan tasarruf etmiş oluruz. İşte bu işlevi gören "readv" ve "writev" POSIX fonksiyonları geliştirilmiştir. Fonksiyonların prototipleri aşağıdaki gibidir. #include ssize_t readv(int fildes, const struct iovec *iov, int iovcnt); ssize_t writev(int fildes, const struct iovec *iov, int iovcnt); Fonksiyonların birinci parametresi "read"/"write" işleminin yapılacağı dosya betimleyicisini, ikinci parametresi elemanları "iovec" yapı türünden olan dizinin başlangıç adresini ve üçüncü parametre ise iş bu dizinin uzunluğunu belirtir. Fonksiyonlar başarı durumunda "read"/"write" yapılan toplam "byte" miktarına, hata durumunda ise "-1" değerine geri döner ve "errno" değişkeni uygun değere çekilir. Bu fonksiyonlar ile yapılan okuma/yazma işlemleri de atomik bir biçimde gerçekleştirilmektedir. "iovec" yapısı aşağıdaki gibi tanımlanmıştır. struct iovec { void *iov_base; // Base address of a memory region for input or output. size_t iov_len; // The size of the memory pointed to by iov_base. }; Yapının "iov_base" isimli elemanı tampon bölge olarak kullanılacak alanın başlangıç adresini, "iov_len" ise tampon bölgenin uzunluğunu belirtir. Programcı bu fonksiyonları kullanırken, fonksiyonların ikinci elemanına geçmek için bir yapı dizisi oluşturur ve içini doldururuz. Şimdi de aşağıdaki örnekleri inceleyelim: * Örnek 1.0, Aşağıda "writev" kullanımına ilişkin örnek verilmiştir. #include #include #include #include #include #include #include #define BUFFER_SIZE 10 void exit_sys(const char *msg); int main(void) { /* # test.txt # aaaaaaaaaabbbbbbbbbbcccccccccc */ int fd; if ((fd = open("test.txt", O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1) exit_sys("open"); char *buf1[BUFFER_SIZE]; memset(buf1, 'a', BUFFER_SIZE * sizeof('a')); char *buf2[BUFFER_SIZE]; memset(buf2, 'b', BUFFER_SIZE * sizeof('a')); char *buf3[BUFFER_SIZE]; memset(buf3, 'c', BUFFER_SIZE * sizeof('a')); struct iovec vec[3]; vec[0].iov_base = buf1; vec[0].iov_len = BUFFER_SIZE; vec[1].iov_base = buf2; vec[1].iov_len = BUFFER_SIZE; vec[2].iov_base = buf3; vec[2].iov_len = BUFFER_SIZE; if (writev(fd, vec, 3) == -1) exit_sys("writev"); close(fd); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 1.1, Aşağıda "readv" kullanımına ilişkin örnek verilmiştir. #include #include #include #include #include #include #include #define BUFFER_SIZE 10 void exit_sys(const char *msg); int main(void) { /* # test.txt # aaaaaaaaaabbbbbbbbbbcccccccccc */ /* # OUTPUT # aaaaaaaaaabbbbbbbbbbcccccccccc */ int fd; if ((fd = open("test.txt", O_RDONLY)) == -1) exit_sys("open"); struct iovec vec[3]; char *buf1[BUFFER_SIZE]; vec[0].iov_base = buf1; vec[0].iov_len = BUFFER_SIZE; char *buf2[BUFFER_SIZE]; vec[1].iov_base = buf2; vec[1].iov_len = BUFFER_SIZE; char *buf3[BUFFER_SIZE]; vec[2].iov_base = buf3; vec[2].iov_len = BUFFER_SIZE; if (readv(fd, vec, 3) == -1) exit_sys("writev"); write(1, buf1, BUFFER_SIZE); write(1, buf2, BUFFER_SIZE); write(1, buf3, BUFFER_SIZE); close(fd); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); }