KDOC 293: 『超例解Linuxカーネルプログラミング』
この文書のステータス
- 作成
- 2024-11-24 貴島
- レビュー
- 2024-11-27 貴島
概要
『超例解Linuxカーネルプログラミング』は、カーネル開発を解説する本。
メモ
- 1章: Linuxカーネルの開発フロー
- LWN.netは、Linux開発のニュースサイト(p29)
- Linuxカーネルは現場のプロによる修正が多く入っている。プロの修正が無償で見られることは、じつはすごいこと(p29)
- 2章: ソフトウェアの品質とライセンス
- stableブランチに入れるソースコードに関して、ルールが定められている(p45)
- ただしくテストされていなければならない
- 1つのコンテキストにおいて、修正量が100行を超えてはならない
- たったひとつのバグが修正されなければならない
- ユーザを悩ましている本当のバグを修正しなければならない。憶測でバグ修正をしてはいけない
- クリティカルな問題が修正されなければならない。、つまりビルドエラー、カーネルパニック、ハングアップ、データ破損、セキュリティ問題など
- つまらない修正は含めてはならない。スペルミスの修正や空白の調整など
- Linuxのデバイスドライバは静的型と動的型に分類できる。静的型は直接カーネルにリンクされており、起動時にデバイスドライバも起動するもの。動的型はカーネルの動作中にデバイスドライバのモジュールを組み込んだり取り外せる(p56)
- rodataセクションは、const宣言された定数や文字列定数が配置される領域。rodataセクションに配置されたデータは静的なデータであり、動的に確保されたカーネルメモリではないので、メモリを確保する操作も不要である(p60)
- 実際の修正例をもとに、起こしがちなバグを解説している
- ワークキューというカーネルの仕組みを利用すると、指定した関数を遅延実行させられる(p62)
- 割り込みハンドラは登録したあとはいつ呼び出されるかわからない。登録する前に必要な初期設定をしておかないといけない(p80)
- ネットワークインターフェースが有効かどうかは、ipコマンドで見たときに「stateがUP」であるという意味。以前はifconfigが使われていたが、現在では非推奨となっている、という(p85)
- ネットワークカードから発生する割り込みを禁止する処理は、ネットワークカードのレジスタにゼロを書き込んでいる(p86)
- ポーリングモードの欠点はパケットの受信処理が遅くなること。割り込みモードとポーリングモードのどちらを選択するかは、カーネルへの負荷とネットワーク性能のトレードオフとなる
- SystemTapという仕組みを利用するのが手軽。SystemTap専用のスクリプト言語を使って簡単なコードを書くことで、自動的にカーネルコードが生成され、動作中のカーネルに組み込める(p89)
apt install systemtap
- 手順: p90
- ネットワークインターフェイスをUPからダウンに変更中にネットワークから割り込みがきた場合が問題であるコード例(p95)
- ネットワークのパケットはいつ外部から入ってくるかわからないので、完全にネットワークドライバが停止していない限り、割り込みハンドラが呼び出されてしまう。割り込みはネットワークカードというハードウェアから上がってくるものなので、ソフトウェアで割り込み禁止にしない限り、延々と割り込みがくる。ハードウェアから見るといつまでも割り込みが処理されていないように見える。そして割り込みが無限に上がってくるとCPU1つを使い続けることになり、負荷が上がる(p95)
- ポインタはメモリ空間を指し示すアドレスなので、メモリ空間が4GBなのであれば、0から(4G-1)の数値を表現できる必要がある。32bitだとポインタのサイズが4バイト
# 32bitに格納できる整数の数をGBに直す 2**32 / 1000 / 1000 / 1000.0
4.294
- 5章: 32bit/64bitに関する落とし穴
- カーネル関数の
printk()
は、C言語のprintf()
に似せて作られているという。従来、printk()
の「%p」はポインタのアドレスを表示していたが、セキュリュティ上があるということで、でたらめな値が表示されるように実装が変更された。ポインタのアドレスをハッシュ化して、元の値がいくつかをわからなくする。「%px」では本当のポインタアドレスを表示する(p101) - Kprobesは、Linuxカーネルの動作中に任意の箇所にブレークポイントを仕掛けられ、そのブレークポイントにデバッグ用コードを設定できる仕組みのこと。SystemTapはKprovesの仕組みを利用したツールである(p103)
- Kprovesではデバッグしたい関数にブレークを設定するには関数のアドレス、つまり関数ポインタのアドレスが必要である(p104)
- ドライバをビルドして、
insmod
でドライブを組み込む。不要になったドライバはrmmod
で取り外せる(p106)- ドライバをロードしたことが
/var/log/syslog
にログ保存される
- ドライバをロードしたことが
- 6章: 処理終了の待ち合わせ
- I2Cとはシリアルバスのこと。組み込み分野ではよく用いられる
- DNA(Direct Memory Access)。I/OデバイスがCPUを介さずに直接メモリにアクセスできる(p116)
- Linuxカーネルのコンプリージョン機能。コンプリージョンを使うと処理完了まで待ち合わせできる。具体的にはユーザープロセスの状態を「割り込み不可なスリープ状態」にして通知を受けたら起動する。待ち合わせ中はユーザープロセスにSIGKILLを送ってもプロセスを強制終了できない(p118)
- ハードウェア割り込みは割り込みハンドラの登録解除しない限りいつ発生するかわからない。割り込みハンドラを登録解除してからメモリ解放しなければならない(p127)
- 7章: シンプルなミス
- マイクロコード: CPUの命令コードの塊のようなもので、CPUのバグ修正をソフトウェアレベルで行える仕組み。CPUのメーカーからベンダーに提供され、BIOSやOSに組み込まれる(p146)
- 8章: セキュリティ
- CPUの投機的実行を悪用した脆弱性。投機的実行はプログラムを先読みして実行していく仕組み。同時に実行することで範囲外アクセスが可能になったりする。1つのプロセスで読み込まれたデータはCPUのキャッシュに載るが、そのキャッシュデータを別のプロセスから読み出すことで本来読まれてはならないデータを読み出せるというもの(p153)
- CPU脆弱性問題
- スペクターv1
- スペクターv2
- メルトダウン
- Linuxの端末実装は3つのレイヤーで構成される。最上位はキャラクタデバイスのインターフェースを提供し、中間層がldisc、最下位層はハードウェアおよび疑似端末と通信するドライバになる(p155)
- kfreeなどでメモリが解放されてもメモリの内容はそのままになっている。なので、情報漏えいしたらまずい内容をメモリに乗せる場合はメモリを解放する前にメモリをゼロクリアするのが定石である(p156)
- C言語によるプログラミングでは、文字列を表現するために終端にヌルが必要である。
char buf[128]
という配列は大きさが128バイトであるから、最大127文字しか格納できない。なので文字列を127文字で切って、終端にヌルを入れるのが一般的である、という(p165) - 無条件にヌルを書き込む方式はセキュリティ的によくない。ユーザーが意図した文字列が意図せずに切り詰められ、別の文字列として実行される可能性がある(p165)
- shebang行が127文字で切り詰められることを前提にしたコードが使われている部分があり、後にrevertされた(p166)
/proc/kallsyms
を参照してLinuxカーネルの全シンボル情報がわかる(p172)- inotifyはファイルシステムの一種として実装されている(p172)
- intは-2147483648~2147483647まで扱える。argに2147483648以上の値を渡すと、以下のif文の条件が負になる。intにキャストして負数になるから(p182)
if (((int)arg >= cdi->capacity))
- チェックをすり抜けたargは以降の処理で使われる。配列アクセスなどしていると、バッファオーバーフローできてしまう
- メモリのゼロクリア処理は、コンパイラの最適化で削除される可能性がある。削除されると困る場合は明示的にゼロクリアする関数を使う(p196)
- Linuxカーネルにはガベージコレクタの仕組みがない(p196)
- カーネル開発においては、関数の早期リターンは避けたほうがよい。メモリの解放漏れなどのバグを作り込むことになるから。代わりにgotoで関数の末尾に呼び、最後にまとめてリソース解放するとよい(p196)
関連
なし。