İçindekiler
PIPE Nedir?
Linux ve diğer tüm UNIX türevi işletim sistemlerinin çok hoş bir özelliği de küçük komut zincirlerini bir araya getirerek büyük programlardan daha kabiliyetli küçük programlar yazmaktır. Bahsettiğim, süreçler arası haberleşme tekniği mekanizması olan pipe'tır. Pipe ilk olarak UNIX'in gelistirildigi Bell labaratuarlarinda gelistirilmistir. Pipe işlemlerini "|" simgesi ile gösteririz. Hemen hemen bütün kabuklar bunu destekler. Borulama kısaca bir programın standart çıkışını başka bir programa standart giriş olarak verme diye tanımlanabilir. Örneğin:
cclub:~> ls -l | grep cpp
Yukarıdaki komut dizinde, içerisinde cpp geçen dosyaların ismi ekrana basar. Şöyleki: "ls -l" dizindeki dosyaları çıktı olarak üretir. Bu çıktılar girdi olarak grep komutuna verilir. grep ise girdide arama yapan bir komuttur. Dolaysıyla dizindeki dosyalardan ismi cpp içerenlerin adını yazar. Burada dikkat ederseniz "ls -l" komutunun çıktısı ekrana basılmadı. Aynı şekilde grep'e üçüncü parametre olarak verilmesi gereken dosya isimleri verilmedi.
FIFO, pipe ile transfer edilecek veriler için bir yoldur. İlk giren, ilk çıkar mantığı ile çalışır. Gerçek bir boru düşünün. Boruya kırmızı bir top, sonra mavi bir top, sonra yeşil bir top sokalım. Borunun diğer tarafından sırasıyla kırmızı, mavi ve yeşil top çıkar. Bu bir veri yapısıdır. Bundan başka ters çalışan yığın denilen LIFO vardır. Burada son giren ilk çıkar. Bunu da şu şekilde hayal edin: Bir çuvala üst üste sırasıyla kırmızı, yeşil, mavi paketler koyalım ve kargoya verelim. Kargoyu teslim alan paketleri mavi, yeşil, kırmızı sırasıyla çıkarır.
Pipe ile çalışan programlar yazabilmek için bilmemiz gereken bazı şeyler var. Bunlardan ilki pipe fonksiyonudur. Tahmin edebileceğiniz fonksiyon aynı isme sahiptir ve unistd.h içerisinde prototipi şu şekilde tanımlanmıştır:
int pipe( int
filedes[2] ) ;
Bu fonksiyon iki adet dosya tanımlayıcısı oluşturur: okumak için filedes[0] ve yazmak için filedes[1]. Fonksiyon sorunsuz çalıştığında 0 değerini geri döndürür.
// pipe.c - Pipe ile süreç haberleştirme
// Versiyon: 011231
// Baris Simsek, <[email protected]>
// http://www.acikkod.org
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int mypipe[2];
char buffer[100];
/* Pipe oluştur. */
if (pipe(mypipe))
{
fprintf(stderr,
"Pipe oluştururken hata oluştu.
");
return 1;
}
/* Çocuk süreç oluştur. */
pid = fork();
if(pid == (pid_t) 0)
{
/* Çocuk süreç. */
read(mypipe[0], buffer, 100);
printf("%s
", buffer);
return 1;
}
else if (pid < (pid_t) 0)
{
fprintf(stderr,
"Fork yaparken hata oluştu.
");
return 1;
}
else
{
/* Ana süreç. */
write(mypipe[1],
"hello world!", 13);
return 1;
}
return 0;
}
|
|
Yukarıdaki örnekte fork() ile ikinci bir süreç oluşturduk. Ve sonra bu süreç ile
pipe'ın okunan kısmından (mypipe[0]) 100 byte'lık buffer okuduk. Ana süreç
içerisinden de pipe'ın yazılacak kısmına (mypipe[1]) "hello
world!" mesajını yazdık.
Fork yaptığınız zaman çocuk süreç ana sürecin bütün durumuna aynen sahip olur.
Açık dosya tanımlayıcılarını da kopyalar. Bu nedenle pipe oluşturduktan sonra fork ederseniz,
oluşturduğunuz pipe'ı iki process'in haberleşmesi için kullanabilirsiniz.
Yukarıdaki kod buna bir örnektir. Ekrana yazılan "hello
world!" mesajı, ana sürecin çıktısı olsa da ekrana basan çocuk
süreçtir. Ana sürec bu mesajı pipe'a yazmış, çocuk süreç te pipe'dan
okumuştur.
Eğer pipe'a yazılandan çok veri okumaya çalışırsanız, ancak yazılan kadar veri alabilirsiniz.
Eğer pipe'dan son okumanızdan sonra pipe'a hiçbir şey yazılmamış ise ve siz pipe'dan okumaya
çalışıyorsanız programınız bloke edilecektir. Ta ki yeni veri yazılana kadar veya
pipe'ın yazılan ucu kapatılana kadar. Bunu göz önünde tutarak programlarınızı
istenmeyen sonuclardan koruyunuz. UNIX işletim sistemlerinde dışardan veri
bekleyen süreçlerin "zamanlayıcı" (scheduler) tarafından bloke
edildiğini hatırlayın.
Linux pipe'ları yazılırken 4096 byte sınırına sahiptir. 4k'dan daha büyük veri pipe'a
yazacağı zaman süreç bloke olur ve pipe'dan okuma yapılmasını bekler.
FIFO Dosyalar
Eğer iki süreç bir pipe üzerinden haberleşmek istiyor ve aynı süreç altında
calışmıyorsa pipe() fonksiyonu işlerini görmeyecektir. Ancak üzülmeyin, bu durumda FIFO
dosyaları (named pipe) kullanabilirsiniz.
FIFO dosyası, dosya sisteminizdeki sıradan bir dosya gibidir, ancak tıpkı pipe gibi
özellikleri vardır. İki süreçten birisi FIFO dosyasını okumak, diğeri yazmak
için açabilir. Bu iki süreç FIFO aracılığı ile haberleşebilir. Birinin
yazdığını diğeri okur. Yukarıdaki gibi blok olma olayı burada da söz konusu.
Sözleri bir yana bırakıp gösteri yapmaya ne dersiniz?
cclub:~> mkfifo myfifo
cclub:~> ls -l myfifo
prw-r--r-- 1 root root 0 Dec 23 20:04 myfifo|
Yukarda yaptıklarıma bir göz atalım: "mkfifo" komutu ile myfifo adında özel
dosyamızı oluşturdum. "ls -l" komutu ile dosyamıza baktığınızda, dosya isminin sonuna "|" karakterinin
eklendiğini göreceksiniz. Bu karakter ile dosya sistemimiz onun bir FIFO
dosyası olduğunu işaretliyor. Biraz man sayfası okuyalım:
DESCRIPTION
mkfifo creates FIFOs (also called "named pipes") with the
specified filenames.
A "FIFO" is a special file type that permits independent
processes to communicate. One process opens the FIFO file
for writing, and another for reading, after which data can
flow as with the usual anonymous pipe in shells or else-
where.
By default, the mode of created FIFOs is 0666 (`a+rw')
minus the bits set in the umask.
Şimdi gösteriye devam edelim.
cclub:~> cat myfifo
Bu komut sonucunda kabuk program (shell) bekleyecektir. Çünkü sürecimiz
bloklanmıştır. myfifo'ya henüz bir seş yazılmadığı halde biz ondan okumak istiyoruz.
Şimdi başka bir terminal açın.
cclub:~> cat > myfifo
Bu komutun anlamı klavyeden alınacak girdileri "myfifo" dosyasına yaz. ">"
sembolu standart çıktıyı yönlendirmek için kullanılır. "cat" fonksiyonu normalde parametre olarak verilen
dosyayı görüntüler. Eğer parametre vermezseniz girdi olarak standart girişi (burada klavye)
kullanır. Şimdi en son açtığımız terminalde bir şeyler yazalım. Örneğin "hello world!"
yazın. Diğer terminale geçtiğinizde ne göreceksiniz, izleyelim:
2. terminal
cclub:~> cat > myfifo
hello world!
1. terminal
cclub:~> cat myfifo
hello world!
Harika değil mi! İkinci açtığımız terminalde klavyeden girdiklerimizi FIFO
dosyamıza yazdık. İlk açtığımız terminalde bloke olmuş sürecimiz pipe
üzerine veri yazıldığını gördü ve okuyup "cat" komutu ile ekrana bastı.
Gördüğünüz gibi iki süreç arasında haberleşmeyi sağladık.
Bu gösteriyi kabuk programı (shell) kullanarak yaptık. "cat" komutu sayesinde dosyadan
okumayı ve yine "cat" komutu ile kabuk programın bir özelliği olan yönlendirmeyi (>) kullanarak ta
yazmayı sağladık. FIFO dosyalarını kendi yazacağınız programlarda da kullanabilirsiniz. C
içinde FIFO dosyası oluşturmak için mkfifo() fonksiyonunu kullanabilirsiniz. Yine man
sayfalarımıza göz atalım:
NAME
mkfifo - make a FIFO special file (a named pipe)
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo ( const char *pathname, mode_t mode );
Prototipi yukarda gördüğünüz gibi. Kullanabilmek için sys/types.h ve sys/stat.h
başlık dosyalarını yüklemeniz gerekir. Yazacağınız iki süreçten birisi FIFO
dosyasından okumaya, diğeri de FIFO dosyasına yazmaya calışsın. Yukarıdaki iki
örneği bu şekilde simule edebilirsiniz.
// fifo-reader.c - FIFO'dan okuyan program
// Versiyon: 011231
// Baris Simsek, <[email protected]>
// http://www.acikkod.org
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int fd;
char buffer[100];
fd=fopen("myfifo","r");
fgets(buffer,100,fd);
printf("Buffer:
%s
",buffer);
return 0;
}
|
|
// fifo-writer.c - FIFO'ya yazan
program
// Versiyon: 011231
// Baris Simsek, <[email protected]>
// http://www.acikkod.org
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int fd;
char buffer[13];
strcpy(buffer,"hello
world!");
fd=fopen("myfifo","w");
fputs(buffer,fd);
return 0;
}
|
|
Pipe ve FIFO, temelde birbiri ile haberleşmek için dizayn edilmemiş iki süreci
haberleştirebilir. Bu teknik kulağınıza bazı hoş şeyleri fısıldıyor
olmalı. Soket haberleşme yazımı da okuyup biraz düşünün.
Barış Şimşek
UNIX Sys. Adm.