🙌

初めてLinuxカーネルにパッチを送信した

に公開

大学院での研究がきっかけで、Linuxカーネルにパッチを送信しました。この記事では技術的な背景と私の貢献内容を説明します。

技術背景

この章では私の貢献内容に関連した技術的な背景を説明します。

Linuxカーネル(OS)

OSの役割をひと言でいうと「資源管理」です。たとえば

  • 計算資源:CPUの利用時間を複数のプロセスに平等に割り当てる
  • 記憶資源:プロセスに対してRAMを割り当てる / HDDにファイルを保存する
  • ネットワーク資源:プロセスから受け取ったデータをネットワークに送信する

私たちはこれらの資源管理をOSに任せることで、ストレージがHDDかSSDか意識せずにファイルを操作したり、TCP/IPやイーサネットを意識せずにインターネットを利用できたりしています。

近年では分散システムにおけるセキュリティやモニタリングなど、OSに対する機能追加の要求が多く発生します。しかしOSには安定性と安全性が求められるため、それ自体に新たな機能を追加するのは困難で時間がかかる作業です。そこでOS本体には手を入れずに、後からプラグインのような形で機能追加できるようにしたのがeBPFです。

eBPF(extended Berkley Packet Filter)

eBPFはLinuxカーネルの機能を安全に拡張できる仕組みです。「パケットフィルタ」とありますが歴史的にこの名前が用いられているだけで、現代ではフィルタとはかけ離れたものになっています。

OSの機能を拡張するには、いくつかの方法がありました。

  1. OS自体のソースコードにパッチをあてる
  2. カーネルモジュールを利用する

率直な方法は1ですが、Linuxのコードベースが膨大であることやデバッグに手間がかかることが問題です。2は長年採用されてきたプラグイン方式の方法ですが、モジュール自体にバグがあった場合、OS自体がフリーズしてしまう可能性があります。こうなってしまうと、マシンを強制再起動するしか復旧の方法はありません。

そこで提案されたのがeBPFです。eBPFではOS内部にサンドボックス環境を用意し、その中でプラグインプログラムを動かすことで、プログラム自体に問題があってもOSフリーズのような最悪の事態を防ぐことができます。このサンドボックス環境をどうやって確保するかが、eBPF技術の要になります。

検証器(Verifier)

eBPFではプラグインプログラムを事前に検証することで、その安全性を担保しています。ここでいう「安全性」とは以下のことです:

  • 不正なメモリアクセスをしないこと
  • プログラムが現実的な時間で終了すること

eBPFの検証器(Verifier)はプログラムを静的解析することで、これらのセキュリティ上の問題がないことを検証します。具体的には、全てのコードパスにおける変数値の範囲、関数呼び出しなどを追跡し確認します。検証器は、サンドボックス外のメモリにアクセスしようとしたり、無限ループの可能性があるコードを発見すると、そのプログラムのロードを拒否します。

検証器の仕組みは安全性を担保する上で強力ですが、プログラミングに制約を与えます。たとえば、for文がいくつもネストしたような複雑なプログラムを書くと、検証器が変数を追跡しきれず拒否されることがよくあります。この性質は文字列比較などの処理では大変不便です。

そこで、プログラムの一部を検証器の外に出してあげる仕組みがあります。

KFunc

KFuncはeBPFから呼び出せるカーネル関数です。eBPFの検証器は関数の引数の範囲や型について検証しますが、KFuncの中身までは検証しません。そこで、文字列比較系の関数をKFuncとして定義することで、検証器の制約を回避することができます。

Linuxカーネルでは、eBPFでよく使われる文字列比較系の関数を事前にKFuncとして定義してあります。関数の例は以下のとおりです:

  • bpf_strcmp:2つの文字列を比較する
  • bpf_strcasecmp:2つの文字列を比較する(大文字小文字を無視)
  • bpf_strstr:文字列の中からある文字列を検索する

これらの関数は、C標準ライブラリの<string.h>で定義されているものに似ていますが、文字列の長さに内部的な制限があったり、検証器が引数を検証できるようにアノテーションされていたりと、eBPF用に調整されています。

貢献内容

私は新しいKfuncとしてbpf_strncasecmpを追加しました。bpf_strcasecmpbpf_str'n'casecmpの挙動はほぼ同じですが、後者は与えられた文字列のうち最初のn文字だけを比較します。動作例は以下のとおりです:

  • bpf_strcasecmp("hello", "HELLO"):2つの文字列は等しい
  • bpf_strcasecmp("hello", "HELLO WORLD"):2つの文字列は異なる
  • bpf_strncasecmp("hello", "HELLO WORLD", 5)2つの文字列は等しい

この関数はeBPFでHTTPヘッダをパースするときに役立ちます。HTTPヘッダではキーの大文字小文字を無視します。HTTPヘッダを行単位でパースする場合、特定のキーをヘッダから検索するには、大文字小文字を無視したプレフィクスマッチが必要です。

感想

メーリングリストを使った開発は初めてでしたが、開発環境がきちんと整えられている点が印象的でした。メールのCCに含める人をスクリプトで取得できたり、メールに特定のプレフィクスを含めると自動でCIが走ったり、多くの開発者が分散してメンテナンスできる仕組みが構築されていました。またClaudeを用いたAIレビューも導入されており、Linuxカーネル開発にも最新技術が取り入れられていることを実感しました。

またレビュワーからの指摘では、「他のコードとの一貫性」について指摘をいただいたのが印象的でした。これは似たような動作をするプログラムでは、似たようなコードを採用するべき、という方針です。より高速に動作するコードよりも、一貫性の保たれたコードを重視するのは、Linuxカーネルという安定性・安全性が求められる環境ならではだと思います。

全体を通じて、Linuxカーネルのデバッグ方法、パッチの送り方、メンテナンスしやすい仕組みづくりなど、多くのことを学びました。

参考文献

Discussion