24 Nisan 2014 Perşembe

Ubuntu Server Üzerine Virtualbox/phpVirtualbox Kurulumu

Bu yazıda Ubuntu Server LTS üzerine Virtualbox sanallaştırma ortamı ve bu sanallaştırma ortamını web üzerinden yönetebilmek için geliştirilen phpVirtualbox sisteminin kurulumundan bahsedeceğiz.
Öncelikle yapılması gerelen bir ubuntu server kurulumu gerçekleştirmek. Kurulum işlemleri tamamlandıktan sonra gerekli paketlerin sisteme kurulumlarını gerçekleştirelim.

SSH sunucusunun kurulmasi uzaktan erisim icin onemli

apt-get install ssh openssh-server

Sonrasinda makinanin guncellenmesi gerekiyor.

apt-get update
apt-get upgrade
reboot

Sistem web uzerinden hizmet verecegi icin web sunucu ortaminin da kurulmasi gerekmektedir.

apt-get install apache2 apache2.2-common apache2-doc apache2-mpm-prefork apache2-utils 
libexpat1 ssl-cert libapache2-mod-php5 php5 php5-common php5-gd php5-mysql php5-imap 
phpmyadmin php5-cli php5-cgi libapache2-mod-fcgid apache2-suexec php-pear php-auth 
php5-curl php5-mcrypt mcrypt php5-imagick imagemagick libapache2-mod-suphp libruby 
libapache2-mod-ruby libapache2-mod-python libapache2-mod-perl2

Oracle Virtualbox Kurulumu

Virtualbox kurulumu islemine gecebiliriz. Bunun icin https://www.virtualbox.org/wiki/Linux_Downloads adresinden istedigimiz uygulama surumunu indirebiliriz. Yada paket kaynagina ekleme yapabiliriz. Ben burada paket kaynagini eklemeyi tercih ediyorum. Asagidaki kaynagi /etc/apt/sources.lst icerisine ekliyorum

deb http://download.virtualbox.org/virtualbox/debian precise contrib

Oracle'in apt icin kullanilan acik anahtarini sisteme eklemeliyiz.

wget -q http://download.virtualbox.org/virtualbox/debian/oracle_vbox.asc -O- | sudo apt-key add -

Sonrasinda Virtualbox uygulamasini rahatlikla indirip kurabiliriz. Ben phpvirtualbox ile uyumlu olmasi icin 4.2 versiyonunu kullanmayi tercih ediyorum.

sudo apt-get update
sudo apt-get install virtualbox-4.2

VMware host icin gerekli olan kernel modullerini de ayrica kurmamiz gerekmektedir.

sudo apt-get install dkms

Burada unutulmamasi gereken bir diger islem de virtualbox'u webservis uzerinden kontrol edebilmek icin bir eklenti kurmak. Bunun için kullandığım virtualbox versiyonu için extension pack aşağıdaki gibi indirilir ve kurulur.

wget http://download.virtualbox.org/virtualbox/4.2.24/Oracle_VM_VirtualBox_Extension_Pack-4.2.24-92790.vbox-extpack
VBoxManage extpack install Oracle_VM_VirtualBox_Extension_Pack-4.2.24-92790.vbox-extpack

phpVirtualbox kurulumu

Virtualbox kurulumunu tamamladiktan sonra, virtualbox sistemimizi uzaktan yonetmemizi saglayacak olan phpvirtualbox web uygulamasini kuralim.

Öncelile uygulamayi web sayfasindan indirelim.

wget http://downloads.sourceforge.net/project/phpvirtualbox/phpvirtualbox-4.2-8.zip

bundan sonra yapilmasi gereken sadece config.php dosyasi icerisinde virtualbox sistemini kullanacak olan kullanıcı bilgilerinin eklenmesi gerekmektedir.

var $username = 'vbox';
var $password = 'pass4vbox';

simdi web browser uzerinde sunucu baglantisini gerceklestirelim. Bu sistemin varsayilan kullanıcı ve parolası admin/admin şeklinde olacaktır.


Sisteme giriş yaptıktan sonra kullanmaya başlayabilirsiniz.




18 Nisan 2014 Cuma

Linux Sistem Programlama - Bölüm 8

Modern ve Yeni Paylaşılan Bellek Alanı Fonksiyonları

Daha önceden de belirtildiği gibi paylaşılan bellek alanlarıiçin iki grup fonksiyon kullanılabilmektedir Bunlardan biri klasik ve eski SystemV fonksiyonlarıdır. Diğeri yeni fonksiyonlardır. Bu yeni fonksiyonlara POSIX paylaşılan bellek alanı fonksiyonları da denilmektedir. Fakat her her iki fonksiyon grubu da posix standartlarınca tescil edilmiş fonksiyonlardır. POSIX Paylaşılan bellek alanı fonksiyonları şöyle kullanılmaktadır.



1. shm_open fonksiyonu ile sankipaylaşılan bellek alanı bir dosyaymış gibi yaratılır yada oluşturulur.

#include <sys/mman.h>
int shm_open(const char *name, int oflag, mode_t mode);

fonksiyonun birinci parametresi paylaşılan bellek alanının ismini belirtir. posix standartları bu ismin kök dizinde bir isim olarak verilmesini tavsiye etmiştir. Fakat bazı sistemlerde her herhangi bir dizin altında verilebilir.
Fonksiyonun ikinci parametresi open fonksiyonunda olduğu gibidir. Üçüncü parametre de yine open fonksiyonundaki gibidir. Fonksiyon başarı durumunda dosya betimleyicisine başarısızlık durumunda -1 değerine geri döner.

2. Paylaşılan bellek alanı yaratıldıktan sonra ftruncate fonksiyonu ile bunun için alan ayrılması gerekmektedir.

#include <unistd.h>
int ftruncate(int fd, off_t length);

ftruncate bir dosyayı büyütüp küçültmeye yarayan bir dosya fonksiyonudur.
Fonksiyonun birinci parametresi dosya betimleyicisini, ikinci parametresi dosyanın yeni uzunluğunu belirtir. Fonksiyon başarılı ise 0 değerine, başarısız ise -1 değerine geri döner.

3. Paylaşılan bellek alanı yaratıldıktan sonra ikinci aşamada mmap isimli fonksiyon ile bellek alanı tahsisatı yapılır (Bu fonksiyon shm_at fonksiyonuna karşılık gelmektedir)

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

Fonksiyon birinci parametresi yine önerilen adresi belirtir. Bu parametre NULL geçilebilir. İkinci parametre paylaşılan bellek alanının uzunluğunu belirtir (pekçok sistem bunu sayfa uzunluğuna göre yukarıya doğru yuvarlamaktadır fakat standart bir davranış değildir). Üçüncü parametre aşağıdakilerden oluşturulur.
  • PROT_READ
  • PROT_WRITE
  • PROT_EXEC
  • PROT_NONE
bu parametre tipik olarak "PROT_READ | PROT_WRITE" biçiminde kullanılmaktadır. Dördüncü parametre 0 geçilebilir yada MAP_SHARED, MAP_PRIVATE, MAP_FIX geçilebilir. Beşinci parametre dosya betimleyicisini belirtmektedir. Tipik olarak biz shm_open fonksiyonundan elde edilen betimleyiciyi buraya geçirebiliriz. Fonksiyonun son parametresi eğer bellek tabanlı dosya sözkonusu ise dosyanın hangi offsetinden itibaren işlemin yapılacağını belirtir. Paylaşılan bellek alanı için buraya 0 girebiliriz. Nihayet fonksiyon geri dönüş değeri başarı durumunda map edilen adres, başarısızlık durumunda NULL olarak geri döner.

