Linux Kernel: NULLポインタエラーハンドリング(ERR_PTR, IS_ERR, PTR_ERR)
“返り値がNULL” = “情報量がない”
C言語には、返り値としてポインタを返す関数があります。
User空間の関数で例を挙げれば、メモリを確保するmalloc()、ファイルを開くfopen()などです。これらの関数は、エラー時にNULLを返します。
しかし、情報がNULLだけでは、どのような原因でエラーが発生したかが分かりません。この問題への対処として、User空間のシステムコールやライブラリ関数(一部)などは、変数errnoに補足情報を付与しています(errnoについては、以下の記事に詳細を記載しました)。
Linux Kernel: エラー番号の一覧
変数errnoは、User空間 <-> Kernel空間で情報をやり取りするための仕組みです。しかし、変数errnoはUser空間に存在するグローバル変数で、User空間の標準ライブラリが制御しています。つまり、Kernelが変数errnoを操作する事は、適切ではありません。
そのため、Kernel空間のNULLポインタエラーハンドリングでは、別の仕組みを用います。
具体的には、エラー時にNULLポインタを返さず、エラー番号をvoid型ポインタにキャストして返します。この方法では、返り値がポインタアドレスかエラー番号かを判断する仕組みが必要です。この仕組みを実現するために、下表に存在する関数を用います。
本記事では、これら関数の実装および使い方を説明します。
関数名 | 使用タイミング | 説明 |
ERR_PTR | エラー時にポインタを返す時 | NULLポインタの代わりに、エラー番号(errno)をポインタとして返します。 |
IS_ERR | 返り値(ポインタ)がエラーかどうかを判断する時 | 返り値(ポインタ)が有効なエラー番号を符号反転した値(-4095〜-1)であれば、真となります。 |
PTR_ERR | 返り値(ポインタ)のエラー原因を取得する時 | 返り値(ポインタ)から符号反転したエラー番号を取得します。 |
ERR_PTR()の実装および使用例
ERR_PTR()は、引数で渡されたエラー番号をvoid型ポインタにキャストして返します。
1 2 3 4 |
static inline void * __must_check ERR_PTR(long error) { return (void *) error; } |
ERR_PTR()の使用例として、memdup_user()を示します。
本関数は、User空間のメモリ内容をKernel空間にコピーします。処理途中にあるkmalloc_track_caller()(Kernel版malloc)のエラー処理において、ERR_PTR()を使用しています。
引数は、メモリ不足によるエラーを示すENOMEMで、ERR_PTR()にエラー番号を符号反転させて渡す事がポイント(お作法)です。符号反転する理由は、後述のIS_ERR()で分かります(説明します)。
なお、copy_from_user()で発生しているエラーは、アドレス不正です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void *memdup_user(const void __user *src, size_t len) { void *p; /* * Always use GFP_KERNEL, since copy_from_user() can sleep and * cause pagefault, which makes it pointless to use GFP_NOFS * or GFP_ATOMIC. */ p = kmalloc_track_caller(len, GFP_KERNEL); if (!p) return ERR_PTR(-ENOMEM); if (copy_from_user(p, src, len)) { kfree(p); return ERR_PTR(-EFAULT); } return p; } |
IS_ERR()の実装
IS_ERR()は、引数で渡されたポインタの実体が、有効なエラー番号(1〜4095)であれば真を返し、異なる場合は偽を返します。
使用例は、PTR_ERR()の使用例と一緒に示します。
1 2 3 4 5 6 |
static inline bool __must_check IS_ERR(__force const void *ptr) { return IS_ERR_VALUE((unsigned long)ptr); } #define MAX_ERRNO 4095 #define IS_ERR_VALUE(x) unlikely((unsigned long)(void *)(x) >= (unsigned long)-MAX_ERRNO) |
上記の実装中で登場するIS_ERR_VALUEマクロの判定式は、少しだけ直感的ではありません。
xが符号反転されたエラー番号と考えた場合、”x >= -4095 && x < 0″と実装すれば素直です。このような実装にした理由は、判定式による比較回数を1回に減らすためです。
unsigned long型にキャストした-MAX_ERRNO(= -4095)は、”0xfffff001″です。つまり、判定式は”x >= 0xfffff001″となります。
xがこの条件を満たす時(”0xfffff001″より大きい場合)は、xが-4095〜-1(反転したエラー番号)の時のみです。ポインタアドレスが-4095〜-1になる事はないため、エラー番号のみが正しく検出できます。
また、前述のERR_PTR()でエラー番号を符号反転させていない場合は、この判定式で意図した結果を得られません。この説明の正しさを確認するため、テストプログラム(User空間アプリ)とその実行結果を後述します。
なお、unlikelyマクロは、条件が偽である場合において、if文の実行速度を向上させる仕組みです。
基本的に、IS_ERR()で検査されるポインタは、有効なアドレスを持つポインタです。そのため、xが-4095〜-1の範囲外となり、unlikelyマクロの判定は偽となります。逆に、条件が真の場合の高速化には、likelyマクロを用います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include #define MAX_ERRNO 4095 #define IS_ERR_VALUE(x) ((unsigned long)(x) >= (unsigned long)-MAX_ERRNO) /* このテストコードは、以下の2点を確認します。 * 1) IS_ERR_VALUEマクロの動作 * ただし、IS_ERR_VALUEはKernel版と実装が少し異なります。 * 2) エラー番号を符号反転していない場合、 * IS_ERR_VALUEマクロの判定が期待値通りとならない事。 */ int main() { int i =0; /* エラー番号を符号反転していないケースも確認するため、 * ループは -4095〜4095 の範囲で行います*/ for(i=-MAX_ERRNO; i<=MAX_ERRNO; i++) { printf("i=%4d, Cast Value=0x%08x, ", i, (unsigned long)i); printf("%s\n", IS_ERR_VALUE(i) ? "ERRNO" : "Not ERRNO"); } return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ ./test i=-4095, Cast Value=0xfffff001, ERRNO i=-4094, Cast Value=0xfffff002, ERRNO i=-4093, Cast Value=0xfffff003, ERRNO 注釈:長いため、省略 i= -2, Cast Value=0xfffffffe, ERRNO i= -1, Cast Value=0xffffffff, ERRNO i= 0, Cast Value=0x00000000, Not ERRNO i= 1, Cast Value=0x00000001, Not ERRNO i= 2, Cast Value=0x00000002, Not ERRNO 注釈:長いため、省略 i=4094, Cast Value=0x00000ffe, Not ERRNO i=4095, Cast Value=0x00000fff, Not ERRNO |
PTR_ERR()の実装および使用例(IS_ERR()の例含む)
PTR_ERR()は、引数で渡されたポインタをlong型にキャストして返します。
引数ptrは、IS_ERR()で検査されている事が前提のため、long型で-4095〜-1を返す事を想定しています。
1 2 3 4 |
static inline long __must_check PTR_ERR(__force const void *ptr) { return (long) ptr; } |
最後に、PTR_ERR()、IS_ERR()の使用例として、キャラクタデバイス初期化処理を示します。
使用例では、デバイスをクラス登録する関数(class_create())にて、エラー処理しています。class_create()が返したポインタをIS_ERR()で検査し、エラー番号が返ってきた場合はPTR_ERR()でエラー番号を取得します。内容が理解できていれば、簡単(テンプレート)な処理だと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#define DEV_NAME "debimate" #define DEV_CLASS "debimate_class" #define MINOR_NR_BASE 0 #define MAX_MINOR_NR 1 static struct class *debimate_class; static struct cdev debimate_cdev; static dev_t dev; static int debimate_init(void) { int result = 0; struct device *debimate_dev = NULL; /* メジャー番号の動的確保 */ result = alloc_chrdev_region(&amp;dev, MINOR_NR_BASE, MAX_MINOR_NR, DEV_NAME); if (0 != result) { pr_err("%s: alloc_chrdev_region = %d\n", __func__, result); goto REGION_ERR; } /* デバイスをクラス登録 */ debimate_class = class_create(THIS_MODULE, DEV_CLASS); if(IS_ERR(debimate_class)){ result = PTR_ERR(debimate_class); pr_err("%s: class_create = %d\n", __func__, result); goto CREATE_CLASS_ERR; } /* 以下、処理を省略 */ |
ロシア人と国際結婚した地方エンジニア。
小学〜大学院、就職の全てが新潟。
大学の専攻は福祉工学だったのに、エンジニアとして就職。新卒入社した会社ではOS開発や半導体露光装置ソフトを開発。現在はサーバーサイドエンジニアとして修行中。HR/HM(メタル)とロシア妻が好き。サイトに関するお問い合わせやTwitterフォローは、お気軽にどうぞ。