2018年8月18日土曜日

[ポエム]Linuxカーネルに関する独自コードをメンテナンスに関して

以下の2つの記事を拝読して、私も何か書きたくなったので書いてみます。
重複する部分も多分にありますので、どちらかというと記事の紹介ができれば私は満足です。(言い訳)
@satoru_takeuchiさんのlinuxカーネルに関する独自コードをメンテナンスするコスト
@mhiramat さんのLinuxカーネルの独自パッチのメンテに関する検討メモ

linuxに独自パッチを追加したくなる理由

そもそも、なぜLinuxに手入れをしたくなるのか?というのは存外知られていないような気がしたので書いてみます。
一口にカーネルと言っても、カーネル自身が有している機能が非常に多岐に渡っているためそのモチベーションは実に様々です。
#イメージしやすいコンポーネントだと、ファイルシステム、ネットワークスタック(TCPとかの実装)、デバイスドライバ等々…

とはいえ、カーネルであろうとソフトウェアであり、他のソフトウェアとあまり差はないかと思っています。シンプルに機能を追加したいとかバグってるから直したいといったことがモチベーションになります。
もう少しわかりやすい具体例としてネットワークカード(NIC)のデバイスドライバーを紹介しますと、PCIのIDを通過することで対応するハードウェアを増やすと言った作業などがあります。
次のパッチはネットワークカードのPCI IDを追加しています。細かいところを省略しますが端的には、PCI IDに基づいてどのデバイスドライバーでハンドルするのか決定される感じになります。そのため、既存のドライバーで動くけどIDテーブルに記載がないという時はこうやって書き足すだけで動作することもあります。

[netdrvr] tulip: add pci id - https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=9b25978ef8eb

もう少しビジネスライクな一例を上げてみます。
例えば、ここにSPIで接続するデバイスがあるとしましょう。
このデバイスは他社製品に比べて安価に調達できるため製品の部品に採用しましたが残念ながら、Linuxにはデバイスドライバーがなく新規で書き起こす必要がありました。
#補足 SPIとは比較的簡単に接続できる物理的なシリアルバスの規格のようなものです。シリアル・ペリフェラル・インタフェース(Serial Peripheral Interface, SPI)

作ったパッチをメインライン化しない場合

基本的には作ったものはアップストリームに取り込んでもらう(メインライン化)というのが基本的な考え方になります。
パッチが取り込まれたものが世の中にリリースされると、カーネルバージョン3.xでネットワークカードに対応しましたということになります。

ネットワークカードの例では、PCI IDを追加することでそのカードが利用可能になりました。メインライン化しない理由は作業がめんどくさいといった理由に限られるでしょう。
一方で、新規でドライバーを書き起こした例はどうでしょうか。
このような発想が現場では生まれがちです。
  • 公開したらみんなが使えてしまう。ライバル会社が同じデバイスを採用してコストの優位性を失ってしまうかも知れない。
  • 業務でプログラミングしたものがライバル企業にも使われてしまい彼らは開発コストを負担しないですんでしまうのではないか。

これはある意味間違ってはいないのですが、メインライン化しない場合のコストを検討する必要があります。
この作業が「独自パッチをセルフメンテナンスするか、メインライン化するか検討」、「コストのトレードオフの検討」となります。

メインライン化しない場合のコストやリスクは冒頭に紹介したお二人の記事に詳細に書かれていますのでご参照ください。
ここでは具体的な一例をを紹介します。次のコードはSPI接続のRTC…システムをシャットオフしても時刻を保存しておくためのデバイスドライバーです。

このコードで紹介したい部分はデバイスドライバーの初期化部分です。
ソースコードは下から読むと良いでしょう。
linux4.9.yと新しい方では、module_i2c_driver(rs5c372_driver);で登録を行っています。引数はその上に宣言されている構造体です。
構造体の中身も直感的ですね。

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/rtc/rtc-rs5c372.c?h=linux-4.9.y#n673

古いlinux3.2.yではどうでしょうか?
module_init(rs5c348_init);がそれっぽいです。
引数の関数を読むとreturn i2c_add_driver(&rs5c372_driver);となっており、linux4.9系の構造体をとっていますね。

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/rtc/rtc-rs5c372.c?h=linux-3.2.y#n683

3.2.yは初回リリースが2012年、4.9.yは2016年頃とギャップがありますが、変化の一例を感じて頂けるものと思います。
メインライン化していない場合、こうした変換に追従するためのコストが発生するかも知れません。

リスク・コストを見積もる難しさ

上記のRTCの例は、リリースされた結果を見ているものでしか無いという点に意識を向ける必要があります。
今回紹介したドライバーのコードは元々コンパクトなこともあり変化が見通せる程度の内容でしたが、それを予知することは難しいと考えます。
トレードオフを検討しようにも未来のコストを見積もるのは困難であるという事実があります。

しかしながら、経験の中で我々開発者はメインライン化した方がたいてい良いことを学んでいます。

RTCのドライバーをメインライン化していなかったら、次のようなシナリオが現場では展開されるかも知れません。
・・・・
新しいバージョンに合わせる書き換えのコストは、上のドライバーの場合比較的コンパクトなため簡単に書き換えられそうです。
3.2系から4.9系に移行する場合は許容可能そうなコストです。
一方で、移行ではなく新規採用の場合など3.2系も維持するような場合、3.2系パッチと4.9系パッチの2つが手元に存在することになります。
こうなった場合、何か変更する時はもれなく2つのパッチに修正を行わなければなりません。発生する事故としては、4.9系にはバグ修正したのに、3.2系には取り込み忘れたのでバグが直ってないという例です。
このように、追従コストとは、コードマネジメントを複雑化させるといった見えにくい部分のコストが含まれています。
・・・・

検討してるなら御の字

このコードはこの場しのぎですぐに使われなくなるつもりで書き捨てた。まだあのコードは動いてるらしい。
PoCでダーティなコードを書いてデモを作ったら何故か製品が完成したと誤解された。
とか聞いたりしたことはありませんか?

まぁ上の例はジョークなのですが…
検討をするにも、コスト感みたいのを経験で知っていないと中々難しいところがあります。
なので、こんなこともあるよーとか、こんな事考えてるんだーというのを知っていただけると良いなと思い稚拙なのは承知でポエムを書いてみました。

0 件のコメント:

コメントを投稿