4. Map edilen ve tahsis edilen adres alanı munmap fonksiyonu ile geri bırakılır. Bu SystemV'in shm_dh fonksiyonuna karşılık gelmektedir.

#include <sys/mman.h>
int munmap(void *addr, size_t length);

Fonksiyonun birinci parametresi unmap edilecek yerin başlangıç adresini, ikinci parametresi bunun uzunluğunu belirtmektedir. Fonksiyon başarı durumunda 0, başarısızlık durumunda -1 değeri ile geri döner.

5. Nihayet paylaşılan bellek alanına ilişkin dosya shm_unlink fonksiyonu ile silinir.

#include <sys/mman.h>
int shm_unlink(const char *name);

Fonksiyon parametre olarak shm_open fonksiyonunda verilen ismi alır. Başarı durumunda 0 başarısızlık durumunda -1 değerine geri döner.
POSIX IPC nesneleri sistemlere daha sonradan eklendiği için bunlar başka bir kütüphanenin içine yerleştirilmiştir. bu nedenle derleme işlemi yapılırken librt.a kütüphanesinin eklenmesi gerekiyor bunun için gcc'ye parametre olarak -lrt girilmesi gerekmektedir.
-l ile derleme yapılırken kütüphanenin baında yazan "lib" atılarak kullanılır





Kullanımı ise şu şekilde olacaktır.


1. Konsol
root@kali:~# gcc -o shmposixproc1 shmposixproc1.c -lrt
root@kali:~# gcc -o shmposixproc2 shmposixproc2.c -lrt
root@kali:~# ./shmposixproc1
Press ENTER to continue...

2. Konsol
root@kali:~# ./shmposixproc2
Press ENTER to continue...

This is a test
root@kali:~#

Bellek Tabanlı Dosyalar

Bellek tabanlı dosyalar eskiden beri (1992'den beri) 32 bit windpws sistemlerde bulunan bir özellikdi. Unix türevi sistemlere bellek tabanlı dosyalar daha sonra realtime extension eklentileri ile girmiştir. bazı unix türevi sistemler bunları çok sonra desteklemeye başlamıştır. Artık pekçok POSIX uyumlu sistem bunları desteklemektedir.

Bellek Tabanlı dosya ne anlama gelmektedir?

Bir dosyanın belleğe okunması ve artık onunla bellek üzerinde işlem yapılması anlamına gelmektedir. Yani read ve write işlemleri ile değil, gösterici işlemleri ile işlemler gerçekleştirilebilir. Dosyayı belleğe çektiğimizde artık biz bellekteki bilgileri değiştirdiğimizde bu değişiklik diske otomatik olarak yansıtılmaktadır. Bellek tabanlı dosyalar ile işlemler oldukça pratiktir.
UNIX Türevi sistemlerde bellek tabanlı dosyaların kullanınmı yine modern POSIX paylaşılan bellek alanları fonksiyonları ile yapılmaktadır.
Bellek tabanlı dosyalar tipik olarak şoyle kullanılmaktadır;
  • ilgili dosya normal bir biçimde open fonksiyonu ile açıklmaktadır.
  • mmap fonksiyonu ile dosyanın istenilen kısmı belleğe map edilir.
  • Sonra işlem bitince yine munmap fonksiyonu ile tahsis edilen bellek alanı boşaltılır.
  • en sonunda normal dosya kapatılır ve artık bütün değişiklikler dosyaya yansıtılmış olur.
Mmap fonksiyonunda bayrak parametresi olarak MAP_PRIVATE gecişirse dosya üzerinde update yapılamaz. MAP_SHARED yapılırsa yapılabilir. Normal olarak bellek tabanlı bir dosya üzerinde update yaptığımızda bunun ne zaman dosyaya yansıtılacağı belli değildir (fakat en kötü olasılıkla dosya kapatıldığında yada munmap uygulandığında). Biz istediğimiz bir anda yaptığımız değişikliklerin dosyaya yansımasını istiyorsak msync fonksiyonunu çağırmalıyız.

#include <sys/mman.h>
int msync(void *addr, size_t length, int flags);

Fonsiyonun birinci parametresi flush edilecek yerin başlangıç adresini belirtir (Bellekteki tüm dosyanın flush edilmesi zorunlu değildir). İkinci parametre alalnın uzunluğunu belirtir. üçüncü parametre MS_SYNC yada ms_ASYNC olarak geçilebilir. MS_SYNC Flush işlemi bitene kadar fonksiyonun geri dönmemesini sağlar. MS_ASYNC ise fonksiyonu hemen sonlandırır ve arka planda flush işlemine devam eder. Ayrıca bunlardan biri ile MS_INVALIDATE kombine edilebilri. MS_INVALIDATE bayrağı kullanıldığında eğer dosyayı birden fazla kişi map etmiş ise onların belleğinde de o anda değişim görünür.
Bir bellek tabanlı dosya uygulaması gerçekleştirelim;

Dosyamızı oluşturuyoruz.

root@kali:~# ls -l > test.txt
root@kali:~# cat test.txt
total 48
drwxr-xr-x 2 root root 4096 Mar  6 01:40 Desktop
drwxr-xr-x 4 root root 4096 Mar  6 07:41 peda
drwxr-xr-x 2 root root 4096 Mar  6 08:51 peda_work
-rw-r--r-- 1 root root 2315 Feb  7 16:19 rogue-wireless.sh
-rw-r--r-- 1 root root  136 Mar 10 10:36 salla.c
-rw-r--r-- 1 root root 1645 Mar 11 07:41 sample.c
-rw-r--r-- 1 root root 1004 Mar 12 06:18 shmposixproc1.c
-rw-r--r-- 1 root root  852 Mar 12 06:16 shmposixproc2.c
-rw-r--r-- 1 root root  767 Mar 12 04:19 shmproc1.c
-rw-r--r-- 1 root root  645 Mar 12 04:19 shmproc2.c
-rw-r--r-- 1 root root   16 Mar 10 10:36 stderr.txt
-rw-r--r-- 1 root root    0 Mar 12 08:12 test.txt
-rw-r----- 1 root root  362 Mar 10 09:21 text.txt
root@kali:~#

Sonrasında kodumuzu oluşturalım;

Uygulamayı çalıştıralım ve sonucu görelim.

root@kali:~# gcc sample.c -o sample
root@kali:~# ./sample
683
total 48
drwxr-xr-x 2 root root 4096 Mar  6 01:40 Desktop
drwxr-xr-x 4 root root 4096 Mar  6 07:41 p
root@kali:~# cat test.txt
total 48
drwxr-xr-x 2 root root 4096 Mar  6 01:40 Desktop
drwxr-xr-x 4 root root 4096 Mar  6 07:41 pxxxxxxwxr-xr-x 2 root root 4096 Mar  6 08:51 a.dat
-rw-r--r-- 1 root root 2315 Feb  7 16:19 rogue-wireless.sh
-rw-r--r-- 1 root root  136 Mar 10 10:36 salla.c
-rw-r--r-- 1 root root 1645 Mar 11 07:41 sample.c
-rw-r--r-- 1 root root 1004 Mar 12 06:18 shmposixproc1.c
-rw-r--r-- 1 root root  852 Mar 12 06:16 shmposixproc2.c
-rw-r--r-- 1 root root  767 Mar 12 04:19 shmproc1.c
-rw-r--r-- 1 root root  645 Mar 12 04:19 shmproc2.c
-rw-r--r-- 1 root root   16 Mar 10 10:36 stderr.txt
-rw-r--r-- 1 root root    0 Mar 12 08:12 test.txt
-rw-r----- 1 root root  362 Mar 10 09:21 text.txt
root@kali:~#

Mesaj Kuyrukları

mesaj kuyrukları da kendi içlerinde senkronizasyon içeren ve çok kullanılan prosesler arası haberleşme yöntemleridir. Bunların da uygulanması için eski SystemV ve modern posix versiyonları vardır. Mesaj kuyrukları adeta datagram soket haberleşmesine benzetilmektedir. Halbuki boru haberleşmeleri stream tarzı TCP haberleşmeye benzemektedir.

Klasik SystemV Mesaj Kuyruklarının Kullanımı

Kullanım tipik olarak şöyle gerçekleştirilmektedir.

1. msgget fonksiyonu ile mesaj kuyrukları yaratılır.

#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);

Fonsksiyonun birinci parametresi iki prosesin anlaşmış olduğu anahtar değerdir. ikinci parametre yanlızca O_CREAT yada "O_CREAT | O_EXCL" geçilebilir. Aynı zamanda bu parametre için erişim hakları da verilmelidir. Fonksiyon başarı durumunda 0 başarısızlık durumunda -1 değerine geri döner.

2. Artık sıra mesaş alıp vermeye gelmştir. Mesaj göndermek için msgsnd fonksiyonu kullanılır.

#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

Fonksiyonun birinci parametresi mesaj kuyruğunun handle değeridir. İkinci parametre göderilecek mesajın başlangıç adresini belirtir. Gönderilecek mesajın başında kesinlikle long türden bir alan olmak zorundadır. Bu alan mesajın türünü belirtir. Mesaj türü olarak 0 sayısı kullanılmaz. fonksiyonun üçüncü parametresigönderilecek mesajın byte uzunluğudur. bu uzunluğa mesajın başındaki long alan dahil değildir. Son parametre 0 geçilebilir yada IPC_NOWAIT  geçilebilir. 0 geçilir ise mesaj gönderilene kadar mesaj blokede bekler. IPC_NOWAIT geçilir ise o zaman mesaj kuyruğunun dolu olmasından dolayı mesajın gönderilememesi durumunda fonksiyon başarısız olur ve errno değeri EAGAIN ile set edilir. Fonksiyon başarı durumunda 0 başarısızlık durumunda -1 geri döner.
3. Mesajı almak için msgrcv fonksiyonu kullanılır.

#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

Fonksiyonun birinci parametresi mesaj kuyruğunun id değerini alır. İkinci parametre mesaj bilgilerinin yerleştirileceği bellek alanının adresini alır. Üçüncü parametre mesajın uzunluğunu belirtmektedir. Buradaki değer mesajın gerçek uzunluğundan büyük ise bu durumda sorun oluşmaz. Fonksiyon tüm mesajı okur. Okuduğu mesajın byte sayısı ile geri döner (long alan mesaj uzunluğuna dahil değil). Eger buradaki değeri mesajın uzunluğundan küçük girersek fonksiyon başarısız olur. Fakat fonksiyonun son parametresi olan flag parametresinde MSG_NOERROR değeri girilir ise fonksiyon başarısız olmaz. Mesaj kırpılarak alınır. Fonksiyonun dördüncü parametresi alınacak mesajın türünü belirtir. Boruya eğer 0 girilir ise türü ne olursa olsun sıradaki ilk mesaj alınır. Eğer bıraya sıfırdan büyük bir değer girilir ise fonksiyon yalnızca bu tür değerine sahip mesajı alır. Eğer buraya sıfırdan küçük bir değer girilir ise o zaman fonksiyon buraya girilen değerin mutlak değerinden küçük yada ona eşit olan ilk mesajı alır. Fonksiyonun son parametresin yine IPC_NOWAIT yada 0 yada IPC_NOERROR değerleri ile kombile edilebilir. IPC_NOWAIT blokesiz çalışma anlamına gelmektedir. Bu durumda fonksiyon kuyrukta mesaj yoksa beklemez. Fonksiyon başarı durumunda okunan mesajın uzunluğuna (tür alanı dahil değildir) başarısızlık durumunda -1 değerine geri döner.

4. İşlem bittikten sonra yine önce kuyruğa yazan tarafın işlemi sonlandırması uygun olur. Kuyruk msgctl fonksiyonu ile yok edilmiş ise önce msgrcv uygulayan program fonksiyondan başarısızlıkla çıkar

5. En sonunda mesaj kuyruğu yine msgctl fonksiyonu ile yok edilir. Fakat yaratım ve yok etme işlemi dışardan da yapılabilmektedir.
msgctl fonksiyonu şu şekildedir.

#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

Fonksiyonun birinci parametresi mesaj kuyruğunun handle değeridir. İkinci parametre silme işlemi için IPC_RMID  biçiminde girilmelidir. Üçüncü parametre ise NULL girilebilir. Fonksiyon başarı durumunda 0 değerine başarısızlık durumunda -1 değerine geri döner.
İnternette yapılan bazı bazı testlere dayanılarak IPC mekanizmaları arasında iyiden kötüye doğru hız performansı şöyle dizilmektedir.
1. Paylaşılan bellek alanları
2. Boru haberleşmesi
3. Mesaj kuyrukları
4. Unix domain soketler
Örnek olarak kendi aralarında haberleşen 2 C programı.




POSIX Mesaj Kuyrukları

Mesaj kuyruklarının yeni versiyonları da vardır.
Burada mesaj kuyrukları mq_open fonksiyonu ile açılır,

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>
mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);

