Normal olarak dosya açılırken O_NONBLOCK özelliği kullanılmamışsa dosya blokeli
modda açılır. Blokeli I/O işlemlerinde read ve write fonksiyonları belirtilen miktarda byte’ın
tamamı yazılana kadar ya da okunana kadar process çizelge dışına çıkarılarak bloke edilir.
Eğer dosya disk üzerinde açılmışsa modern sistemlerde bu işlemler için geniş cache alanları
kullanıldığı için genellikle bir bloke durumu oluşmaz. Ancak donanım aygıtlarından okumalar
sırasında, pipe’lara okuma yazma sırasında, mesaj kuyruklarına okuma yazma sırasında bloke
durumunun oluşmasına sıklıkla rastlanır. Blokeli çalışma bazı durumlarda problemli
olabilmektedir.
1) Yavaş aygıtlardan okuma yazma yapıldığı zaman aynı zamanda arka planda başka
işlemler de yapılacaksa bloke durumu istenmez.
2) Birden fazla yavaş aygıttan okuma yapılmak istensin. Bu durumda blokeli çalışma
problemli olur, çünkü bir betimleyicide process bloke edilebilir, ancak diğerlerine bilgi gelmiş
olabilir. Halbuki istenen şey hangi betimleyiciye bilgi gelirse onu işleme sokmaktır.
Blokesiz moda geçebilmek için open fonksiyonunda O_NONBLOCK kullanılır. SystemV IPC
mekanizmasında msgrcv ve msgsnd fonksiyonlarında IPC_NOWAIT parametresi
kullanılırsa mesaj kuyruğu blokesiz moda geçirilebilir. Benzer biçimde soketler de blokesiz
çalışabilmektedir.
Blokesiz modda read fonksiyonu hiç bekleme yapmadan o anda okuyabildiği kadar bilgiyi
okur. Örneğin biz 100 byte okumak isteyelim; read fonksiyonu hazırda 10 byte varsa bu 10
byte’ı okur ve geri döner. Eğer read fonksiyonu o anda hiçbir okuyacak bilgiye sahip değilse
başarısızlık anlamında –1 değerine geri döner ve errno EAGAIN olarak set edilir. read
fonksiyonu 0 değerine geri döndüyse bu durum EOF anlamına gelir.
Blokesiz olarak SIZE uzunlukta bir bilgi bir döngü içerisinde okunarak bir diziye
yerleştirilecek olsun. Bu işlem şöyle yapılabilir:
char buf[SIZE];
int nleft, nindex, nread;
nleft = SIZE;
nindex = 0;
while (nleft >= 0) {
nread = read(fd, buf + nindex, nleft);
if (nread == -1) {
if (errno == EAGAIN)
continue;
else {
perror(“read”);
exit(1);
}
}
nleft -= nread;
nindex += nread;
}
Birden fazla betimleyiciden okuma yapılması gerektiğinde yukarıdaki algoritma nasıl
düzenlenmelidir?
typedef struct _MSG {
int fd;
int nindex;
int nleft;
char buf[SIZE];
} MSG;
MSG msg[10];
Initiaize(....); /* tüm dizideki elemanlar initialize edilir... */
for (;;) {
for (i = 0; i < 10; ++i) {
nread = read(msg[i].fd, msg[i].buf + msg[i].nindex,
msg[i].nleft);
if (nread == -1) {
if (errno == EAGAIN)
continue;
else {
perror(“read”);
exit(1);
}
}
msg[i].nleft -= nread;
msg[i].nindex += nread;
if (msg[i].nleft == 0) {
Proc(&msg[i]);
Initialize(....); // ilgili eleman initialize edilir...
}
}
}
Yukarıdaki algoritmalar blokesiz okumada kullanılan klasik algoritmalardır. Ancak blokesiz
okumada küçük bir problem daha vardır. Yukarıdaki gibi bir döngüde eğer aygıttan bilgi çok
yavaş geliyorsa gereksiz yinelemeler oluşacaktır. Bu da gereksiz bir CPU zamanı
harcanmasına neden olur. Bu durumda en iyi seçenek process’in herhangi bir betimleyiciye
bilgi gelene kadar bloke edilmesini sağlamaktır. İşte select fonksiyonu böyle bir işlem
yapmaktadır. Select fonksiyonuna bir grup betimleyici verilir. select fonksiyonu bunlardan
herhangi birinde okuma ya da yazma oluşana kadar process’i bloke eder. select fonksiyonu
yalnızca bu olayların gerçekleştiğini belirtmektedir. Ancak kaç byte’lık bir okuma ya da
yazma olduğunu vermemektedir.
select Fonksiyonu
select fonksiyonu bir POSIX fonksiyonu olmamakla birlikte POSIX’in son eklemelerine
yerleştirileceği düşünülmektedir. select fonksiyonunun benzeri BSD sistemlerinde poll
fonksiyonu biçiminde kullanılmaktadır. Bu iki fonksiyon da modern Unix sistemlerinin
hemen hepsi tarafından desteklenmektedir. Ancak select fonksiyonu çok daha yaygın
kullanıma sahiptir.
int select(int maxfd, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *tv);
fd_set türü bitsel bir yapıya sahip olan bir türü temsil eder. Bu tür genellikle bir yapı
biçimindedir. Fonksiyonun fd_set türünden gösterici parametrelerine NULL geçilebilir. Bu
durum bu parametrelerle ilgilenilmediği anlamına gelir. fd_set bir betimleyici kümesi belirtir.
fd_set sistemden sisteme değişebilecek bir türü belirttiğine göre bu kümeye betimleyici
eklemek ya da bu kümeden betimleyici çıkartmak için özel makrolar kullanılmalıdır. fd_set
genellikle long türden bir dizi elemanına sahip bir yapı biçimindedir. İlgili betimleyici
kümeye dahilse ilgili bit 1, değilse 0 biçimindedir. fd_set için kullanılan makrolar şunlardır:
FD_ZERO(fd_set *fdset);
FD_SET(int fd, fd_set *fdset);
FD_CLR(int fd, fd_set *fdset);
FD_ISSET(int fd, fd_set *fdset);
FD_ZERO tüm kümeyi sıfırlar. FD_SET belirli bir betimleyiciyi kümeye ekler. FD_CLR
belirli bir betimleyiciyi kümeden çıkartır. FD_ISSET ise belirli bir betimleyicinin kümeye
dahil olup olmadığına bakar. select fonksiyonu okuma, yazma ve istisna durumları kontrol
eder. Genellikle programcılar tek bir kümeyi belirleyip diğerlerini NULL olarak geçerler.
Fonksiyon verilen kümeler içerisindeki herhangi bir betimleyici içerisinde okuma ya da
yazma olayı gerçekleştiğinde sonlanır. select fonksiyonunun geri dönüş değeri üç biçimde
olabilir.
1) Geri dönüş değeri –1 ise bu durum error anlamına gelir. Örneğin bir signal oluştuğunda
fonksiyon –1 değeriyle geri döner.
2) Geri dönüş değeri 0 ise herhangi bir betimleyicide istenilen olay gerçekleşmemiştir.
Fonksiyon timeout nedeniyle çıkmıştır.
3) Fonksiyonun geri dönüş değeri 0’dan büyükse bu değer tüm kümelerdeki gerçekleşen
toplam olay sayısını belirtir.
select fonksiyonunun birinci parametresi işletim sisteminin işini kolaylaştırmak için
düşünülmüştür. Bu parametre tüm kümelerdeki arama için yapılacak maksimum betimleyici
sayısını belirtir. Örneğin birinci parametreyi 10 olarak girsek işletim sistemi fd_set
kümelerinin yalnızca ilk 10 slotu için araştırma yapar (yani 0-9 arası). Normal olarak bu
sayının toplam kümelere dahil edilmiş olan en büyük betimleyici numarasının bir fazlası
biçiminde verilmesi uygundur. Fonksiyonun son parametresi timeval türünden bir yapı
değişkeninin adresini alır.
struct timeval {
long tv_sec;
long tv_usec;
};
Programcı son parametreyi NULL geçerse zaman aşımı vermemiş olur. Yapının tv_sec ve
tv_usec elemanlarının her ikisi de 0 olarak girilirse select fonksiyonu hiç bekleme yapmadan
olayın gerçekleştiği betimleyicileri rapor eder. select fonksiyonu başarılı bir biçimde geri
döndüğünde fd_set kümeleri yalnızca olayın gerçekleştiği betimleyicileri içerecek hale
getirilmiş olur. Yani bu durumda fonksiyon çıkışında FD_ISSET uygulanarak ilgilenilen
betimleyicide olayın gerçekleşip gerçekleşmediği sorgulanabilir. Aşağıda select fonksiyonu
kullanılarak bir döngü içersinde etkin bekleme sağlayan kalıp verilmiştir.
int fd1, fd2, maxfd;
fd_set rdset;
for (;;) {
FD_ZERO(&rdset);
FD_SET(fd1, &rdset);
FD_SET(fd2, &rdset);
maxfd = MAX(fd1, fd2);
if (select(maxfd + 1, &rdset, NULL, NULL, NULL) == -1) {
perror(“select”);
exit(1);
}
if (FD_ISSET(fd1, &rdset)) {
...
}
if (FD_ISSET(fd2, &rdset)) {
...
}
}
Bu döngü sırasında bazı betimleyiciler zamanla kapanabilir. Kapanan betimleyicilerin
kümeden çıkartılması kolay değildir. Zaten kümeden çıkartılmalarına da gerek yoktur, nasıl
olsa kapanan betimleyicilere ilişkin olaylar hiç gerçekleşmeyecektir. FD_ZERO taşınabilirlik
açısından döngü içerisinde tutulmalıdır. Çünkü tüm betimleyicilerin sıfırlanması işleminde
fd_set içerisindeki başka elemanlar çeşitli değerlerle set ediliyor olabilir. select
fonksiyonundan çıkıldığında yalnızca olay gerçekleşen betimleyiciler fonksiyon tarafından
kümede bırakılır diğerleri küme dışına alınır.
/* select.c */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define PIPE_BUF 10
typedef struct _PIPEREC {
int nleft;
int index;
char buf[PIPE_BUF + 1];
} PIPEREC;
void process(int pipeno, char *str)
{
str[PIPE_BUF] = '\0';
printf("pipe %d: %s\n", pipeno, str);
}
int main(void)
{
int pipe1, pipe2, maxfd, n;
fd_set rdset;
PIPEREC prec1 = {PIPE_BUF, 0 }, prec2 = {PIPE_BUF, 0};
if ((pipe1 = open("pipe1", O_RDONLY|O_NONBLOCK)) == -1) {
perror("pipe1");
exit(1);
C ve Sistem Programcıları Derneği 159
}
if ((pipe2 = open("pipe2", O_RDONLY|O_NONBLOCK)) == -1) {
perror("pipe1");
exit(1);
}
maxfd = MAX(pipe1, pipe2);
for (;;) {
FD_ZERO(&rdset);
FD_SET(pipe1, &rdset);
FD_SET(pipe2, &rdset);
if (select(maxfd + 1, &rdset, NULL, NULL, NULL) == -1) {
perror("select");
exit(1);
}
if (FD_ISSET(pipe1, &rdset)) {
n = read(pipe1, prec1.buf + prec1.index, prec1.nleft);
if (n == -1) {
perror("read");
exit(1);
}
if (n == 0) {
prec1.nleft = PIPE_BUF;
prec1.index = 0;
}
prec1.nleft -= n;
prec1.index += n;
if (prec1.nleft == 0) {
process(1, prec1.buf);
prec1.index = 0;
prec1.nleft = PIPE_BUF;
}
}
if (FD_ISSET(pipe2, &rdset)) {
n = read(pipe2, prec2.buf + prec2.index, prec2.nleft);
if (n == -1) {
perror("read");
exit(1);
}
if (n == 0) {
prec2.nleft = PIPE_BUF;
prec2.index = 0;
}
prec2.nleft -= n;
prec2.index += n;
if (prec2.nleft == 0) {
process(2, prec2.buf);
prec2.index = 0;
prec1.nleft = PIPE_BUF;
}
}
}
close(pipe1);
close(pipe2);
}