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型ポインタにキャストして返します。

ERR_PTR()の使用例として、memdup_user()を示します。本関数は、User空間のメモリ内容をKernel空間にコピーします。処理途中にあるkmalloc_track_caller()(Kernel版malloc)のエラー処理において、ERR_PTR()を使用しています。引数は、メモリ不足によるエラーを示すENOMEMで、ERR_PTR()にエラー番号を符号反転させて渡す事がポイント(お作法)です。符号反転する理由は、後述のIS_ERR()で分かります(説明します)。

なお、copy_from_user()で発生しているエラーは、アドレス不正です。

       

IS_ERR()の実装

IS_ERR()は、引数で渡されたポインタの実体が、有効なエラー番号(1〜4095)であれば真を返し、異なる場合は偽を返します。使用例は、PTR_ERR()の使用例と一緒に示します。

上記の実装中で登場する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マクロを用います。

                             

PTR_ERR()の実装および使用例(IS_ERR()の例含む)

PTR_ERR()は、引数で渡されたポインタをlong型にキャストして返します。引数ptrは、IS_ERR()で検査されている事が前提のため、long型で-4095〜-1を返す事を想定しています。

最後に、PTR_ERR()、IS_ERR()の使用例として、キャラクタデバイス初期化処理を示します。使用例では、デバイスをクラス登録する関数(class_create())にて、エラー処理しています。class_create()が返したポインタをIS_ERR()で検査し、エラー番号が返ってきた場合はPTR_ERR()でエラー番号を取得します。内容が理解できていれば、簡単(テンプレート)な処理だと思います。

                             

あわせて読みたい

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です