daha sonra mq_send fonksiyonu ile mesaj gönderilir,

#include <mqueue.h>
int mq_send(mqd_t mqdes, const char *msg_ptr,size_t msg_len, unsigned msg_prio);

mq_receive ile mesaj alınır,

#include <mqueue.h>
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio);

Nihayet mq_unlink fonksiyonu ile de mesaj kuyruğu silinir.

#include <mqueue.h>
int mq_unlink(const char *name);

11 Nisan 2014 Cuma

Linux Sistem Programlama - Bölüm 7

İsimsiz Boruların Kullanılması

isimsiz boruların kullanıması sırasıyla şu işlemleri gerçekleştirir.

1.  Üst proses pipe fonksiyonu ile boruyu yaratır ve buradan iki betimleyici elde eder.


#include <unistd.h>
int pipe(int pipefd[2]);
Bu prototip gösteriminde köşeli parantez içindeki 2 değerinin C açısından hiçbir önemi yoktur. Ancak programcı buraya iki elemanlı bir dizinin adresini vermesi gerektiğini bildirmektedir. Aşağıdaki göterim fonksiyon parametresi olarak birbirine eşdeğerdir.

void Foo(int pi[2])
void Foo(int pi[])
void Foo(int *pi)
Fonsiyon bizden 2 elemanlı bir int dizisinin adresini ister. iki betimleyici oluşturarak bu dizinin içerisine yerleştirir. Fonksiyon başarı durumunda 0 başarısızlık durumunda -1 değerine geri döner. Dizinin sıfıncı indexli elemanında bulunan betimleyici borudan okuma yapma amacıyla 1 numaralı indexinde bulunan betimleyici boruya yazma yapmak için kullanılır.

2. Bu noktada senaryonun belirlenmesi gerekir. proseslerden biri okuma diğeri yazma yapacaktır. Fork işlemi ile alt proses yaratılır. Boru yazma yapacak taraf okuma betimleyicisini okuma yapacak taraf yazma betimleyicisini kapatmalıdır. Yani boruya okuma ve yazma yapacak tek bir betimleyicini bulunması gerekir.

3. Artık herşey hazırdır. Yazma taraf yazan wite fonksiyonunu, okuma yapan taraf da read fonksiyonunu kullanır.

4. Yazan taraf boru tamamen dolu ise okuayan taraf da boru tamamen boş ise bloke olur. Önce yazma yapan tarafın boruyu kapatması gerekir. bu durumda okyan taraf borudakileri okur. En sonunda boruda hiçbirşey kalmadığı zaman read fonksiyonu 0 ile geri döner. Artık okuyan taraf da boruyu kapatır.


