İşler Ters Giderse: errno, ERR_PTR() …
130-003930
POSIX fonksiyonları başarılı iken 0, başarısız ise -1 e geri dönmektedir.
Fakat kernel içerisinde durum böyle değildir. Mesela errno POSIX kütüphanesi
tarafından oluşturulur ve kernel içerisinde yoktur. Biz bir modül içerisinde
export edilmiş bir kernel fonksiyonunu çağırınca onun başarı durumunu nasıl
anlayacağız?
Eğer bu fonksiyon int türden dönüş yapıyorsa başarılı ise 0 ya da pozitif
bir değer dönüyor, başarısız dönüşü göstermek istiyorsa negatif değer dönüyor.
Diyelim bir fonksiyon EACCESS hatası dönüyor ve bunun #define değeri 17
olsun. Bu durumda fonksiyon -17 değeri ile geri dönecektir. Yani kernelde
aslında kavram olarak errno var ama errno diye değişken yok.
Eğer fonksiyon void *foo(); gibi bir fonksiyon ise fonksiyon bize bir adres
dönecektir. Diyelim ki 64-bit bir sistemdeyiz, bu durumda bu adres tipik olarak
8 byte olacaktır. Hata durumunda bu adreslere aslında errno değerleri konuluyor.
Yani adres dönüyor ama gerçek adres değil, aslında errno değeri bulunuyor, bir
adres gibi dönüyor. Peki nasıl ayırt edeceğiz biz bunu? Yani gerçek adres mi
yoksa hata kodu mu var? Bunun alanı belli ve bir nevi inte dönüştürerek
buna bakabiliyoruz. Bunun için makrolar ve inline fonksiyonlar vardır.
Özetle kernel içerisinde hata kodları fonksiyon geri dönüş değerine kodlanarak iletiliyor.
Sistem çağrısı yapan POSIX ya da kütüphane fonksiyonları da aslında böyle çalışıyor. Mesela:
4long __syscall_ret(unsigned long r)
5{
6 if (r > -4096UL) {
7 errno = -r;
8 return -1;
9 }
10 return r;
11}
Mesela burada temelde errno kernelden dönen değerin yani bir sistem çağrısı
fonksiyonunun döndüğü değerin negatif değerine atanmış. Dönen değer negatif
olduğu için pozitif değere dönüştürülmüş ve kullanıcıya -1 döndürülmüş.
Burada kernel içerisinde de EXXX şeklindeki #define değerler 1 den
başlamaktadır. Bunun negatifi dönüldüğü zaman aslında işaretsiz olarak en büyük
sayılar elde edilmektedir. Yani -1 dönmek aslında bitlerin 0xffffffff gibi
değerler alması demektir. -4096UL ile aslında 4096UL (unsigned long) negatif
değeri alınmıştır. Yani temelde kontrol edilen geçerli bir EXXX değeri
olup olmadığıdır. Burada 4096UL bir integer literal olup, - aslında bir
unary prefix operatördür.
Bnkz: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h
Yani errno değişkeni, libc kütüphanesi (standard C ve POSIX kütüphanesi)
içerisinde tanımlıdır.
Kernel modül yazanlar için şu şekilde kod yazılması uygundur.
if (işler ters gitti)
return -EXXX;
ERR_PTR()
POSIX arayüzünde adrese geri dönen fonksiyonlar başarısızlık durumunda genel
olarak NULL adrese dönmektedir. Oysa kernel kodlarında adrese geri dönen
fonksiyonlar başarısızlık olduklarında yine sanki bir adresmiş gibi negatif
errno değerine dönerler. Bu dönüşümü ERR_PTR isimli inline fonksiyon ile
yapabiliriz.
void *foo(void)
{
//...
if(isler_ters_gitti)
return ERR_PTR(-EXXX);
}
Fonksiyon şu şekildedir:
30/**
31 * ERR_PTR - Create an error pointer.
32 * @error: A negative error code.
33 *
34 * Encodes @error into a pointer value. Users should consider the result
35 * opaque and not assume anything about how the error is encoded.
36 *
37 * Return: A pointer with @error encoded within its value.
38 */
39static inline void * __must_check ERR_PTR(long error)
40{
41 return (void *) error;
42}
Kernel içerisinde örnek bir kullanıma da bakalım:
1363/**
1364 * file_open_name - open file and return file pointer
1365 *
1366 * @name: struct filename containing path to open
1367 * @flags: open flags as per the open(2) second argument
1368 * @mode: mode for the new file if O_CREAT is set, else ignored
1369 *
1370 * This is the helper to open a file from kernelspace if you really
1371 * have to. But in generally you should not do this, so please move
1372 * along, nothing to see here..
1373 */
1374struct file *file_open_name(struct filename *name, int flags, umode_t mode)
1375{
1376 struct open_flags op;
1377 struct open_how how = build_open_how(flags, mode);
1378 int err = build_open_flags(&how, &op);
1379 if (err)
1380 return ERR_PTR(err);
1381 return do_filp_open(AT_FDCWD, name, &op);
1382}
Mesela burada da struct file* türünden dönüşü olan fonksiyon hata durumunda
bu macro ile dönüş yapmaktadır.
PTR_ERR()
Bir de PTR_ERR() inline fonksiyon vardır. O da böyle bir pointer dönüşü alınca
içerisinden hata kodu elde etmeye yarar.
50/**
51 * PTR_ERR - Extract the error code from an error pointer.
52 * @ptr: An error pointer.
53 * Return: The error code within @ptr.
54 */
55static inline long __must_check PTR_ERR(__force const void *ptr)
56{
57 return (long) ptr;
58}
Örnek kullanım:
1058/**
1059 * finish_no_open - finish ->atomic_open() without opening the file
1060 *
1061 * @file: file pointer
1062 * @dentry: dentry, ERR_PTR(-E...) or NULL (as returned from ->lookup())
1063 *
1064 * This can be used to set the result of a lookup in ->atomic_open().
1065 *
1066 * NB: unlike finish_open() this function does consume the dentry reference and
1067 * the caller need not dput() it.
1068 *
1069 * Returns 0 or -E..., which must be the return value of ->atomic_open() after
1070 * having called this function.
1071 */
1072int finish_no_open(struct file *file, struct dentry *dentry)
1073{
1074 if (IS_ERR(dentry))
1075 return PTR_ERR(dentry);
1076 file->__f_path.dentry = dentry;
1077 return 0;
1078}
1079EXPORT_SYMBOL(finish_no_open);
Not
C dilinde sayı türleri ve pointerlar arsındaki bu tarz dönüşümler implementation defined olarak tanımlanır ama kernel gibi donanımın dibinde çalışan yazılımlarda sıkça kullanılırlar. Portable, taşınabilir, olmadığı için aslında mümkün olan her durumda varsa o konu için hazırlanmış kernel macrosu kullanılmalıdır.
IS_ERR()
IS_ERR() inline fonksiyonu da elde ettiğimiz pointerın bir error kodu mu
olduğunu, yani return ERR_PTR() ile oluşturulup oluşturulmadığını kontrol
eder.
63/**
64 * IS_ERR - Detect an error pointer.
65 * @ptr: The pointer to check.
66 * Return: true if @ptr is an error pointer, false otherwise.
67 */
68static inline bool __must_check IS_ERR(__force const void *ptr)
69{
70 return IS_ERR_VALUE((unsigned long)ptr);
71}
Örnek kullanım:
1058/**
1059 * finish_no_open - finish ->atomic_open() without opening the file
1060 *
1061 * @file: file pointer
1062 * @dentry: dentry, ERR_PTR(-E...) or NULL (as returned from ->lookup())
1063 *
1064 * This can be used to set the result of a lookup in ->atomic_open().
1065 *
1066 * NB: unlike finish_open() this function does consume the dentry reference and
1067 * the caller need not dput() it.
1068 *
1069 * Returns 0 or -E..., which must be the return value of ->atomic_open() after
1070 * having called this function.
1071 */
1072int finish_no_open(struct file *file, struct dentry *dentry)
1073{
1074 if (IS_ERR(dentry))
1075 return PTR_ERR(dentry);
1076 file->__f_path.dentry = dentry;
1077 return 0;
1078}
1079EXPORT_SYMBOL(finish_no_open);
Tüyo
Hatırlatma: Linux kernelindeki EXXX değerleri ile POSIX EXXX ler aynı
değerdedir.
Belki aklımıza şöyle bir soru takılabilir: Bunlar neden macro değil de
fonksiyon? Normalde böyle operasyonların macro ile yapılmasını bekleriz. Ama
burada bir iki sebep olabilir: Birincisi fonksiyon ile tür kontrolü
yapılabilmektedir. Örneğin PTR_ERR() ye argüman olarak pointer geçmemiz
gerektiğini derleyici de kontrol edebilir. İkincisi fonksiyonlar atanan
__must_check gibi, aslında __warn_unused_result__, derleyici ya linter
spesifik __force gibi [1] özelliklerin makrolarda çalışmama ihtimali
yüksektir. Fonksiyon zaten static inline olduğu için yüksek ihtimalle makro
ile aynı performansı gösterecektir.
Yapılacaklar
Bu kısmı topluluğa sorabilirsin.