> POSIX Fonksiyonlarında Hata Kontrolleri: >> İş bu fonksiyonların %70'e yakını "int" türden değer döndürmektedir. Böyle fonksiyonlarda "0" değeri başarı, "-1" değeri ise başarısızlık manasına gelmektedir. Diğer yandan bazı fonksiyonlar ise gösterici türünden değer döndürmektedir. Bu durumda da NULL değeri başarısızlık anlamına gelmektedir. >> İlgili fonksiyonlar başarısız olduklarında, yukarıdakilere ek olarak, "errno" isimli değişkenin de değerini değiştirmektedir. Bu "errno" değişkeni "errno.h" başlık dosyası içerisinde bildirilmiş olup, "int" türden bir değişken ya da bu türe açılabilen bir makrodur. Dolayısıyla bizlerin "errno" isminde bir makro yazması ya da "errno" isminde bir değişkeni kullanması "Tanımsız Davranış" oluşturacaktır. Ek olarak birden fazla POSIX fonksiyonu "errno" değişkeninin değerini değiştirdiğinde, en son değiştireninki geçerli olacaktır. Bu nedenden dolayı hangi POSIX fonksiyonlarının "errno" değişkenini değiştirdiğini bilirsek, hataların nedenlerini daha sağlıklı bulabiliriz. Son olarak "errno" değişkeninin almış olduğu değerler de "#define" edilmiştir. Hangi değerleri aldığı ve bu değerlerin açıklamalarını gerek ilgili POSIX fonksiyonunun dökümanında gerek şu https://pubs.opengroup.org/onlinepubs/9699919799/ adresinden öğrenebiliriz. Bu değerlerin anlamları ve değerlerin kendileri standart haldedir fakat bazı sistemler kendilerine özgü değerler de tanımlamış olabilir. Aşağıda bu konuya bir örnek verilmiştir. * Örnek 1, //.. int main() { //.. if(ThePosixFunc() == -1) { //.. /* * Buradaki "EPERM", UNIX sistemlerinde ortak bir arayüz oluşturmak için * tanımlanmıştır ve "Operation not permitted" anlamındadır. */ if(errno == EPERM) { //.. } //.. } //.. } Bütün bunların yanı sıra, hiç bir fonksiyon "errno" değişkeninin değerini sıfıra çekmez. Çünkü standartlarca bu garanti altındadır. Yine unutmamalıyız ki bazı POSIX fonksiyonları başarılı olduklarında bile "errno" değişkeninin değerini değiştirmektedirler. Bunların hangi fonksiyon olduklarına ileride değineceğiz. Dolayısıyla tavsiye edilen yöntem, sadece başarısız olduğu bilinen fonksiyonlar için "errno" değişkenine bakmak olacaktır. Son olarak bazı POSIX fonksiyonları "errno" değişkeninin değerini hiç değiştirmeden, direkt olarak hata kodu ile dönerler. Böyle fonksiyonlar için, "0" ile dönmek demek başarılı olduğu anlamındadır. Öte yandan bu "errno" değişkeni "thread" lere özgü olduğundan, ayrı "thread" içerisindekiler birbirlerininkini "set" edemezler. Pekiyi bizler bu "errno" değişkeninin almış olduğu değerleri, daha doğrusu vermek istediği mesajı ekrana nasıl yazdırırız? Tabiki "strerror" ya da "perror" isimli fonksiyonları kullanarak: >>> "strerror" fonksiyonu, aşağıdaki parametrik yapıya sahiptir; #include char *strerror(int errnum); Bu fonksiyon, "errno" değişkenini argüman olarak almakta ve bu değere karşılık gelen yazıyı da geri döndürmektedir. Döndürülen yazı statik ömürlü bir yazı olduğundan, "free()" fonksiyonu ile yazının tuttuğu alanı tekrardan geri vermeye lüzum yoktur.Ek olarak bu fonksiyon standart bir C fonksiyonudur. * Örnek 1, #include "stdio.h" #include "stdlib.h" #include "string.h" #include "fcntl.h" #include "errno.h" int main(void) { /* # INPUT # ./wd */ /* # OUTPUT # Error opening file: No such file or directory */ int fd; /* * Buradaki "open" fonksiyonu bir POSIX fonksiyonu olup, * başarısız olduğunda "-1" ile geri dönmektedir. */ if((fd = open("xxx.txt", O_RDONLY)) == -1) { fprintf(stderr, "Error opening file: %s\n", strerror(errno)); exit(EXIT_FAILURE); } fprintf(stdout, "\n********************************\n"); return 0; } * Örnek 2, Merak ettiğimiz herhangi bir "errno" değişkeninin mesajını da yazdırabiliriz: #include "stdio.h" #include "stdlib.h" #include "string.h" #include "fcntl.h" #include "errno.h" int main(void) { /* # INPUT # ./wd */ /* # OUTPUT # Operation not permitted ******************************** */ fprintf(stdout, "%s\n", strerror(EPERM)); fprintf(stdout, "\n********************************\n"); return 0; } >>> "perror" fonksiyonu, aşağıdaki parametrik yapıya sahiptir; #include void perror(const char *s); Bu fonksiyon ise parametre olarak bir yazı almaktadır. Daha sonra bu yazının sonuna ": " karakterlerini eklemektedir. Devamında da o anki "errno" değişkeninin sahip olduğu değerin yazısal karşılığı eklenmektedir. Son olarak birleştirilen bu yazı, "stderr" akımına yazılmaktadır. Yine bu fonksiyon da standart bir C fonksiyonudur. * Örnek 1, #include "stdio.h" #include "stdlib.h" #include "fcntl.h" int main(void) { /* # INPUT # ./wd */ /* # OUTPUT # [Error opening file: ]_xxx.txt: No such file or directory */ int fd; /* * Buradaki "open" fonksiyonu bir POSIX fonksiyonu olup, * başarısız olduğunda "-1" ile geri dönmektedir. */ if((fd = open("xxx.txt", O_RDONLY)) == -1) { fprintf(stderr, "[Error opening file:]_"); perror("xxx.txt"); exit(EXIT_FAILURE); } fprintf(stdout, "\n********************************\n"); return 0; } * Örnek 2, "perror" fonksiyonunun temsili implementasyonu: #include "stdio.h" #include "stdlib.h" #include "fcntl.h" #include "errno.h" #include "string.h" void MyErrno(const char* msg) { fprintf(stderr, "%s: %s\n", msg, strerror(errno)); } int main(void) { /* # INPUT # ./wd */ /* # OUTPUT # [Error opening file:]_xxx.txt: No such file or directory */ int fd; /* * Buradaki "open" fonksiyonu bir POSIX fonksiyonu olup, * başarısız olduğunda "-1" ile geri dönmektedir. */ if((fd = open("xxx.txt", O_RDONLY)) == -1) { fprintf(stderr, "[Error opening file:]_"); MyErrno("xxx.txt"); exit(EXIT_FAILURE); } fprintf(stdout, "\n********************************\n"); return 0; } * Örnek 3, İlgili fonksiyonu bir başka fonksiyon ile sarmalamak: #include "stdio.h" #include "stdlib.h" #include "fcntl.h" void exit_system(const char* msg); int main(void) { /* # INPUT # ./wd */ /* # OUTPUT # Error opening: : No such file or directory */ int fd; /* * Buradaki "open" fonksiyonu bir POSIX fonksiyonu olup, * başarısız olduğunda "-1" ile geri dönmektedir. */ if((fd = open("xxx.txt", O_RDONLY)) == -1) exit_system("Error opening: "); fprintf(stdout, "\n********************************\n"); return 0; } void exit_system(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 4, Değişken adetli argüman alan bir fonksiyon kullanarak hata mesajının yazdırılması: #include "stdio.h" #include "stdlib.h" #include "string.h" #include "stdarg.h" #include "errno.h" #include "fcntl.h" void exit_system(const char* msg, ...); int main(void) { /* # INPUT # ./wd */ /* # OUTPUT # Error opening xxx.txt: : No such file or directory */ int fd; char path[] = "xxx.txt"; /* * Buradaki "open" fonksiyonu bir POSIX fonksiyonu olup, * başarısız olduğunda "-1" ile geri dönmektedir. */ if((fd = open(path, O_RDONLY)) == -1) exit_system("Error opening %s: ", path); fprintf(stdout, "\n********************************\n"); return 0; } void exit_system(const char* msg, ...) { va_list ap; va_start(ap, msg); vfprintf(stderr, msg, ap); fprintf(stderr, ": %s\n\n", strerror(errno)); va_end(ap); exit(EXIT_FAILURE); } >> UNIX/Linux sistemlerde, standart C fonksiyonları da aynı zamanda POSIX fonksiyonu kabul edilmektedirler. Dolayısıyla bu fonksiyonlar başarısızlık durumunda "errno" değişkeninin değerini değiştirebilmektedir. Her ne kadar standart C fonksiyonları özünde böyle bir işlem YAPMASALARDA. Örneğin, Standart C fonksiyonu olan "fopen" ile bunun UNIX/Linux dünyasındaki karşılığını aşağıda görebiliriz. >>> Standart C fonksiyonu olan "fopen" => https://en.cppreference.com/w/cpp/io/c/fopen >>> POSIX fonksiyonu olan Standart C fonksiyonu olan "fopen" => https://pubs.opengroup.org/onlinepubs/9699919799/ > Hata mesajlarının yerelleştirilmesi için "locale" bilgisinin değiştirilmesi gerekiyor. "setlocal" fonksiyonu ile "locale" bilgisini değiştirebiliriz. Varsayılan lokal dili İngilizce dilidir. Lokallerin yazım biçiminde önce ülke, sonra o ülke içindeki lehçe ve "encoding" ayarına dair bilgi gelir. Aşağıdaki örnekte "tr" ülkeyi, "TR" o ülke içindeki şiveyi, "UTF-8" ise "encoding" bilgisini içerir. Bir program başladığında ise varsayılan lokal bilgisi "C" şeklindedir. Minimal, hiç bir ülkeyle ve dil ile alakası olmayan bir lokaldir. Yani "tr_TR.UTF-8" yerine "C" gelir. * Örnek 1, #include "stdio.h" #include "stdlib.h" #include "string.h" #include "errno.h" #include "locale.h" void exit_sys(const char* msg); int main(void) { if (setlocale(LC_ALL, "tr_TR.UTF-8") == NULL) { fprintf(stderr, "cannot set locale!...\n"); exit_sys("setlocale"); } puts("Success!..."); puts(strerror(EPERM)); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } > Hatırlatıcı Notlar: >> Aslında sistem fonksiyonları, "errno" değişkeninin değerini değiştirmezler. Negatif değer ile dönen sistem fonksiyonları, hata kodunun kendisinin negatif değerini geri döndürürler. Onu çağıran kod ise bu negatif değeri pozitif hale getirip, "errno" değişkenine atarlar. >> Hiç bir POSIX fonksiyonu "errno" değerini "0" değerine ÇEKMEZ. >> Bazı POSIX fonksiyonları, başarılı olmalarına rağmen, "errno" değişkeninin değerini DEĞİŞTİRİRLER. >> "errno" değişkeninin aldığı değerlerin açıklaması: https://man7.org/linux/man-pages/man3/errno.3.html >> C standartlarınca "errno" değişkeni çok kısıtlı alanda kullanılmaktadır. Fakat POSIX standartlarınca, standart C fonksiyonları da birer POSIX fonksiyonu sayıldığı için, iş bu "errno" değerini bir hayli kullanmaktadır. Örneğin, "malloc" fonksiyonu POSIX standartlarınca hata durumunda "errno" değişkenini "set" eder fakat C standartlarınca "errno" değişkenini "set" etme zorunluluğu yoktur. Öte yandan "fopen" fonksiyonu da başarısız olduğunda POSIX standartları "errno" değişkenini uygun değere çekmektedir. * Örnek 1, Standart bir C fonksiyonunun başarısını, POSIX sistemlerinde, aşağıdaki gibi sorgulayabiliriz. //... int main() { //... if((p = malloc(SIZE)) == NULL) exit_sys("malloc); } //... Tabii bizler, Standart C uyumunu korumak için, ilgili fonksiyonların geri dönüş değerini "errno" üzerinden sorgulamamalıyız. * Örnek 1, Standart bir C fonksiyonunun başarısını aşağıdaki şekilde sorgulamak daha uygundur. //... int main() { //... if((p = malloc(SIZE)) == NULL) { fprintf(stderr, "cannot allocate memory!...\n); exit(EXIT_FAILURE); } } >> "errno" değişkeni aslında "libc" kütüphanesinin (standart C ve POSIX kütüphanesi) içerisinde tanımlanmış bir değişkendir. "Kernel" modda yani "kernel" ın içerisinde "errno" isimli bir değişken yoktur. >>> Bu nedenle kerneldaki fonksiyonlar POSIX fonksiyonları gibi başarısızlık durumunda "-1" ile geri dönüp "errno" değişkeninin "set" etmezler. "Kernel" içerisindeki fonksiyonlar başarısızlık durumunda negatif "errno" değeri ile geri dönerler. Örneğin, "open" POSIX fonksiyonu "sys_open" isimli "kernel" içerisinde bulunan sistem fonksiyonunu çağırdığında onun negatif bir değerle geri dönüp dönmediğine bakar. Eğer "sys_open" fonksiyonu negatif değerle geri dönerse bu durumda bu değerin pozitiflisini "errno" değişkenine yerleştirip "-1" ile geri dönmektedir. Başka bir deyişle aslında bizim çağırdığımız "int" geri dönüş değerine sahip POSIX fonksiyonları, sistem fonksiyonlarını çağırıp, o fonksiyonlar negatif bir değerle geri dönmüş ise bir hata oluştuğunu düşünerek o negatif değerin pozitiflisini "errno" değişkenine yerleştirip "-1" ile geri dönmektedir. "Kernel" modül yazan programcıların da bu geleneğe uyması iyi bir tekniktir. Şöyleki: if (some_control_failed) /* burada kontrol yapılıyor */ return -EXXXX; /* fonksiyon başarısız ise negatif errno değeriyle geri döndürülüyor */ Özetle biz "kernel" içerisindeki geri dönüş değeri "int" olan bir fonksiyonu çağırdığımızda onun başarılı olup olmadığını geri dönüş değerinin negatif olup olmadığı ile kontrol ederiz. Eğer çağırdığımız fonksiyonun geri dönüş değeri negatif ise onun pozitif hali başarısızlığa ilişkin "errno" numarasını vermektedir. >>> POSIX arayüzünde adrese geri dönen fonksiyonlar genel olarak başarısızlık durumunda "NULL" adrese geri dönmektedir. Oysa "kernel" kodlarında adrese geri dönen fonksiyonlar başarısız olduklarında yine sanki bir adresmiş gibi negatif "errno" değerine geri dönerler. Örneğin şöyle bir "kernel" fonksiyonu olsun: void *foo(void); Biz bu fonksiyonu kernel modülümüz içerisinde çağırdığımızda eğer fonksiyon başarısızsa negatif "errno" değerini bir adres gibi geri döndürmektedir. Negatif küçük değerlerin ikiye tümleyen aritmetiğinde başı birlerle dolu olan bir sayı olacağına dikkat ediniz. Örneğin, bu "foo" fonksiyonu "EPERM" değeri ile geri dönüyor olsun. "EPERM" değeri "1" dir. "64-bit" sistemdeki "-1" değeri ise şöyledir: FF FF FF FF FF FF FF FF Bu değer ise çok yüksek bir adres gibidir. O zaman eğer fonksiyon çok yüksek bir adres geri döndürdüyse başarısız olduğu sonucunu çıkartabiliriz. Tabi bu işlemler için makrolar bulundurulmuştur. Kernel kodlarındaki "ERR_PTR" isimli makro ya da "inline" fonksiyon, bir tamsayı değeri alıp onu adres türüne dönüştürmektedir. Bu nedenle adrese geri dönen fonksiyonlarda aşağıdaki gibi kodlar görebilirsiniz: void *foo(void) { ... if (expression) return ERR_PTR(-EXXXX); ... } "ERR_PTR" aşağıdaki gibi tanımlanmıştır: static inline void *ERR_PTR(long error) { return (void *) error; } Bu işlemin tersi de "PTR_ERR" makrosu ya da iinline fonksiyonu ile yapılmaktadır. Yani "PTR_ERR" bir adresi alıp onu tamsayıya dönüştürmektedir. Bu fonksiyon da şöyle tanımlanmıştır: static inline long PTR_ERR(const void *ptr) { return (long) ptr; } Pekiyi bir adres değerinin içerisinde "errno" hata kodunun olduğunu nasıl anlarız? İşte negatif "errno" değerleri bir adres gibi ele alındığında adeta adres alanın sonındaki adresler gibi bir görünümde olacaktır. "errno" değerleri için toplamda ayrılan sayılar da sınırlı olduğu için kontrol kolaylıkla yapılabilir. Ancak bu kontrol için "IS_ERR" isimli bir makro ya da inline fonksiyon da bulundurulmuştur: static inline long IS_ERR(const void *ptr) { return (unsigned long)ptr > (unsigned long)-4095; } Burada fonksiyon adresin adres alanının son "4095" adresinden biri içerisinde mi kontrolünü yapmaktadır. Negatif "errno" değerlerinin hepsi bu aralıktadır. Tabii "4095" errno değeri yoktur. Burada geleceğe uyumu korumak için "4095" lik bir alan ayrılmıştır. Bu durumda kernel kodlarında adrese geri dönen fonksiyonların başarısızlığı aşağıdaki gibi kontrol edilebilmektedir. Şöyleki: void *ptr; ptr = foo(); if (IS_ERR(ptr)) return PTR_ERR(ptr) Kernel modül programcılarının da buradaki konvansiyona uygun kod yazması iyi bir tekniktir. Linux çekirdeğindeki "EXXX" sembolik sabitleri POSIX arayüzündeki "EXXX" sabitleriyle aynı değerdedir.