/*------------------------------------------------------------------
    Üst ve alt prosesler arasında isimsiz boru haberleşmes
------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

void exitsys(const char *msg);
void do_parent(int fd);
void do_child(int fd);

int main(int argc, char *argv[])
{
    int fds[2];
    pid_t pid;
        
    if (pipe(fds) < 0)
        exitsys("pipe");
    
    if ((pid = fork()) < 0)
        exitsys("fork");
    
    if (pid != 0) {            /* parent yazma yapacak */
        close(fds[0]);        
        do_parent(fds[1]);
        close(fds[1]);
        if (wait(NULL) < 0)
            exitsys("wait");
        
    }
    else {                    /* child okuma yapacak */
        close(fds[1]);        
        do_child(fds[0]);
        close(fds[0]);
    }
        
    return 0;
}

void do_parent(int fd)
{
    int i;
    
    for (i = 0; i < 10; ++i)
        if (write(fd, &i, sizeof(int)) < 0)
            exitsys("write");
}

void do_child(int fd)
{
    ssize_t n;
    int val;
    
    while ((n = read(fd, &val, sizeof(int))) > 0)
        printf("%d ", val);
    printf("\n");
    
    if (n < 0)
        exitsys("read");
}

void exitsys(const char *msg)
{
    perror(msg);
    exit(EXIT_FAILURE);
}

Shell Üzerinde Boru işlemleri

Komut satırında a ve b birer program olmak üzere a | b işleminde şunlar olmaktadır;
Şhell programı bir boru yaratır. Sonra a ve b programlarını çalıştırır. a programının stdout dosyasını boruya, b programının da stdin dosyasını boruya yönlendirir. Böylece a programın kendisini ekrana yazdığını sanırken aslında boruya yazmaktadır. b programı da kendisini klavyeden okuduğunu sanırken aslında borudan okumaktadır. Böylece a nın ekrana yazdıklarını b klavyeden okuyormuş gibi alır.
CTRL+D stdin içerisinde EOF manasına gelmektedir.
Komut satırında kullanılan pekçok komutta eğer komut bir dosya argümanı almaz ise varsayılan olarak stdin dosyasından girişi bekler. Böylece biz bu tür komutları borular ile kullanabiliriz.

root@kali:~# ls -l | wc
     11      92     510
root@kali:~#

yada örneğin

root@kali:~# ps -e | grep tty
 2600 tty7     00:00:02 Xorg
 2957 tty1     00:00:00 getty
 2958 tty2     00:00:00 getty
 2959 tty3     00:00:00 getty
 2960 tty4     00:00:00 getty
 2961 tty5     00:00:00 getty
 2962 tty6     00:00:00 getty
root@kali:~# 

Komut satırında borulama birden fazla kez yapılabilir. Örneğin;

root@kali:~# ps -e | grep ssh | awk '{print $1}'
3280
3544
3562

Boru işlemi yapan shell kodlarını biz yazacak olsaydık bununasıl yapardık? Bunun birkaç yöntemi olabilir. Tipik yöntem şudur.
  1. Önce bir boru yaratılır ve iki betimleyici elde edilir.
  2. Her iki program için de fork yapılır.
  3. Bu noktada borudan okuma yazma potansiyeline sahip 3'er betimleyici olur. o halde üst prosesin iki boru betimleyicisini de kapatması gerekir. ve aynı zamanda yine yazacak taraf okuma betimleyicisini okyacak taraf da yazma betimleyicisini kapatır.
  4. Artık dup2 fonksiyonu ile her iki alt proses için de boru yönlendirmesi yapılır.
  5. Artık exec uygulanarak her iki program da çalıştırılır.

/*-------------------------------------------------------------------
    exec'li boru iþlemi (shell boru iþlemlerini nasil yapmaktadir?)
---------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

void exitsys(const char *msg);
void do_child1(const char *path, int fd);
void do_child2(const char *path, int fd);

/* usage cpipe <prog1> <prog2> */

int main(int argc, char *argv[])
{
    int fds[2];
    pid_t pidChild1, pidChild2;
    
    if (argc != 3) {
        fprintf(stderr, "wrong number of arguments!..\n");
        exit(EXIT_FAILURE);
    }
    
    if (pipe(fds) < 0)
        exitsys("pipe");
    
    if ((pidChild1 = fork()) < 0)
        exitsys("fork");
    
    if (pidChild1 != 0) {
        if ((pidChild2 = fork()) < 0)
            exitsys("fork");
        
        if (pidChild2 == 0) {        /* second child */
            close(fds[1]);
            do_child2(argv[2], fds[0]);
        
            /* unreachable code */
        }
    }
    else {        /* first child */
        close(fds[0]);
        do_child1(argv[1], fds[1]);
        
        /* unreachable code */
    }

    close(fds[0]);
    close(fds[1]);
    
    if (waitpid(pidChild1, NULL, 0) < 0)
        exitsys("waitpid");
    
    if (waitpid(pidChild2, NULL, 0) < 0)
        exitsys("waitpid");
    
    
    return 0;
}

void do_child1(const char *path, int fd)
{
    if (dup2(fd, STDOUT_FILENO) < 0)
        exitsys("dup2");
    close(fd);
        
    if (execlp(path, path, (char *) NULL) < 0)
        exitsys("execlp");
}

void do_child2(const char *path, int fd)
{
    if (dup2(fd, STDIN_FILENO) < 0)
        exitsys("dup2");
    close(fd);
    
    if (execlp(path, path, (char *) NULL) < 0)
        exitsys("execlp");
}

void exitsys(const char *msg)
{
    perror(msg);
    exit(EXIT_FAILURE);
}
root@kali:~# gcc sample.c -o sample
root@kali:~# ./sample ls wc
     10      10      91
root@kali:~#

İsimli Boru Haberleşmesi

İsimli boru işlemlerinde şu aşamalar gerçekleştirilir..
  • isimli boru, mkfifo isimli fonksiyonla yaratılır. Prototipi şu şekildedir. 
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
  • Haberlesecek iki proseste isimli boruyu sanki bir dosya gibi open fonksiyonu ile açar. Yine proselerden birinin yazma rolunde diğerinin de okuma rolunde olması gerekir. Yazma rolünde olan proses boruyu O_WRONLY modunda okuma yapmak isteyen proses ise O_RDONLY modunda açmalıdır.
  • Blokeli modda read ve write fonksiyonlarının yanısıra open fonksiyonunun kendisinde de bloke oluşur. Örneğin, proseslerden önce yazma yapan çaıştırılmış olsun. Bu durumda open fonksiyonubaşka bir proses boruyu okuma modunda açana kadar blokede bekler. Benzer biçimce önce okuyan prosesi çalıştırırsak bu kez de okuyan proses open fonksiyonunda başka bir proses boruyu yazma modunda açana kadar beklemektedir.
  • Okuma yazma işlemi yine read ve write fonksiyonları ile isimsiz boruda olduğu gibi yapılır.  Yine önce yazan taraf boruyu kapatır. Okuyan taraf boruyu okuduktan sonra o da boruyu kapatır.
  • Borunun yaratılması ve yok edilmesini proseslerden birisi yapabilir. Yada tamamen bu işlemler komut satırından yapılabilir. Haberleşme bittikten sonra da boru dosyası silinmez ise dosya sisteminde kalmaya devam eder.
1. Konsol
root@kali:~# mkfifo sallama
root@kali:~# ls -la sallama
prw-r--r-- 1 root root 0 Mar 11 07:47 sallama
root@kali:~# ls -la > sallama

