İş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.