> Dosya betimleyicilerinin çiftlenmesi: Anımsayacağımız üzere proseslerin kontrol bloğunda, Dosya Betimleyici Tablosunun adresi yer almaktadır. Aşağıda bu ilişkiye dair bir gösterim mevcuttur; task_struct(files) -> files_struct(fdt) -> fdtable(fd) -> Elemanları "file*" olan bir dizi -> Bu dizideki her bir eleman "file" türünden. ^ ^ ^ ^ ^ Açtığımız her bir yeni dosyanın bilgilerini tutan, "file" türünden yapı nesneleri. İş bu "file" türünden yapı nesnelerinin tutulduğu dizi. "fdtable" isimli yapı içerisindeki "fd" isimli eleman, iş bu diziyi göstermektedir. "files_struct" içerisindeki "fdt" isimli eleman ise "fdtable" türünden bir yapı nesnesinin adresini tutmaktadır. "task_struct" içerisindeki "files" isimli bir eleman ise "files_struct" türden bir yapı nesnesinin adresini tutmaktadır. Bu tablonun ilk üç indisi halihazırda doluyken, dördüncü indisten itibaren bizlerin kullanımına sunulmaktadır. Her bir başarılı "open" fonksiyonu çağrısı sonrasında bu tablodaki ilk boş indise yeni bir "fd" de eklenmektedir. Açılmış dosyanın bilgileri de diskten çekilerek, yeni oluşturulan "fd" yapı nesnesinin içerisine konur. Erişim hakları, "i-node" numarası vb. bilgiler diskten çekilmektedir. Nasıl ki "hard-link" çıkartılırken farklı dizin girişleri aynı "i-node" numarasına sahip olursa, burada da bir "file" türünden elemanın adresi Dosya Betimleyici Tablosundaki farklı indislerde bulunmaktadır. Dosya Betimleyici Tablosunun sıfır numaralı indisine halk arasında "stdout" betimleyicisi, bir numaralı indisine "stdin" ve iki numaralı indisine de "stderr" denmektedir. "unistd.h" içerisinde bunlar için de sembolik sabitler tanımlanmıştır; #define STDIN_FILENO 0 #define STDOUT_FILENO 1 #define STDERR_FILENO 2 Fakat bir ve iki numaralı indisler aynı "file" yapısının adresini tutmaktadırlar. "stdin" isimli indisteki "file" nesnesi, "Terminal Device Driver" ı aittir. Benzer şekilde "stdout" ve "stderr" de aynı betimleyici göstermekte olup, yine "Terminal Device Driver" a aittir. Bu üçü "Terminal Device Driver" içerisindeki ilgili fonksiyonlara çağrı yapmaktadır. "Aygıt sürücüler(device drivers)" de dosya gibi açılarak kullanılmaktadır, yani "open" fonksiyonu ile aygıt sürücüleri açarak kullanabiliriz. Devamında "read" ile okuma ve "write" ile yazma yapabiliriz. Dolayısıyla bir "file" nesnesi bir dosyaya ilişkin olabileceği gibi bir aygıt sürücüsüne ilişkin olabilir. Eğer "file" nesnesi bir dosyaya ilişkin ise bu dosya üzerinde bahsi geçen işlemleri yapabiliriz. Eğer bir aygıt sürücüsüne ilişkin ise bu aygıt sürücüsünün ilgili fonksiyonlarına çağrı yapılacaktır. Sonuçta bizim dosyamız üzerinde "read" işlemi yaparken aslında sistem fonksiyonlarına çağrı yapılmakta. Aygıt sürücüsüne "read" yapıldığında da bu sürücünün sunduğu "read" fonksiyonuna çağrı yapılacaktır. Dosyaları kapattığımız zaman("close" fonksiyonu ile), "file" yapısının içerisinde bulunan sayaç("f_count") bir azaltılmaktadır. Bu sayaç sıfıra geldiğinde "file" nesnesinin kendisi de YOK EDİLECEKTİR. "file" yapısı aşağıdaki gibidir; https://elixir.bootlin.com/linux/v6.1.4/source/include/linux/fs.h#L940 struct file { union { struct llist_node f_llist; struct rcu_head f_rcuhead; unsigned int f_iocb_flags; }; struct path f_path; struct inode *f_inode; /* cached value */ const struct file_operations *f_op; /* * Protects f_ep, f_flags. * Must not be taken from IRQ context. */ spinlock_t f_lock; atomic_long_t f_count; unsigned int f_flags; fmode_t f_mode; struct mutex f_pos_lock; loff_t f_pos; struct fown_struct f_owner; const struct cred *f_cred; struct file_ra_state f_ra; u64 f_version; #ifdef CONFIG_SECURITY void *f_security; #endif void *private_data; /* needed for tty driver, and maybe others */ #ifdef CONFIG_EPOLL struct hlist_head *f_ep; /* Used by fs/eventpoll.c to link all the hooks to this file */ #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; errseq_t f_wb_err; errseq_t f_sb_err; /* for syncfs */ } __randomize_layout __attribute__((aligned(4))) /* lest something weird decides that 2 is OK */ ; Bir betimleyicinin gösterdiği "file" nesnesini gösteren başka bir betimleyici oluşturabiliriz. Bunun için iki adet POSIX fonksiyonu oluşturulmuştur. Bu fonksiyonları "dup" ve "dup2" isimli fonksiyonlardır. >> "dup" fonksiyonu aşağıdaki biçimde bir parametrik yapıya sahiptir; #include int dup(int fildes); -> Bu fonksiyonun aldığı parametre, açık bir dosyaya ilişkin "fd". Geri dönüş değeri olarak da iş bu "fd" tarafından gösterilen "file" nesnesini gösteren yeni bir "fd". Bu fonksiyonun en düşük "fd" değerini verdiği garanti altındadır. Yani tablodaki en düşük indisi vermektedir. -> Argüman olarak geçtiğimiz "fd" nin geçersiz olması, Dosya Betimleyici Tablosunun tamamen dolması ki maksimum indis sayısı varsayılan durumda 1024 adettir, vb. nedenlerden dolayı fonksiyonumuz başarısız olacaktır ve "-1" ile geri dönecektir. Buradaki "fd" aslında Dosya Betimleyici Tablosundaki bir indistir. Başarısızlık durumunda yine "errno" uygun değerini alacaktır. Açık dosyanın bütün bilgileri "file" nesnesinin içinde olduğuna göre ve iki "fd" de aynı "file" nesnesini gösteriyorsa, ortaya aynı "file pointer" a sahip olma durumu çıkacaktır. Aşağıdaki örnekte bu gösterilmiştir. * Örnek 1, aşağıdaki kodlar "main.c" dosyası içerisindedir. #include #include #include #include void exit_sys(const char* msg); int main(void) { /* # INPUT # *main.c* */ /* # OUTPUT # [#include <] | [stdio.h> ] */ int fd; if( (fd = open("main.c", O_RDONLY)) == -1 ) exit_sys("open"); int fd2; if( (fd2 = dup(fd)) == -1 ) exit_sys("dup"); char buffer[10 + 1]; ssize_t result; if( (result = read(fd, buffer, 10)) == -1 ) exit_sys("read"); buffer[result] = '\0'; printf("[%s]", buffer); printf(" | "); if( (result = read(fd, buffer, 10)) == -1 ) exit_sys("read"); buffer[result] = '\0'; printf("[%s]", buffer); /* * "file" nesnesinin kendisini de yok etmek için bütün * "fd" lerin kapatılması gerekmektedir. */ close(fd); close(fd2); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >> "dup2" isimli fonksiyon, işlevsel olarak "dup" fonksiyonunu da kapsamaktadır fakat biraz daha ayrıntılı biçimidir. Bu fonksiyon aşağıdaki parametrik yapıya sahiptir; #include int dup2(int fildes, int fildes2); Fonksiyonun, -> Birinci parametresi, çiftlemek istediğimiz "fd" yi belirtmektedir. -> İkinci parametresi ise yeni oluşturulacak olanın "fd" bilgisidir. Anımsanacağı üzere "fd" bilgileri birer indis bilgileridir. Dolayısıyla yeni oluşturulacak "fd", iş bu ikinci parametreki indis numarasına sahip olacaktır. Eğer bu parametre ile gösterilen açık bir dosyaya ilişkin ise ilgili dosya önce kapatılacaktır. Fakat bu durum, bahsi geçen dosyanın yok edileği anlamına da gelmesin çünkü halihazırda çiftlenmiş bir "fd" olabilir. -> Başarısızlık durumunda "-1" ile, başarı durumunda ise ikinci parametredeki ile geri dönmektedir ve "errno" uygun değerini alacaktır. Artık bu fonksiyon ilk boş betimleyiciyi değil, ikinci parametresine geçilen betimleyici ile geri dönmektedir.Bir diğer deyişle; bu fonksiyona geçilen "fd" değerleri aynı "file" nesnesini gösterecektir(fonksiyonun başarılı olduğu varsayılmıştır). Fakat unutmamalıyız ki iş bu fonksiyon, argüman olarak geçilen "fd" değerlerini işleme sokmadan önce birbiri ile eşit olup olmadığını kontrol etmektedir. * Örnek 1, #include #include #include #include void exit_sys(const char* msg); int main(void) { /* # INPUT # *main.c* */ /* # OUTPUT # <<< 3 | 31 >>> [#include <] | [stdio.h> ] */ int fd; if( (fd = open("main.c", O_RDONLY)) == -1 ) exit_sys("open"); int fd2; if( (fd2 = dup2(fd, 31)) == -1 ) exit_sys("dup2"); printf("<<< %d | %d >>>\n", fd, fd2); char buffer[10 + 1]; ssize_t result; if( (result = read(fd, buffer, 10)) == -1 ) exit_sys("read"); buffer[result] = '\0'; printf("[%s]", buffer); printf(" | "); if( (result = read(fd, buffer, 10)) == -1 ) exit_sys("read"); buffer[result] = '\0'; printf("[%s]", buffer); close(fd); close(fd2); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } > IO Yönlendirmesi (IO Redirection): Bir dosya üzerinde işlem yaptığımızı zannederken, aslında bambaşka bir dosya üzerinde işlem yapıyor oluşumuzdur. Arka planda ise dosya betimleyicisi "fd" nin başka bir dosya nesnesini göstermesi durumudur. Bu işlem en çok "stdin", "stdout" ve "stderr" dosyaları üzerinde uygulanmaktadır. Anımsanacağı üzere, bir proses hayata geldiğinde dosya betimleyici tablosunun ilk üç indisi dolu durumdadır. Sıfır numaralı indis "stdin", bir ve iki numaralı indisler ise sırasıyla "stdout" ve "stderr" isimli dosya nesnelerine ilişkindir. Burada bir ve iki numaralı indiste bulunan dosya betimleyicileri "duplicate" edilmiştir. Bunun detaylarına ilerleyen konularda değineceğiz. Bir dosya betimleyicisi disk üzerindeki gerçek bir dosyaya ilişkin de olabilir, bir aygıt sürücüsüne ilişkin de olabilir. Dosya betimleyici tablosunun ilk üç indisinde bulunan betimleyiciler "Terminal Device Driver" isimli aygıt sürücüsüne ilişkindirler. Aygıt sürücüsüne ilişkin dosya betimleyicileri kullanılarak "read", "write" gibi POSIX fonksiyonları çağrıldığında aygıt sürücüsünün kendi içerisindeki okuma ve yazmaya ilişkin fonksiyonlar çağrılmaktadır ve asıl işi aygıt sürücülerinin ilgili fonksiyonları yapmaktadır. Yine burada da, POSIX fonksiyonları sarmalayıcı bir görev üstlenmiştir, diyebiliriz. Aygıt sürücülere ilişkin detaylar kursun ilerleyen dönemlerinde işlenecektir. Yine unutmamalıyız ki aygıt sürücüler de "kernel mode" biçiminde çalışmaktadır. Sıfır indisli dosya betimleyicisi "read-onlu" modda açılmıştır ve bu betimleyici kullanılarak bir okuma yapılmak istendiğinde aygıt sürücüsünün okuma işlevi görev fonksiyonu çağrılacaktır ve iş bu fonksiyon da klavyeden okuma işlemini gerçekleştirecektir. Benzer şekilde bir ve iki numaralı indisli dosya betimleyiciler de "write-only" modda açılmıştır ve aygıt sürücüsünün yazma işlevini yürüten fonksiyonları çağrılacaktır eğer bu dosya betimleyicisini kullanırsak. Bu durumda da iş bu fonksiyonlar, ekrana yazı basma işlevini yürüteceklerdir. Buradan da görüleceği gibi aygıt sürücüler sanki birer dosyaymış gibi ele alınmaktadır. Yine POSIX sistemlerde "stdint", "stdout" ve "stderr" isimli dosya nesnelerine hitap eden sıfır, bir ve iki numaraları dosya betimleyicilerine sırasıyla "STDIN_FILENO", "STDOUT_FILENO" ve "STDERR_FILENO" sembolik sabitlerini tanımlamışlardır. İndis numaraları yerine iş bu sembolik sabitleri de kullanabiliriz. Amaç okunabilirliği arttırmaktır. Bu üç sembolik sabit de "unistd.h" isimli başlık dosyasında tanımlanmıştır. C dilindeki "stdio.h" içerisinde tanımlanmış ve "stdin" ve "stdout" dosyaları üzerinde işlem yapan "printf", "scanf" gibi fonksiyonlar günün sonunda sıfır ve bir numaralı indislerde bulunan dosya betimleyicilerini "read" ve "write" fonksiyonları ile çağırmaktadır. Burada "print" isimli fonsksiyon "write" isimli POSIX fonksiyonunu çağırırken, "scanf" ise "read" isimli fonksiyonu çağırmaktadır. Sadece ilgili POSIX fonksiyonlarını çağırmadan evvel yazılacak ya da okunacak yazıları bir "buffer" içerisinde depolarlar. Dosya betimleyici tablosunun ilk üç betimleyicisinde otomatik olarak yer alan "stdin", "stdout" ve "stderr" isimli dosyaları bizler KAPATMAMALIYIZ. * Örnek 1, #include #include #include void exit_sys(const char* msg); int main() { /* * Görüldüğü üzere "open" fonksiyonuna çağrı yapmadan direkt * olarak ekrana yazı bastık. */ write(1, "this is a test\n", 16); // OUTPUT => this is a test } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, #include #include #include void exit_sys(const char* msg); int main() { char buffer[4096]; ssize_t result; /* 'Enter' tuşuna basılana kadar klavyeden okuma yapacak ve * bunları da 'buffer' alanına yazacaktır. */ if((result = read(0, buffer, 4096)) == -1) exit_sys("read"); // INPUT => Ulya Yuruk /* * 'buffer' alanındakiler ekrana basacaktır. */ if((write(1, buffer, result)) == -1) exit_sys("write"); // OUTPUT => Ulya Yuruk return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, #include #include #include #include void exit_sys(const char* msg); int main() { /* # q.txt # Number: 0 Number: 1 Number: 2 Number: 3 Number: 4 Number: 5 Number: 6 Number: 7 Number: 8 Number: 9 */ /* Bir numaralı dosya betimleyicisi kapatıldı. */ close(1); int fd; /* * Bir numaralı betimleyici kapatıldı. "open" fonksiyonu da müsait olan en düşük betimleyiciyi * döndüreceği garanti olduğu için bize bir numaralı betimleyiciyi döndürmektedir. Fakat bizler * "open" ile "q.txt" dosyasını açtığımız için, bir numaralı betimleyici artık "q.txt" dosyasına * ilişkin. */ if((fd = open("q.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) exit_sys("open"); /* * 'printf' fonksiyonu, günün sonunda 'write' fonksiyonunu bir numaralı betimleyici kullanarak * çağırdığı için, ekrana yazmak yerine açmış olduğumuz dosyaya yazacaktır. */ for(int i = 0; i < 10; ++i) printf("Number: %d\n", i); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } 3 numaralı örnekteki gibi bir "IO" yönlendirmesi yapmamız genel hatlarıyla bir soruna yol açmaz çünkü "open" fonksiyonu ilk boş betimleyiciyi bize döndürmektedir. Eğer bizler bu üç betimleyici haricindeki bir betimleyiciye yönlendirme yaparsak bir sorun ile karşılaşabiliriz çünkü "open" fonksiyonu bizim kapattığımız betimleyiciyi geri döndürmeyebilir. Karşılaşacağımız bir diğer problem ise "multi-thread" programlama sırasındadır. "close" ile kapattıktan sonra "open" ile açmadan evvel başka bir "thread" "open" fonksiyonuna çağrı yaparsa, ilk boş betimleyiciyi o alacağı için, bizler başka bir betimleyici alabiliriz. Bu problemlere çözüm olarak "dup2" fonksiyonunu kullanmalıyız çünkü iş bu fonksiyon hem atomik olarak yukarıda yapılanları yapmaktadır hem de bizim istediğimiz dosya betimleyicisini geri döndürmektedir(başarılı olduğu varsayılmıştır). Fakat geri dönmek için, "dup2" öncesinde ilgili dosya betimleyicisini de yedeklemeliyiz. * Örnek 1, #include #include #include #include void exit_sys(const char* msg); int main() { /* # q.txt # Number: 0 Number: 1 Number: 2 Number: 3 Number: 4 Number: 5 Number: 6 Number: 7 Number: 8 Number: 9 */ int fd; /* * "open" fonksiyonu en düşük "fd" değerini bize döndürecektir. */ if((fd = open("q.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) exit_sys("open"); printf("fd : [%d]\n", fd); // OUTPUT => fd : [3] /* * Dosya Betimleyici Tablosunun bir numaralı indisindeki "file" nesnesi kapatılıyor fakat * ilgili "file" nesnesi henüz yok edilmiyor çünkü iki numaralı indis de hala bu "file" * nesnesini göstermektedir. Ek olarak, bir numaralı indis ile "fd" aynı "file" nesnesini * göstermektedir. */ if(dup2(fd, 1) == -1) exit_sys("dup2"); /* İlgili "fd" betimleyicisini biz açtığımız için biz kapatmalıyız. */ close(fd); /* * 'printf' fonksiyonu, günün sonunda 'write' fonksiyonunu bir numaralı betimleyici kullanarak * çağırdığı için, ekrana yazmak yerine açmış olduğumuz dosyaya yazacaktır. Çünkü sadece bir * numaralı indis, yukarıda "open" ile oluşturulan "file" nesnesini göstermektedir. */ for(int i = 0; i < 10; ++i) printf("Number: %d\n", i); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, #include #include #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # Number: 0 Number: 1 Number: 2 Number: 3 Number: 4 Number: 5 Number: 6 Number: 7 Number: 8 Number: 9 */ int fd; /* * "open" fonksiyonu en düşük "fd" değerini bize döndürecektir. */ if((fd = open("q.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) exit_sys("open"); printf("fd : [%d]\n", fd); // OUTPUT => fd : [3] /* * Dosya Betimleyici Tablosunun bir numaralı indisindeki "file" nesnesi kapatılıyor fakat * ilgili "file" nesnesi henüz yok edilmiyor çünkü iki numaralı indis de hala bu "file" * nesnesini göstermektedir. Ek olarak, bir numaralı indis ile "fd" aynı "file" nesnesini * göstermektedir. */ if(dup2(fd, 1) == -1) exit_sys("dup2"); /* İlgili betimleyiciyi biz açtığımız için biz kapatmalıyız. */ close(fd); /* * 'printf' fonksiyonu, günün sonunda 'write' fonksiyonunu bir numaralı betimleyici kullanarak * çağırdığı için, ekrana yazmak yerine açmış olduğumuz dosyaya yazacaktır. Çünkü sadece bir * numaralı indis, yukarıda "open" ile oluşturulan "file" nesnesini göstermektedir. */ for(int i = 0; i < 10; ++i) printf("Number: %d\n", i); /* 'print' tamponlu çalıştığı için iş bu tamponu sıfırlamamız gerekiyor. */ fflush(stdout); /* Bir numaralı betimleyici tekrardan kapatılıyor ve iki numaralı betimleyici göstermeye * başlıyor. Artık yazılar dosyaya değil, ekrana çıkacaktır eğer 'print' kullanırsak. */ if(dup2(2, 1) == -1) exit_sys("dup2"); for(int i = 0; i < 10; ++i) printf("Number: %d\n", i); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, #include #include #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # Number: 0 Number: 1 Number: 2 Number: 3 Number: 4 Number: 5 Number: 6 Number: 7 Number: 8 Number: 9 */ /* İki numaralı dosya betimleyicisi kapatıldı. */ close(2); int fd; /* * İki numaralı betimleyici kapatıldı. "open" fonksiyonu da müsait olan en düşük betimleyiciyi * döndüreceği garanti olduğu için bize iki numaralı betimleyiciyi döndürmektedir. Fakat bizler * "open" ile "q.txt" dosyasını açtığımız için, iki numaralı betimleyici artık "q.txt" dosyasına * ilişkin. */ if((fd = open("q.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) exit_sys("open"); int fd_stdout; /* * Bir numaralı betimleyicinin gösterdiği "file" nesnesini gösteren yeni bir betimleyici elde edildi. * Böylelikle bir numaralı betimleyici yedeklenmiş oldu. */ if((fd_stdout = dup(1)) == -1) exit_sys("dup"); /* * Bir numaralı betimleyici kapatıldı ve "fd" betimleyicisi ile aynı dosya nesnesini gösterir oldular. */ if(dup2(fd, 1) == -1) exit_sys("dup2"); /* İlgili betimleyiciyi biz açtığımız için biz kapatmalıyız. */ close(fd); /* Artık bu aşamada bir numaralı betimleyici "q.txt" dosyasını gösterir durumdadır. */ /* * 'printf' fonksiyonu, günün sonunda 'write' fonksiyonunu bir numaralı betimleyici kullanarak * çağırdığı için, ekrana yazmak yerine açmış olduğumuz dosyaya yazacaktır. */ for(int i = 0; i < 10; ++i) printf("Number: %d\n", i); /* 'print' tamponlu çalıştığı için iş bu tamponu sıfırlamamız gerekiyor. */ fflush(stdout); /* Bir numaralı betimleyici tekrardan kapatılıyor ve 'fd_stdout' isimli betimleyici göstermeye * başlıyor. */ if(dup2(fd_stdout, 1) == -1) exit_sys("dup2"); for(int i = 0; i < 10; ++i) printf("Number: %d\n", i); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 4, #include #include #include #include void exit_sys(const char* msg); int main() { /* # q.txt # 10 20 30 30 20 10 10 20 30 30 20 10 20 30 10 20 10 */ /* # OUTPUT # 10 20 30 30 20 10 */ int fd; if((fd = open("q.txt", O_RDONLY)) == -1) exit_sys("open"); /* * Sıfır numaralı betimleyici kapatılıyor ve ilgili betimleyici "fd" betimleyicisi * ile aynı dosyayı göstermeye başlıyor. */ if(dup2(fd, 0) == -1) exit_sys("dup2"); /* İlgili betimleyici biz açtığımız için bizler kapatmalıyız. */ close(fd); int val; /* * "scanf" fonksiyonu da sıfır nolu betimleyiciyi kullanarak "read" fonksiyonunu * çağırdığı için, dosyadan okuma yapıyor. Klavyeden değil. */ while( scanf("%d", &val) == 1 ) printf("%d\n", val); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Öte yandan şöyle bir senaryo da gerçekleşebilir; -> "open" fonksiyonu ile bir dosyayı açmak isteyelim. Fakat "open" işleminden evvel 1 numaralı "fd" nin gösterdiği "stdout" u da kapatılmış olsun. Anımsanacağınız üzere "open" fonksiyonu bize en düşük "fd" değerini verecektir. -> Dolayısıyla "open" ile açacağımız dosya artık 1 numaralı "fd" değeri ile gösterilmektedir. -> Daha sonra bizler "dup2(fd, 1)" şeklinde bir çağrı yapalım. Buradaki "fd", az evvel "open" ile açtığımız dosyaya aittir. -> "dup2" fonksiyonuna geçilen argümanların ikisi de aynı ise fonksiyon bir işlem gerçekleştirmiyordu. -> Fakat hemen ardından "close(fd)" çağrısı yapmamız aslında "1" numaralı betimleyici kapatacaktır. Bizler aslında gereksiz olarak gördüğümüz "fd" yi kapatmak istiyorken, zaten bir tane olan "fd" yi kapatmış olacağız. Bunu gidermek için aşağıdaki gibi bir koruma mekanizması gerçekleştirebiliriz: if(fd != 1) { if(dup2(fd, 1) == -1) exit_sys("dup2"); close(fd); } Böylece "fd" ile "1" farklı betimleyiciler ise gereksiz olan "fd" kapatılacaktır. Tabii bu durum çok nadir karşımıza çıkabilecek bir durumdur. Dolayısıyla özel bir durum belirtilmediyse, böyle bir kontrole gerek yoktur. "./sample > q.txt" biçimindeki bir kabuk komutu kullanıldığında, kabuk programı "sample" programını ve "q.txt" dosyasını açıyor. Daha sonra "sample" programının bir numaralı betimleyicisini "dup2" ile "q.txt" dosyasının betimleyicisine eşliyor. Böylelikle "stdout" dosyası yerine "q.txt" dosyasına yazma yapıyor eğer "sample" programı içerisinde "printf" vb. fonksiyonlar varsa. "q.txt" dosyası açılırken "O_WRONLY | O_TRUNC" modlarını kullanıyor. Fakat kabuk programının bunu nasıl yaptığı ileriki konularda ele alınacaktır. Bu yöntem ile "ls", "cat" gibi kabuk komutlarını da istediğimiz dosyaya yazma yapmalarını sağlayabiliriz. Bu durumda kabuk üzerinde şu komutu uygulayabiliriz; "ls -l > q.txt". Eğer ">" yerine ">>" kullanılırsa, "q.txt" dosyasının açış modu "O_CREAT | O_WRONLY | O_APPEND" biçiminde olacaktır. Böylelikle yazma işlemi dosyanın sonuna yapılacaktır. "ls -l >> q.txt" komutunu örnek olarak verebiliriz. Burada "IO" yönlendirmesini yapan "./sample" veya "ls" programları değil, kabuk programına ">" seçeneğinin geçilmesidir. "<" sembolünün kullanılması ise sıfır indisli betimleyicinin ilgili dosyaya yönlendirileceği anlamındadır. Örneğin, "./sample < q.txt" şeklinde bir komut çalıştıralım. İlgili "q.txt" dosyası "O_RDONLY" modda açılır ve "./sample" prosesinin sıfır numaralı betimleyicisini "q.txt" dosyasına yönlendirir. Böylelikle "./sample" programı klavyeden okuma yapmak yerine dosyadan okuma yapacaktır; "scanf" programı artık dosyadan okuma yapacaktır. Velevki "n>" kullanırsak, "n" numaralı betimleyiciyi sağ taraftaki dosyaya yönlendirmektedir(yazma amacıyla). "10<" ise 10 numaralı betimleyiciyi ilgili dosyaya okuma amacıyla yönlendirecektir. "ls -l 2> test.txt" şeklindeki bir komut, iki numaralı betimleyiciyi ilgili text dosyasına yönlendirecektir. Kabuk, bütün bu yönlendirme işlemlerini şu şekilde yapmaktadır; önce bir kez "fork" işlemi yapar, daha sonra yönlendirme işlemini. En son da "exec" işlemini. Dosya betimleyici tablosu prosese özgür bir tablodur, dolayısıyla dosya betimleyiciler de prosese özgürüdür. Son olarak kabuk üzerinden hem "stdout" hem de "stdin" dosyalarını birlikte yönlendirebiliriz. Örneğin, "./sample > out.txt < in.txt" komutunu çalıştırırsak; bir numaralı betimleyici "out.txt" ye yazarken, sıfır numaralı betimleyici ise "in.txt" dosyasından okuma yapacaktır. > "stderr" dosyası nedir? Anımsayacağımız üzere dosya betimleyici tablosunun bir ve iki numaralı indisli betimleyicileri aynı "file" nesnesini gösterir durumdadırlar. Yani "fprintf" fonksiyonu üzerinden "stdout" ve "stderr" isimli betimleyicileri (ya da bir ve iki indis numaralı betimleyicileri) kullandığımız vakit, çıktının ekrana yapıldığı görmekteyiz. Burada "fprintf" kullanma sebebimiz, istediğimiz dosya betimleyicisini belirleme imkanımızın olmasıdır. Çünkü "printf" fonksiyonu direkt olarak "stdout" isimli betimleyiciyi kullanmaktadır. Bu konudaki farklılık "scanf" ile "fscanf" fonksiyonları arasında da vardır. Fakat unutmamalıyız ki C dilindeki "stdout", "stdin" ve "stderr" isimli değişkenler DOSYA BETİMLEYİCİSİ NİTELEMEZLER. BUNLAR "FILE*" türden değişkenlerdir. * Örnek 1, #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # This is a output => stdout This is a output => stderr */ fprintf(stdout, "This is a output => stdout\n"); // printf("This is a output => stdout\n"); fprintf(stderr, "This is a output => stderr\n"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, "stdout", "stdin" ve "stderr" isimleri bir dosya betimleyicisini nitelemezler. Çünkü bunlar "FILE*" türden değişkenlerdir: #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # expected ‘FILE * restrict’ {aka ‘struct _IO_FILE * restrict’} but argument is of type ‘int’ extern int fprintf (FILE *__restrict __stream, */ fprintf(0, "This is a output => stdout\n"); // warning: NULL argument where non-NULL required (argument 1) [-WnonNULL] fprintf(1, "This is a output => stderr\n"); // warning: passing argument 1 of ‘fprintf’ makes pointer from integer without a cast [-Wint-conversion] return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } O halde "stderr" dosya betimleyicisinin var oluş amacı nedir? Bir programdaki hata mesajlarını, programcı, "stderr" dosyasına yazdırsın ama "stdout" dosyasına yazdırmasın. Böylelikle ilgili dosya betimleyicilerini tekrardan yönlendirerek, programın çıktısını incelemek ve okumak daha kolay olsun. Örneğin, kabuk üzerinden "./sample > test.txt" komutunu çalıştırdığımız zaman ilgli programın üreteceği mesajları ekrana değil "test.txt" dosyasına yazılacaktır. Buradan hareketle diyebiliriz ki hata mesajlarının "stderr" dosyasına yazdırılması iyi bir tekniktir. Eğer ileride "IO" yönlendirmesi yaparsak hata mesajları yönlendirilen dosyaya yazılacaktır. Eğer yönlendirme yapmaz isek yine ekrana çıkmaya devam edecektir. Bir nevi ileriye dönük yatırım olarak da görebiliriz. * Örnek 1, herhangi bir "IO" yönlendirmesi yapmazsak: #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # This is a output => stdout This is a output => stderr */ fprintf(stdout, "This is a output => stdout\n"); fprintf(stderr, "This is a output => stderr\n"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, "./sample > test.txt" şeklinde bir yönlendirme yaparsak: #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # This is a output => stderr */ fprintf(stdout, "This is a output => stdout\n"); fprintf(stderr, "This is a output => stderr\n"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, "./sample 2> test.txt" şeklinde bir yönlendirme yaparsak: #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # This is a output => stdout */ fprintf(stdout, "This is a output => stdout\n"); fprintf(stderr, "This is a output => stderr\n"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 4, "find / -name "sample.c"" biçiminde bir komut çalıştırdığımız zaman karşımıza aşağıdaki çıktılar gelecektir: $ find / -name "sample.c" find: '/lost+found': Permission denied find: '/home/redxx': Permission denied find: '/home/objc': Permission denied find: '/root': Permission denied find: '/run/cryptsetup': Permission denied find: '/run/cups/certs': Permission denied find: '/run/httpd': Permission denied //... * Örnek 5, "find / -name "sample.c" 2> err.txt" biçiminde bir komut çalıştırdığımız zaman karşımıza aşağıdaki çıktılar gelecektir(https://www.tutorialspoint.com/linux_terminal_online.php): $ find / -name "sample.c" 2> err.txt $ * Örnek 6, "find / -name "sample.c" 2> /dev/NULL" biçiminde bir komut çalıştırdığımız zaman, hata mesajları "/dev/NULL" dosyasına yazılacaktır ki bu dosya kendisine yazılanların hepsini silmektedir. Dipsiz bir kuyu gibi de düşünebiliriz. Dolayısıyla hata mesajları için yeni bir dosya oluşturmak istemiyorsak, iş bu aygıt sürücüsünü kullanabiliriz.