POSIX Fonksiyonlarında Başarısızlık Nedeninin Tespit
Transkript
POSIX Fonksiyonlarında Başarısızlık Nedeninin Tespit
POSIX Fonksiyonlarında Başarısızlık Nedeninin Tespit Edilmesi Kaan Aslan 12 Mayıs 2006 POSIX sistem fonksiyonlarının çok büyük çoğunluğu başarı durumunda 0 değerine başarısızlık durumunda ise –1 değerine geri dönmektedir. Fonksiyonlar başarısızlık durumunda başarısızlığın nedenini anlatan bir sayısal değeri errno isimli global bir değişkene yerleştirirler. Yani biz başarısızlığı tespit ettikten sonra bunun nedenini errno değişkeni içerisindeki değere bakarak tespit edebiliriz. errno değişkeninin extern bildirimi <errno.h> başlık dosyası içerisindedir. Bu başlık dosyasında ayrıca errno değişkeninin alabileceği hata değerleri E öneki ile başlayan sembolik sabitler biçiminde define edilmiştir. Hatanın nedenini anlamak için bu sembolik sabitleri kullanabilirsiniz. Aşağıda Linux sistemlerindeki <errno.h> dosyasının bu sembolik sabitlere ilişkin bir kısmını görüyorsunuz: #define #define #define #define #define #define #define #define #define #define #define #define #define EPERM ENOENT ESRCH EINTR EIO ENXIO E2BIG ENOEXEC EBADF ECHILD EAGAIN ENOMEM EACCES 1 2 3 4 5 6 7 8 9 10 11 12 13 /* /* /* /* /* /* /* /* /* /* /* /* /* Operation not permitted */ No such file or directory */ No such process */ Interrupted system call */ I/O error */ No such device or address */ Arg list too long */ Exec format error */ Bad file number */ No child processes */ Try again */ Out of memory */ Permission denied */ Burada önemli bir noktayı belirtmek istiyoruz. POSIX standartlarında errno global değişkeninin içerisindeki değerlerin sayısal karşılıklarının ne olacağı hakkında bir belirlemede bulunulmamıştır. Yalnızca hata nedenlerini anlatan sembolik sabitlerin neler olacağı ve bu sembolik sabitlerin hangi hataları belirttiği açıklanmıştır. Bu durumda örneğin her POSIX sisteminde <errno.h> başlık dosyası içerisinde EACCESS isimli sembolik sabit tanımlıdır, fakat bu sembolik sabit her sistemde başka başka değerlere define edilmiş olabilir. O halde kodumuzun taşınabilirliğini bozmamak için o sembolik sabitleri tercih etmeliyiz. Örneğin: #include <errno.h> ... herhangi_bir_posix_fonksiyonu(); if (errno == EACCESS) { .... } Linux sistemlerinde EACCESS değeri 13 olduğu halde başka sistemlerde farklı olabilir. 1 Kaan Aslan Makale Arşivi – www.kaanaslan.net errno standartlara göre int türden bir nesne belirtmek zorundadır. Bu durumda errno int türden bir değişken olabileceği gibi, bir makro da olabilir. Fakat bizim errno isimli ifadeye bir atama yapabiliyor olmamız gerekir. Örneğin POSIX standartlarına göre her thread’in ayrı bir errno değeri olmak zorundadır. Bunun için errno değişkeninin thread’e özgü alanda yaratılması ve thread specific data olarak o alandan çekilmesi gerekir. Bu da ancak fonksiyon çağrısı ile yapılabilir. Örneğin: #define errno (*(__errno_location())) errno değişkenine ancak POSIX fonksiyonu başarısız olduğunda bakılmalıdır. Son çağrılan fonksiyonun başarılı olması durumunda errno değişkeni 0 gibi özel bir değere çekilmek zorunda değildir.[1] POSIX standartlarında her fonksiyon için o fonksiyonun başarısızlık durumunda errno değişkenini hangi hata değeriyle dolduracağı tek tek açıklanmıştır. Hiçbir fonksiyon errno değişkenine POSIX standartlarında belirtilen hata değerlerinden başka bir değer yerleştirmez. Yani fonksiyonların hangi nedenlerle başarısız olabileceği önceden bilinebilmektedir.[2] Bir POSIX fonksiyonunda oluşan hatanın nedenini yazdıracak olalım. Bu bir switch içerisinde yapılabilir: char *msg; if ((fd = open("test.dat", O_RDONLY)) < 0) { switch (errno) { case EACCES: msg = "Permission denied"; break, case ENOENT: msg = "No such file or directory"; break; case EINVAL: msg = "Invalid argument"; break; ... } fprintf(stderr, "%s\n", pMsg); } Böyle bir işlemin oldukça zahmetli olduğunu söylemeye gerek var mı, bilemiyoruz. Hata mesajlarının yazdırılmasını kolaylaştırmak için birkaç standard POSIX fonksiyonundan faydalanılabilir. perror isimli fonksiyon errno global değişkeninin içerisindeki değere bakarak hata değerine ilişkin bir yazıyı stderr dosyasına (yani varsayılan durumda ekrana) yazdırmaktadır. Prototipi aşağıdaki gibidir: void perror(const char *s); perror aslında prototipi <stdio.h> içerisinde olan standart bir C fonksiyonudur. perror fonksiyonu önce parametresiyle belirtilen yazıyı, sonra bir : karakterini sonra bir tane boşluk karakterini ve sonra da errno değişkeninin içerisindeki değere ilişkin yazıyı yazdırır. Örneğin: 2 Kaan Aslan Makale Arşivi – www.kaanaslan.net if ((fd = open(("test.dat", O_RDONLY)) < 0) { perror("open"); exit(EXIT_FAILURE); } open fonksiyonunun -1 ile geri döndüğünü ve errno değişkenine ENOENT değerinin yerleştirildiğini varsayalım. Bu durumda stderr dosyasına şunlar yazdırılacaktır: open: No such file or directory perror fonksiyonu mesajları İngilizce yazdırmaktadır. Mesajların başka bir dilde (örneğin Türkçe) yazdırılmasının standart ve kolay bir yolu yoktur. Komut satırında çalıştırdığınız komutların da hata mesajlarını perror fonksiyonuyla yazdırdığına tanık olmuşsunuzdur. Örneğin: errno değerine karşılık gelen hata yazısını doğrudan değil de işleme sokarak yazdırmak isteyebilirsiniz. Bunun için öncelikle bu yazıyı elde etmeniz gerekebilir. strerror fonksiyonu bu işi yapmaktadır: char *strerror(int errnum); fonksiyon hata kodunu parametre olarak alır ve hata yazısının yerleştirildiği static alanın adresi ile geri döner. Bu fonksiyonun thread güvenli biçimi de vardır: int strerror_r(int errnum, char *strerrbuf, size_t buflen); Bazı POSIX fonksiyonlarında başarısızlık durumu geri dönüş değeri yoluyla belirlenememektedir. Örneğin getpwent fonksiyonu hem parola dosyasının sonuna gelindiğinde hem de okuma hatası olduğunda (tabi çok seyrek ortaya çıkabilir) NULL adresle geri döner. Parola dosyasınının sonuna gelinmiş olması normal bir durumdur. İşte bu tür durumlarda fonksiyonu çağırmadan önce errno değişkenine 0 yerleştirip çıkışta errno değerine bakıp başarısızlığı belirleyebiliriz: struct passwd ent; ... errno = 0; while ((ent = getpwent()) { ... } if (errno != 0) { perror("getpwent"); exit(EXIT_FAILURE); } Bazı POSIX fonksiyonları ise errno değişkenine değer atamaz. Başarısızlık nedenine ilişkin hata koduyla geri dönerler. Örneğin thread fonksiyonları böyledir: 3 Kaan Aslan Makale Arşivi – www.kaanaslan.net result = pthread_create(...); if (result) { fprintf(stderr, "%s\n", strerror(result)); exit(EXIT_FAILURE); } Bir POSIX fonksiyonu çalıştırıldığında birden fazla hata ile karşılaşılıyor olabilir. Bu durumda fonksiyonun hangi hata değeri ile geri döneceği ya da errno değişkenine yerleştireceği konusunda bir belirlemede bulunulmamıştır. Bu değerlerden herhangi biri olabilir. [1] Yukarıda da belirtildiği gibi sistem fonksiyonlarının çoğu başarısızlık durumunda -1 özel değerine geri dönmektedir. Fakat bazı programcılar sistem fonksiyonunun başarısızlığını –1 değeriyle karşılaştırma yaparak değil, < 0 biçiminde sorgularlare. Örneğin: if (stat(...) < 0) { ... } Mikro düzeyde bakıldığında –1 le karşılaştırma yerine < 0 karşılaştırması daha etkin bir makine kodu üretilmesine yol açacağı söylenebilir. Tabii böylesi olası bir kazancın programınız için ciddi bir anlamı yoktur. [2] Windows sistemlerinde son çağrılan API fonksiyonunda oluşan hata değeri GetLastError fonksiyonuyla elde edilebilir. Fakat bu sistemlerde API fonksiyonlarının hangi nedenlerle başarısız olacağı Microsoft tarafından tam olarak belgelenmemiştir. API fonksiyonlarının birbirlerini çağırdığı karmaşık içerisinde böyle bir belirlemenin zor olması bunun önemli bir nedenidir. 4 Kaan Aslan Makale Arşivi – www.kaanaslan.net