2. Konsol
root@kali:~# cat < sallama
total 176
drwxr-xr-x 18 root root 4096 Mar 11 07:46 .
drwxr-xr-x 22 root root 4096 Jan  8 21:30 ..
drwx------  2 root root 4096 Mar  3 06:50 .aptitude
-rw-------  1 root root 6349 Mar 11 05:55 .bash_history
-rw-r--r--  1 root root 3391 Jan  1 20:28 .bashrc
drwx------  8 root root 4096 Mar 11 05:56 .cache
drwx------  9 root root 4096 Mar  6 01:40 .config
drwx------  3 root root 4096 Jan  8 21:41 .dbus
drwxr-xr-x  2 root root 4096 Mar  6 01:40 Desktop
drwx------  3 root root 4096 Mar 11 05:56 .gconf
-rw-------  1 root root 1322 Mar  6 10:07 .gdb_history

Read ve Write Fonksiyonlarının Borudaki Davranışları

Read ve write fonksiyonları boru sözkonusu olduğunda blokeli (blocking) ve blokesiz (nonblockking) olmak üzere iki modda çalışabilmektedir. Varsayılan mod blokeli moddur.
Blokesiz modda işlem yapabilmek için open fonksiyonuna O_NONBLOCK bayrağının eklenmesi gerekmektedir.
  • Read fonksiyonunun blokeli moddaki davranışı şu şekildedir:
    • Read fonksiyonu eğer boruda hibir byte yok ise blokede bekler fakat boruda en az 1 byte var ise talep edilen miktarda okumak için beklemez.
    • Olan okur ve olanı bayte sayısına geri döner. Fakat boruda hiçbir bilgi yoksa ve karşı taraf boruyu kapatmış ise 0 ile geri döner.
  • Write fonksiyonunun blokeli moddaki davranışı şöyledir:
    • Eğer boru tüm bilgiyi yazamayacak kadar dolu ise bu durumda write fonksiyonu tüm bilgiyi yazana kadar blokede bekler.
    • Eğer yazılacak miktar borunun toplam uzunluğundan büyük ise bu durumda write bloke olmaz.
    • Boru uzunluğu kadar bilgiyi yazar ve yazdığı byte sayısı ile geri döner. Başka bir deyişle boru uzunluğundan büyük m,ktarda boruya bilgi yazmak istersek bu tek hamlede yapılmaz.
    • Dolayısı ile başka bir proseste boruya yazma yapıyorsa araya girme olabilir.
    • Borunun uzunluğu ilgili sistemde PIPE_BUF sembolik sabiti ile belirtilmiş durumdadır.
    • Eğer write fonksiyonu ile borunun uzunluğundan daha fazla bilgi yazmak istersek write yine tüm bilgiler boruya aktarılana kadar bekler.
    • Ancak başka proseslerde boruya yazma yapıyor ise araya girme olabilir.
  • Read fonksiyonunun blokesiz moddaki davranışı şöyledir:
    • Bu modda read yine boruda en az 1 bye var ise okyabileceği kadarını okur ve okyabildiği byte sayısı ile geri döner.
    • Fakat boruda hiç bilgi yok ise -1 değeri ile geri döner ve errno EAGAIN deği ile set edilir.
  • Write Fonksiyonunun blokesiz moddaki davranışı şu şekildedir:
    • Bu modda write tüm bilgi yazılana kadar blokede beklemez.
    • Eğer tüm bilginin yazılacağı kadar boruda boş yer var ise tüm bigiyi yazar  ve yazdığı byte sayısı ile geri döner.
    • Eğer tüm bilgiyi yazacak kadar boruda yer yoksa -1 değeri ile geri döner ve boruya hiçbir bilgi yazmaz. Bu durumda errno EAGAIN değeri ile set edilir.
    • Eğer write ile biz boru büyüklüğünden daha fazla sayısa byte'ı boruya yazmaya çalışırsak bu durumda write boruya yazabildiği kadar değeri boruya yazar ve yazdığı byte sayısı ile geri döner.
    • Fakat boruda hiç yer yoksa -1 değeri ile geri döner ve errno EAGAIN değeri ile set edilir.

Örnek:

Öncelikle kendi pipe dosyamızı oluşturalım

root@kali:~# mkfifo sallama

Haberleşen iki uygulamanı kodları şu şekildedir.

/*--------------------------------------------------------
    isimli boru işlemleri
---------------------------------------------------------*/

/* writefifo.c */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>

void exitsys(const char *msg);

int main(void)
{
    int fd;
    char buf[1024];
    
    /*
    if (mkfifo("testfifo", S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0)
        exitsys("mkfifo");
    */
    
    if ((fd = open("testfifo", O_WRONLY)) < 0)
        exitsys("testfifo");
    
    for (;;) {
        printf("text:");
        fflush(stdout);
        gets(buf);
        if (write(fd, buf, strlen(buf)) < 0)
            exitsys("write");
        if (!strcmp(buf, "quit"))
            break;
    }
    
    close(fd);
    
    /*
    if (unlink("testfifo") < 0)
        exitsys("testfifo");
    */
    return 0;
}    

void exitsys(const char *msg)
{
    perror(msg);
    exit(EXIT_FAILURE);
}
/* readfifo.c */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>

void exitsys(const char *msg);

int main(void)
{
    int fd;
    char buf[1024];
    ssize_t n;
        
    if ((fd = open("testfifo", O_RDONLY)) < 0)
        exitsys("testfifo");
    
    while ((n = read(fd, buf, sizeof(buf))) > 0) {
        buf[n] = '\0';
        if (!strcmp(buf, "quit"))
            break;
        puts(buf);
    }
    if (n < 0)
        exitsys("read");

    close(fd);
    
    return 0;
}    

void exitsys(const char *msg)
{
    perror(msg);
    exit(EXIT_FAILURE);
}

Yukarıdaki örnekde tek yönlü boru haberleşmesi yapılmıştır eğer iki yönlü haberleşme pşabilmesi için iki ayrı pipe oluşturmak gerekecektir.

Mevcut linux sistemlerinde boru uzunluğu, yani PIPE_BUF değeri 4096'dır.

Borularla Client - Server Haberleşmesi

Client Server haberleşmesinde client ve server iki ayrı programdır. Asıl işi server yapar. Client yalnızca istekte bulunur. Server birden fazla client'e hizmet verebilmektedir. Böyle bir haberleşmenin faydaları şöyle özetlenebilir.
  • Server güçlü bir makinada çalışıyor olabilir ve biz bu serverin gücünden faydalanabiliriz.
  • Server bir aygıtı yada bir kaynağı kullanıyor olabilir ve onu paylaştırmak gerekebilir.
  • Server tün client'lar arasında koordinasyon sağlanabilir. (chat programları)
  • Server güvenli bir yerde ve güvenli bir biçinde kaynaklara erişiyor olabilir.
  • Boru kullanılarak client server haberleşmede serverden client'a bilgi aktarımı için her client için boru açılması gerekir. Fakat clientlardan server'a istek belirtmek için tek bir boru yetrlidir.
http://codingfreak.blogspot.com/2008/09/client-server-example-with-pipes.html
Yukarıdaki linkdeki örnek gayet uygun bir örnektir. 

Paylaşılan Bellek Alanları Yöntemi

Paylaşılan bellek alanları (shared memory), hem Unix/Linux hemde windows sistemlerinde benzer sistemde kullanılmaktadır.  Bu yöntemde proseslerin bellek alanları birbirinden ayrı olduğu halde ram'de iki prosesinde erişebileceği ortak bir bölge ayrılır. Proseslerden biri oraya yazma yapar, diğeri oradan okuma yapar. Ancak borulardaki gibi bir senkronizasyon mekanizmanın kendi içerisinde yoktur. Senkronizasyonu ayrıca uygulamak gerekmektedir. Paylaşılan bellek alanlarını oluşturmak için iki grup fonksiyon bulunur. Bir tanesi Klasik SystemIPC fonksiyonlarıdır. Bunlar pekçok unix tirevi sistemlerde vardır. İkincisi sonradan eklenmiş modern fonksiyonlardır.

