🐧 Linux 総合学習プラットフォーム
デバイスドライバ実装 ・ 上級

ユーザ空間とのデータ受け渡し(copy_to_user / copy_from_user)

ドライバの read/write に渡されるバッファはユーザ空間のアドレスで、カーネルから直接読み書きしてはいけない。1バイトなら put_user(カーネル→ユーザ)と get_user(ユーザ→カーネル)、複数バイトなら copy_to_user と copy_from_user で安全にコピーする。これらはコピーできなかった残りバイト数を返し、0 なら全て成功。この境界の作法がドライバの安全性を支える。

登録が済み、アプリの read や write が自分のハンドラに届くようになった。ハンドラには、データを受け渡すためのバッファのアドレスが渡ってくる。

では、そのアドレスをドライバから直接読み書きすれば済むのか。ここに、カーネル空間で動くドライバならではの落とし穴がある。

💡
ポイントread/write に渡るバッファのアドレスはユーザ空間のもの。カーネルから直接触ってはいけない。

read や write のハンドラに渡されるバッファは、呼び出したアプリ側、つまりユーザ空間のアドレスだ。ドライバはカーネル空間で動いているため、このユーザ空間のアドレスをそのまま自分で読み書きするのは危険な行為になる。

なぜ危険なのか。そのアドレスが本当に有効か、アプリがそこにアクセスする権利を持っているかをドライバが確かめないまま触ると、無効なアドレスを踏んでカーネルごと巻き込んだ不具合を起こしかねない。悪意あるアプリに、触れてはいけない場所を触らされる隙にもなる。だからこの境界には、専用の安全な受け渡しの作法が用意されている。

🪙 1バイトを渡す — put_user と get_user

まず、1バイトや1つの値といった小さなデータをやり取りする場合から見よう。

カーネルからユーザ空間へ値を1つ渡すときは put_user を、ユーザ空間からカーネルへ値を1つ受け取るときは get_user を使う。名前の user は「ユーザ空間との受け渡し」を表していて、方向が put(送る)か get(受け取る)かで使い分ける。

put_user(read_data, buf) でカーネルの値をユーザ空間へ、get_user(led_value, buf) でユーザ空間の値をカーネルへ。

たとえば、スイッチの状態を表す1バイトをアプリに返す read ハンドラでは put_user を使い、アプリが書き込んだLEDへの指示1バイトを受け取る write ハンドラでは get_user を使う、といった具合だ。

これらは、渡されたユーザ空間のアドレスが安全に扱えるかをカーネルの仕組みが確認したうえでコピーしてくれるので、ドライバはアドレスを直接踏まずに済む。少量のデータなら、この2つで十分こと足りる。

📦 まとまったデータを渡す — copy_to_user と copy_from_user

扱うデータが1バイトで収まらず、複数バイトのまとまりになったらどうするか。

そのときは copy_to_user と copy_from_user を使う。copy_to_user はカーネル空間からユーザ空間へ、copy_from_user はユーザ空間からカーネル空間へ、指定したバイト数ぶんのデータをまとめて安全にコピーする関数だ。

copy_to_user(ユーザ側, カーネル側, n) で n バイト返し、copy_from_user(カーネル側, ユーザ側, n) で n バイト受け取る。

引数は、コピー先のアドレス、コピー元のアドレス、コピーするバイト数の順で渡す。方向によってどちらがユーザ空間側かが入れ替わるので、コピー先とコピー元を取り違えないよう注意がいる。

たとえば複数のスイッチの状態をまとめて返すなら copy_to_user を、複数のLEDへの指示をまとめて受け取るなら copy_from_user を使う。put_user / get_user の「まとめ版」だと捉えると分かりやすい。

🔢 戻り値は「コピーできなかった残り」

これらのコピー関数の戻り値には、独特の約束がある。ここを誤解すると、成功判定を逆に書いてしまう。

copy_to_user と copy_from_user が返すのは、コピーに成功したバイト数ではなく、コピーできなかった残りのバイト数だ。

つまずきコピー関数の戻り値は「失敗した残りバイト数」。0 なら全て成功、0 でなければ一部または全部が失敗している。

したがって、戻り値が 0 であれば「残りゼロ=すべて成功」を意味し、0 でなければ、その数だけコピーできなかった、つまり何らかの失敗があったことになる。

なぜ成功数ではなく失敗数を返すのか。この形にしておくと、「全部成功したか」を戻り値が 0 かどうかだけで判定でき、エラー処理を書きやすいからだ、と捉えておけばよい。read ハンドラでは、実際にアプリへ渡せたバイト数(要求から失敗ぶんを引いた数)を返す、といった使い方につながる。

🧭 どちらを使うか、迷わないために

put_user / get_user と copy_to_user / copy_from_user、4つが出てきた。使い分けの軸は2つだけだ。

1つめの軸は「量」。1バイトや値1つなら put_user / get_user、複数バイトのまとまりなら copy_to_user / copy_from_user を選ぶ。

2つめの軸は「向き」。カーネルからユーザ空間へ送る(read でアプリに返す)なら put_user / copy_to_user、ユーザ空間からカーネルへ受け取る(write でアプリから受ける)なら get_user / copy_from_user を使う。

コツ「量(1つか複数か)」と「向き(返すか受けるか)」の2軸で選べば、4つのどれを使うかは自然に決まる。

read ハンドラは基本的に「返す」側なので to_user 系、write ハンドラは「受ける」側なので from_user 系、と対応づけて覚えると混乱しにくい。

✅ まとめ — 境界を守る作法

ドライバとアプリのあいだには、カーネル空間とユーザ空間という越えてはいけない境界がある。read/write のバッファはその向こう側にあり、直接触れば事故につながる。

だからこそ、1バイトなら put_user / get_user、まとまりなら copy_to_user / copy_from_user という専用の関数を通し、戻り値の 0 で成否を確かめる。この作法を守ることが、暴走せず安全に動くキャラクタ型ドライバの土台になる。

file_operations で窓口を決め、cdev で登録し、この受け渡しの作法でデータを橋渡しする。ここまでがそろって、はじめて「アプリからファイル操作で機器を動かす」自作ドライバが完成する。

この項目に出てくる用語

copy_to_user / copy_from_userこぴーとぅーゆーざーこぴーふろむゆーざー
ユーザ空間との間で複数バイトを安全にコピーする関数。
ユーザ空間ポインタゆーざくうかんぽいんた
read/write ハンドラに渡る、アプリ側のアドレス。直接触れない。

▶ 学習アプリでこの続きを学ぶ・演習する