Bu iki grup fonksiyon da POSIX standartlarınca kabul edilmiş fonksiyonlardır. Ayrıca paylaşılan bellek alanları bellek tabanlı dosya oluşturmak için de kullanılmaktadır.

Klasik System V Fonksiyonlarıyla Paylaşılan Bellek Alanları

Paylaşılan bellek alanları şöyle oluşturulur;

1. Öncelikle bir anahtar belirleyerek paylaşılan bellek alanı shmget fonksiyonu ile oluşturulur. Burada fonksiyonun birinci parametresi iki prosesin ortaklaşa belirleyeceği bir id değeri yada bir anahtar değerdir. Fonksiyonun ikinci parametresi oluşturulacak paylaşılan bellek alanının uzunluğunu belirtir. Buradaki değer PAGE_SIZE değerine yukarıya doğru yuvarlanmaktadır. Fonksiyonun son parametresi iki değerden bir yada ikisi olabilir.

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

IPC_CREAT: yok ise yarat anlamına gelmektedir.
IPC_EXCL : var iseoluşturmaz yok ise hata verir. IPC_CREAT ile kullanılır.  Nihayet bu parametreye aynı zamanda nesne erişim hakları içinS_IXXX biçimindeki sembolik sabitler de eklenebilir. Fonksiyon başarı durumunda paylaşılan bellek alanına ilişkin bir handle değerine başarısızlık durumunda -1 değerine geri döner.
Fonksiyonun birinci parametresine anahtar girmek yerine IPC_PRIVATE girilebilir. Bu durumda sistem anahtar değeri kendisi üretip nesneyi yaratır.

2. Şimdi yaratılan bellek alanı için ram'de bir alanın oluşturulmasına sıra gelmiştir. Bu işlem shmat fonksiyonu ile yapılır.

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);

Fonksiyonun birinci parametresi shmget fonksiyonundan elde edilen id değeridir. ikinci parametre önerilen bellek adresini belirtir. Yani biz kendi prosesimizin bellek alanı içerisinde kendi belirlediğimiz bir alanı bu amaçla kullanabiliriz. Fakat bu parametre NULL geçilebilir. Bu durumda sisitem istediği adresi belirleyebilir (Genellikle bu yöntem uygulanır). Fonksiyonun üçüncü parametresi paylaşılan alanın kullanımını belirleyen bayrakları içerir. Bu parametre SHM_RND girilebilir. Bu değer ikinci parametredeki adresin aşağıya doğru sayfa sınırlarına yuvarlanacağını belirtir. Fonksiyonun geri dönüş değeri başarı durumunda oluşturulan bellek alanının adresine, başarısızlık durumunda NULL değere gerei döner. Üçüncü parametre 0 geçilebilir.

3. Artık proseslerden biri paylaşılan bellek alanına yazma yapıp diğeri bunu okuyabilir. Yöntem çok hızlıdır. çünkü hiç aracılıksız erişim yapılmaktadır.

4. İşlem bittikten sonra paylaşılan bellek alanı kalmaya devam eder. Onu yok etmek için, yani shmat ile yapılanları geri almak için shmdt fonksiyonunu çağırmak gerekir.

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);

Fonksiyon parametre olarak shmat fonksiyonundan elde edilen bellek adresini alır. Başarı durumunda 0 başarısızlık durumunda -1 değerine geri dönmektedir.
Bir programın başka bir programa bilgi aktarabileceği en hızlı yöntem shared memory dir.
5. işlemler bitince shmctl fonksiyonu ile tüm paylaşılan bellek alanı işlemleri geri alınmalıdır. Shmctl aynı zamanda başka amaçlar için de kullanılabilmektedir. fonksiyon prototipi şu şekildedir.

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

fonksiyonun birinci parametresi paylaşılan bellek alanının handle değeri, ikinci parametresi eğer nesne silinecek ise IPC_RMID biçininde girilir. Üçüncü parametre ikinci parametrenin durumuna göre anlam kazanır. Eğer ikinci parametre IPC_RMID giriliyorsa bu durumda üçüncü parametre NULL girilebilir. paylaşışlan bellek alanının silinmesi ve yaratılması yine başka bir program yoluı ile yapılabilir.
SystemV in klasik IPC nesneleri reboot işlemi yapılana kadar kalıcı olmaktadır. Yani onları yaratan programlar sonlansa bile onlar kalmaya devam eder.

/*---------------------------------------------------
    Paylaşılan bellek alanı uygulaması    
---------------------------------------------------*/

/* shmproc1.c  - Yazma yapan uygulama*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SHMKEY        0x12345678

void exitsys(const char *msg);

int main(void)
{
    int shmid;
    char *buf;
    
    if ((shmid = shmget(SHMKEY, 4096, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|IPC_CREAT)) < 0)
        exitsys("shmget");
    
    if ((buf = (char *) shmat(shmid, NULL, 0)) < 0)
        exitsys("shmat");
    
    strcpy(buf, "This is a test");
    printf("press ENTER to key to continue...\n");
    getchar();
    
    shmdt(buf);
    
    if (shmctl(shmid, IPC_RMID, NULL) < 0)
        exitsys("shmctl");
        
    
    return 0;
}

void exitsys(const char *msg)
{
    perror(msg);
    exit(EXIT_FAILURE);
}

/* shmproc2.c - Okuma yapan uygulama */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SHMKEY        0x12345678

void exitsys(const char *msg);

int main(void)
{
    int shmid;
    char *buf;
    
    if ((shmid = shmget(SHMKEY, 4096, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|IPC_CREAT)) < 0)
        exitsys("shmget");
    
    if ((buf = (char *) shmat(shmid, NULL, 0)) < 0)
        exitsys("shmat");
    
    printf("Press ENTER to continue...\n");
    getchar();
    
    puts(buf);
    
    shmdt(buf);
        
    return 0;
}

void exitsys(const char *msg)
{
    perror(msg);
    exit(EXIT_FAILURE);
}
root@kali:~# ./shmproc1
press ENTER to key to continue...

yukarıdaki işlemde bellek alanına yazma yapar ve bekler. Aşağıdaki komut ile de bellek alanına yazılan bilgi ekrana basılmaktadır.

root@kali:~# ./shmproc2
Press ENTER to continue...

This is a test
root@kali:~#

O anda sistemde yaratılmış bütün IPC nesneleri, ipcs komutu ile görülebilmektedir. bir ipc nesnesi anahtar veya handle değeri ile ipcrm shell komutu ile silinebilir.

4 Nisan 2014 Cuma

Linux Sistem Programlama - Bölüm 6

Dosya Betimleyicilerinin Anlami

BIr dosya acildiginda kernel o dosya ile ilgili islem yapabilmek icin bir veru yapisi olusturur. Buna dosya nesnesi (File object) denilmektedir. Bu dosya nesnesinin adresi dosya betimleyici tablosu denilen bir tabloya yazilir.  Dosya betimleyici tablosu bir gosterici dizisidir. Dosya betimleyici tablosunun adresi de proses kontrol blogunda saklanmaktadir.


Iste dosya betimleyicisi dosya betimleyicisi tablosunda bir index belirtir. O halde dosya betimleyicisini kullanan bir fonksiyonu cagirdigimizda (read fonksiyonu gibi) sirasiyla sunlar olur.
  • Kernel o andaki aktif prosesin proses kontrol bloguna erisir.
  • Kernel oradan fonksiyonu cagiran prosesin dosya betimleyici tablosuna erisir.
  • Kernel buradan da dosya betimleyicisini index olarak kullanarak dosya nesnesine erisir.
  • dosya ile ilgili islem yapmak icin gerekli butun bilgiler dosya nesnesi icerisindedir.
Sistem bu islem ile veri okunanin  dosya mi yoksa bir device mi bunu bilemez. Bu duruma virtual file system denilmektedir.

Bir dosya nesnesi bir disk dosyasina iliskin olabilecegi gib bir aygit surucuye iliskin de olabilir. Aslinda okuma-yazma gibi temel islemleri yapan fonksiyonlarin adresleri dosya nesnesinin icindeki bir dizide tutulmaktadir. Iste eger dosya nesnesi bir aygit surucuye iliskin ise burada aygit surucunun read ve write fonksiyonlarina adresleri bulunur. Eger dosya nesnesi bir disk dosyasina iliskin ise burada diskten okuma yazma yapacak fonksiyonlarin adresleri bulunur. Bu durumda ornegin biz read fonksiyonunu cagirdigimizda read fonksiyonu da dosya nesnesi icerisinde bulunan adresteki fonksiyonu cagirir. Yani aslinda read fonksiyonu bile nereden okuma yaptigini bilmemektedir.

Peki close fonksiyonu ile bir dosya kapatildiginda ne olur?

Dosya nesnesinin sayaci 1 eksiltilir. Sayac sifira dusmus ise dosya nesnesi silinir ve dosya betimleyici tablosundaki giris de silinir. Boylece zamanla dosya betimleyici tablosunun bazi girisleri dolu bazi girisleri bos olur. POSIX Standartlerinda open fonksiyonunun ilk bos betimleyiciyi (yani sayisal degeri en dusuk olan ilk betimleyiciyi) verecegi garanti edilmistir.
Bir proses yaratildiginda dosya betimleyici tablosunnun ilk uc betimleyici dolu durumdadir. (Bu uc betimleyicinin neden dolu oldugu aciklanacaktir.) Sifir numarali betimleyici klavyayi temsil eden dosya nesnedini, 1 ve 2 numarali betimleyiciler ekrani temsil eden dosya nesnesini gosterir. Boylece biz 0 numarali betileyiciyi kullanarak okuma yaptigimizda klavyeden okuma yapmus oluruz. 1 ve 2 numarali betimleyicileri kullanarak yazdirma yaparsak ekrana yazdirmis oluruz.

I/O Yonlendirmesinin Temeli

Biz 0,1 yada 2 numarali betimleyiciyi close fonksiyonu ile kapatip hemen arkasindan open fonksiyonu uygularsak open fonksiyonu bize eldeki bos betimleyiciyi verecegi icin bu kapattigimiz betimleyiciyi verecektir. Ornegin close(1) isleminden sonra open fonksiyonu ile bir dosya acmis olalim. Open bize 1 numarali betimleyiciyi verecektir.  Bunun sonucu olarak 1 numarali betimleyicinin gosterdigi dosya nesnesi bizim dosyamiza iliskin olacaktir.
Bu durumda biz printf gibi ekrana yazan fonksiyonlari kullandigimizda bu fonksiyonlar hep nihayetinde 1 numarali betimleyici kullandiklari icin yazilanlar ekrana degil ilgili dosyaya yapilacaktir.



 
Bu konunun duzenli anlatimini icin buradaki makalenin okunmasi gereklidir.

Dosya Nesnelerinin Paylasimi

Her open cagrisi kesinlikle farkli bir dosya nesnesinin yaratilmasina yol acmaktadir. Yani ayni dosya iki kere open ile actigimizda biz diskte ayni dosyayi goren iki farkli dosya nesnesi olusturmus oluruz.
Dosya gostericisinin konumu dosya nesnesinin icerisinde oldugu icin bir betimleyicide dosya betimleyicini konumlandirdigimizda bu digerini etkilemez.
POSIX Standartlarinda read, write gibi fonksiyonlarin sistem genelinde atomik oldugu acikca belirtilmistir. Yani bir proses digerinin ya tum etkisini gorur yada digeri onun tum etkisini gorur. Ornegin iki proses ayni anda dosyanin ayni bolgesinden okuma ve yazma ypiyor olsunlar. okuyan proses bozuk bir bilgi okumaz. Ya digerinin yazmasindan once olan bilgiyi okur yada yazmasindan sonraki bilgiyi okur. Fakat bir kismi once bir kismi sonraki bilgiyi okumaz.
Fakat suphesiz iki ayri dosya islemi (iki ayri write) atomik degildir. Yani bu iki islemin arasina baska prosesin bir islemi girebilir .ista bu nedenle bu tur operasyonlarda onlemi programcinin kendisi almalidir. Bu tur islemleri etkin bir bicimde saglamak icin dosya kilitlemesi (file locking) mekanizmasi sunulmustur.
Bazen ayni dosya nesnesini goren ikinci bir betimleyici olusturmak isteyebiliriz. Bu ayni dosyayi iki kere acmakla ayni sey degildir.

Ornegin;



Burada fd1 ile fd2 ayni dosya nesnesini gostermektedir. Dolayisi ile bu iki betimleyicileri kullanmak arasinda bir farklilik yoktur. Bu islem dup fonksiyonu ile yapilmaktadir.
Fonksiyonun prototipi su sekildedir;

#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);

Doaya parametresi acilmis bir dosyaya iliskin dosya betimleyicisifdir. Fonksiyon arguman olarak verdigimiz dosya betimleyicisi ile ayni dosya nesnesini goren yeni bir betimleyici olusturur ve bize o betimleyiciniin degerini verir.




Fonksiyon basarisiz olabilir. bu durumda -1 degerine geri doner.




Linux'da normal bir kullanicinin ayni anda elde edebilecegi betimleyici sayisi (yani acik olarak tutabilecegi dosya sayisi) 1024'dur. Bu ayni zamanda dosya betimleyici tablosunun uzunlugudur.



Prosesin
 pekcok limiti gibi dosya betimleyici tablosunun uzunlugu da root 
prosesi tarafindan degistirilebilir. Bir prosesin o anki limitlerini 
elde etmek icin getrlimit, set etmek icin ise setrlimit fonksiyonlari 
kullanilmaktadir.
Ayni dosya nesnesini gosteren irden fazla betimleyici olabilecegine gore bir betimleyiciyi close fonksiyonu ile kapattigimizda dosya nesnesinin silinmemesi gerekir. iste tipik olarak kernel dosya nesnesinin icerisinde bir sayac tutar. close islemi sirasinda sayaci 1 eksiltir. Sayac 0'a duserse dosya nesnesini siler.

Fork islemi sirasinda Dosya betimleyici tablosunun alt prosese aktarilmasi

Fork islemi yapildiginda alt proses icin yeni bir proses kontrol blogu olusturulur ve ust prosesin proses kontrol blogunun icerigi birkac istisma disinda alt prosesin kontrol bloguna kopyalanir. iste bu sirada ust prosesin dosya betimleyici tablosu da alt prosesin dosya betimleyici tablosuna kopyalanmaktadir. yani ust prosesin dosya betimleyici tablosundaki dosya nesnelerinin adresleri kopyalanmakatdir. Boylece ust prosesin dosya betimleyici tablosundaki girisler ile alt prosesin dosya betimleyici tablosundaki girisler ayni dosya nesnelerini gosteriyor durumda olur. (Tabi dosya nesnelerinin sayaclari da 1 artirilir)
Buradan su sonuclar cikartilabilir:
  • fork islemi sirasinda ust prosesteki dolu olan betimleyicilerin hepsi alt proseste de gecerli olarak kullanilabilir. Baska bir degis ile biz ust proseste bir dosyayi acip fork yaptigimiz zaman o dosya alt proseste de acik durumdadir.
  • Ust proses ile alt proses ayni dosya nesnelerini paylastigi icin, ornegin ust proses dosya nesnesini degistirir ise alt proses bunu degismis olarak gorur.
  • Fork isleminden sonra artik iki prosesin daha sonra acacagi dosyalar paylasilmayacaktir.


Fork isleminden sonra exec yapilacak ise cogu kez (fakat her zaman degil) eski betimleyicilerin yani calistirilan program icin bir anlami olmaz. Yani yeni calistiirlan program zaten bunlardan habersizdir ve bu betimleyiciler bosuna yer kaplar durumda olabilirler. Iste exec yaparken istediginiz bir dosyanin otomayik kapatilmasini sistemden isteyebiliriz. Buna dosyanin "close on exec" bayragi denir. dosya temelinde bu bayrak acilip kapatilabilmektedir. Varsayilan olarak bu bayrak false durumdadir. yani exec yaparken dosya kapatilmaz.

I/O Yönlendirmelerinde dup2 fonksiyonunun kullanılması

Yönlendirme işlemlerinde önce close uygulayıp sonra open uyguladığımızda bu iki işlem arasında hibir dosyanın açılmaması gerekir. Şüphesiz programcı bu koşulu sağlayabilir. Fakat bazı durumlarda bu zor olabilir. Ayrıca yüksek numaralı bir betimleyicini bu biçimde yönlendirilmesi mümkün olmayabilir. işte bunun için dup2 fonksiyıonu tasarlanmıştır.

#include <unistd.h>
int dup2(int oldfd, int newfd);

dup2 fonksiyonu ikinci parametresi ile belirtilen betimleyiciyi açıksa önce onu kapatır. Sonra birinci parametresi ile belirtilen betimleyici açık ise onu kapatır. Sonra ikinci parametresi ile belirtilen betimleyicinin birinci parametresi ile belirtilen betimleyici ile aynı dosya nesnesini göstermesini sağlar. Başarı durumunda ikinci parametre ile belirtilen betimleyiciye başarısızlık durumunda -1 ile geri döner. Şüphesiz dup2 bu işemleri atomik yapmaktadır.

Yönlendirmelerde dup yerine dup2 kullanılması iyi bir tekniktir.



Altx Prosesin Yönlendirilmesi

Komut satırında > stdout dosyasına yönlendirmek için, < stdin dosyasına yönlendirmek için, n> veya n< bir dosyaya yönlendirmek için kullanılmaktadır. Komut satırı bu yönlendirmeyi tipik olarak şu şekilde yapmaktadır.
  • önce üst proses (yani komut saturı prosesi) bir kez fork yapar,
  • alt prosesi oluşturur,
  • sonra henüz exec uygulamadan yönlendirmeyi yapar ve exec uygular.

root@kali:~# gcc -o sample sample.c
root@kali:~# ./sample text.txt ls -l
root@kali:~# cat text.txt
......
-rw-r----- 1 root root  362 Mar 10 09:20 text.txt

Bir
 dosya açık iken remove yada unlink fonksiyonu ile dosya silinebilir. Bu
 durumda dosyanın dizin girişi silinebilir fakat dosyanın inode ve data 
bloktaki bilgileri silinmez. Fakat not alınır, dosya kapatılınca bu 
silme de yapılır.

STDIN, STDOUT ve STDERR Dosyalarının Yaratılması ve Anlamları

Stdin,stdout ve stderr dosyaları aslında mingetty yada benzeri isimde olan terminal kontrol prosesi tarafından yaratılmaktadır. Sonra oradan login prosesine, oradan shell'e, oradan da bizim programlarımıza geçer.



Pekçek sistemde login programının shell programını çalıştırması doğrudan exec ile yapılmaktadır. Yani login, yaşamına shell olarak devam etmektedir. Dolayısı ile shell farklı prosesler değil aynı prosestir. Login/shell prosesi mingetty programı tarafından beklenmektedir. Dolayısı ile biz shell'den çıktığımızda yeniden mingetty devreye girer ve o da yeniden login programını çalıştırır.

işin başında stdin dosyası klavyeye, stdout ve sterr'de ekrana yönlendirilmiş durumdadır. Tipik olarak mingetty terminal kontrol programı önce terminal aygıt sürücüsünü open fonksiyonu ile O_RWONLY payraği ile açar böylece 0 numaralı betimleyici elde edilir. Aynı aygıt sürücüyü bu kez O_WRONLY mofu ile açar.  Bu kez 1 numaralı betimleyici elde edilir. Ve nihayet 1 numaralı betimleyiciyi de dup işlemine sokara stderr için 2 numaralı betimleyiciyi elde eder. Programcı programın normal mesajlarını stout dosyasına error mesajlarını stderr dosyasına yazdırmalıdır. Böylece isterse bunları birbirinden ayırabilir.

#include <stdio.h>

int main(void)
{
    fprintf(stderr,"test for stderr\n");
    fprintf(stdout,"test for stdout\n");
    return 0;
}


root@kali:~# gcc salla.c -o salla
root@kali:~# ./salla
test for stderr
test for stdout
root@kali:~# ./salla 2> stderr.txt
test for stdout
root@kali:~# cat stderr.txt
test for stderr
root@kali:~#

Prosesler Arası Haberleşme (Interprocess Communication)

Bir prosesin başka bir prosese bilgi gönderip almasına prosesler arası haberleşme denilmektedir. Prosesler arası haberleşme ikiye ayrılmaktadır.
  • Aynı PC'de prosesler arası haberleşme
  • Network altında birbirine bağlı farklı makinaların prosesler arası haberleşme.
Aynı makinanın prosesleri arasında haberleşömelerde işletim sistemine özgü çeşitli sistemler kullanılmaktadır. Her nekadar sistemden sisteme değişse de pekcok sistem benzer yöntemleri kullanmaktadır.
Farklı makinaların prosesleri arasındaki haberleşmelerde belirlenmiş bir takım kurallara uyulması gerekmektedir. Bunun için pekçok protokol ailesi varsa da en yaygın kullanılan IP ailesidir.

Aynı Makinanın Prosesleri Arasında Haberleşme

Boru Haberleşmeleri

Boru haberlesmesinde tipik olarak iki proses vardir. Bir proses boruya yazma yapar, digeri de borudan okuma yapar. Eger yazma yapan proses hizli davranir digeri yavas kalirsa boru dolar. Dolu bir boruya yazmak istendiginde write() fonksiyonu bloke olur. Karsi taraf borudan okuma yaptiginda boruda yer acilir ve yazan taraf yazmaya devam eder. Benzer bicimde yazan taraf geri kalmis ise okuyan taraf borunun bos oldugunu gordugunda read fonksiyonunda bloke olusur. Taki diger taraf birseyler yazana kadar. Bu haberlesme once yazan tarafin sonlanmasi ile sonlanmalidir. Bu durumda okuyan taraf once boruda kalanlari okur sonra yazantarafin boruyu kapattigini anlar. O da boruyu kapatir.
Borular tamamen dosya mekanizmasi ile iliskilendirilmistir. Yani borular icin birer betimleyici ve dosya nesneleri olusturulur. Dosya fonksiyonlari ile onlar uzerinde islem yapilir.
Borular iki gruba ayrilmaktadir.
  1. isimsiz yada anonim borular. Bunlar yalnizca ust ve alt proses arasinda kullanilabilirler.
  2. Isimli borular, Bunlar ayni makinadaki herhangi iki makina arasinda kullanilabilirler. Unix/Linux sistemlerde isimli borulara FIFO'da denilmektedir.