Linuxカーネルを新しいCPUに移植する

この記事はLinuxアドベントカレンダー2015 21日目に大遅刻した記事です。ごめんなさい

皆さん、ご家庭に「作ってみたはいいけれど、動くOSがなくて困ってる!」っていうCPUはありませんか?もしそんなCPUがあればLinuxを移植してみてはどうでしょう??
この記事ではLinuxを新しいCPUへ移植する話をします。

前書き

なぜLinuxを新CPUに移植する話をしているかというと、学科の同期数名で、FPGAで作ったCPUにLinuxを移植するということを5月ごろから細々と進めているからです。(もっとも院試やら卒論やらで本格的に作業できるのは実際には来年の2月以降です。。)これは以前自作CPUにxv6を移植したやつの第2段階ですね。

まだ新CPUのもろもろが出来上がっていませんので、現在は本番の移植のための勉強としてx86アーキテクチャのミニマムな再実装をやっているところです。本記事は今までの知見をもとに書かれています。まだ移植を完成させたわけではないので、記述に不完全/誤ったところがあるはずです。見つけた場合はご指摘くださいませ。

移植関連資料

さて、Linuxを新CPUに移植するのは簡単なタスクではありません。やることはそれなりにありますしLinuxカーネルの理解ももちろん必要です。そして何よりまとまった資料が少ないことが問題になりました。以下に参考になりそうな資料をいくつか挙げておきます。

Embedded Linux Conferenceでの発表資料です。移植の全体的な流れが俯瞰できると思います。

K1というプロセッサーにLinuxを移植した際の話が載っています。実際に移植する際の具体的な雰囲気が感じ取れます。

Linuxカーネルを移植するための正しい方法」についてのLWNの記事なんですが、要約すると「x86からコピペすんなカス」という記事です。詳しい移植の流れなどについては特に触れられていませんが、今では不可欠な存在になったasm-genericが紹介されています。

で、僕らが作業を始めた頃はこれくらいしか資料がなく大変だなーと思っていたのですが、実は最近次の記事がLWNに上がったようです。

移植にあたって書かなければいけないことが、簡単にではありますが、網羅的にまとまっています。これからLinuxを移植しようと思ったらまず第一に参照すべき資料になると思います。*1

Linuxカーネルそのものの情報については、定番ですが

詳解 Linuxカーネル 第3版

詳解 Linuxカーネル 第3版

が参考になると思います。

ただ、ご存じの通り詳解Linuxカーネルは扱うカーネルのバージョンがだいぶ古く、現在の状況と食い違うことがしばしばあります。最近は0xAXさんによる Linux Inside - GitBook が、最新の4.x系列のカーネルに沿った解説をしていてかなり参考になります。現在進行形で内容がどんどん充実してきているのでぜひブクマしておきましょう。

ツールチェインの移植 - clangも使えるけど・・・

Linuxを移植するにはLinuxコンパイルできるコンパイラアセンブラ、リンカが必要になります。なので先にこれを移植しなければなりません。

まず絶対にGNU binutilsは必要です。asやldですね。Linuxをビルドしたいなら他の選択肢はないはずです。次にコンパイラですが、移植するのはgccとclangどちらでも一応大丈夫です。「あれ、Linuxってclangでビルドできたっけ?」となると思うんですが、実はアーキテクチャ非依存部はclangでもビルド可能になっています。x86のビルドが現時点ではこけてしまうので実感がないと思うのですが、後々紹介するスタブアーキテクチャはclangでもばっちりビルドできます。clangでのLinuxのビルドの状況は別記事にでもまとめたいですね。

ただ現状ではclangのビルドは不安定です。3.6系でtypeof演算子のバグを踏んでカーネルのビルドが落ちていたなどclang側でも時々問題が発生しますし、カーネル側もarch/x86を見ればわかる通りclang未対応の機能を気兼ねなくぶち込んできます。お互いにお互いをあまり慮っていないように見えるので、clangを選ぶ際はそこらへんを考慮した方がいいでしょうね。Linuxをclangでビルドできることが当たり前になる時代はそう遠くないと体感的には感じているんですが・・・。

gccbinutilsの移植に関しては今回はこれ以上触れません。 詳しくは、Open Computer Design Projectの 新アーキテクチャに GCC を移植 [Open Design Computer Project] がとても参考になると思います。

archディレクトリ

ここからが本題になりますね。Linuxカーネルアーキテクチャ依存部を書く話です。

まずLinuxソースコードの構成をざっと俯瞰すると、だいたい以下のような感じになっています。

├ Documentation/     
├ arch/     
├ block/    
├ certs/    
├ crypto/   
├ drivers/  
├ firmware/     
├ fs/   
├ include/  
├ init/     
├ ipc/  
├ kernel/   
├ lib/  
├ mm/   
├ net/  
├ samples/  
├ scripts/  
├ security/     
├ sound/    
├ tools/    
└ usr/virt/  

kernelにはカーネルのコアになる機能、fsにはファイルシステム、mmにはメモリ管理周りといった風に、目的に応じてソースコードが分類されて詰められています。ドライバーなどは別にすれば、移植に当たって触らなければいけないのは、上から2番目のarchディレクトリだけです。この中にさらにアーキテクチャごとにディレクトリが作られ、例えばx86やらmipsやらopenriscやらといったものがこの中にすでに並んでいます。ここに新しいディレクトリを作り、アーキテクチャ依存部として必要なソースコードを追加していくのが具体的なLinuxの移植作業になります。

例えばnew_cpuというアーキテクチャを追加したければ、arch/new_cpuなるディレクトリを作ればいいことになります。加えてちょこちょことMakefileなどを追加すれば、

make ARCH="new_cpu"

とかするとカーネルがnew_cpuディレクトリを認識してくれてビルドを始めることができます。便利ですね。
といっても何も書いていない空ディレクトリではもちろん普通にビルドに失敗します。移植の第一歩はひとまずビルドができるように、各種ファイルやスタブ実装のコードを用意することになります。

ビルドシステムの理解

arch/new_cpu以下に必要なのはソースコードだけではありません。arch依存のMakefileも必要ですし、menuconfig用のconfigなどなども必要になります。ここらへんを何の頼りもなしに用意するのは不可能ですから、ほかのアーキテクチャを参考にしてarch/new_cpu以下に何を配置すべきなのか参考にしましょう。ただしこのとき、間違ってもx86などの奇天烈なアーキテクチャを参考にしてはいけません。おすすめなのは先ほど上げた資料中でも紹介されていたscoreというアーキテクチャで、とてもファイル数が少なく全体を読み通すことができます。僕らのx86再実装もscoreを参考にしつつ進めています。

scoreを参考にしてarch/score以下にどのようなファイルがあるか見てみましょう。

 ├ boot/  
 │    vmlinuxの作成に必要なMakefileなど  
 ├ configs/  
 │    アーキテクチャのデフォルトのmenuconfigなど  
 ├ include/    
 │    ├ asm  
 │    │   アーキテクチャ依存ヘッダファイル
 │    └ uapi  
 │         そのうち、ユーザー空間にエクスポートされるもの  
 ├ kernel/  
 │   ├ *.c      
 │   │   アーキテクチャ依存の関数などを記述します  
 │   └ vmlinux.ld.S  
 │        カーネルのリンカスクリプトを記述します  
 ├ lib/  
 │    文字列関数やビット演算など補助ライブラリ  
 └ mm/  
       アーキテクチャ依存関数のうち、メモリ管理周り  

x86などの巨大なアーキテクチャでない限り、だいたいこれと同じ構成になります。例えばopenriscなんかは全く同じディレクトリ構成です。(もっともscoreがopenriscを参考にしたのかもしれませんが。)
scoreの具体的なソースコードは、Linux/arch/score/ - Linux Cross Reference - Free Electronsなどでご覧ください。kernelやmmを覗くと、移植に必要なファイル数は思ったよりも少ないかも?って感想を抱くかもしれません。

ご覧の通り、ビルドのために各種Makefileやconfigファイルが必要です。先ほど書いた通り、各アーキテクチャの実装も参考にするのでが、大本の参考資料としてkernel.orgのDocumentationにあるKbuildのマニュアルは必読です。これらはおそらくオンラインで手に入るLinuxMakefileやconfigの最も詳細な解説になります。arch開発者向けの資料も用意されています。

リンカスクリプトであるvmlinux.ld.Sも書く必要があります。リンカスクリプトは資料がほとんどないため書くのに苦労すると思います。基本的にはGNU ldのあまりまとまりのよろしくないマニュアルを読んで理解するしかありません。VMAとLMAがきちんと設定できているか気を付けながら書きましょう。具体的なカーネルリンカスクリプトの例の一部がinclude/asm-generic/vmlinux.lds.hの冒頭のコメントにあって参考になります。また、後に紹介する僕らのアーキテクチャstub nullpo-head/Linux-Architecture-Stub · GitHub がこれをもとに完全なvmlinux.ld.Sを書いていますので参考になるかと思います。

スタブを作る

ビルドシステムに必要なファイルを準備できたら、次はリンクを成功させるためにスタブコードを用意しなければなりません。

一番最初に紹介した資料Porting Linuxのように、基本的には「他のアーキテクチャをコピペしてはいけない」です。ので、Makefileやvmlinux.ld.Sだけ他のアーキテクチャを参考に書き、ひとまずビルドが走るようになったらエラーメッセージを頼りにスタブを増やしていく・・・というのが理想の流れとされているようです。Porting Linux to a new processor architecture, part 1: The basics [LWN.net]でも、エラーメッセージを頼りに何の関数が必要か推測すると書いてあります。これは、すでにあるアーキテクチャをコピーすると不要なものまで実装してしまいやすいからだとThe right way...では説明されています。

とはいえこの作業は大変ですし、最低限のスタブはわりとHW非依存になるはずなのに各自で書くことに若干不毛さも感じてしまいます。これを解消するためにThe right way...では、「ブートまでできるexampleアーキテクチャを用意してそこからブランチできるようにするぜ!」と宣言していて実装も進んでいたそうなんですが、コメント欄いわく「gitインフラ移行のゴタゴタで消えちゃったわ」だそうです。合掌。

さて、前言をひっくり返すようですが、僕らはx86最小実装ではあえてscoreのコピーから始め、最低限の定義以外をガリガリ削ってスタブを作ることにしました。繰り返すように、既存のアーキテクチャをコピーして始めることのデメリットはとにもかくにも無駄な機能もよく分からないままコピーしてしまうことです。しかし、scoreはとても小さい実装なのですべての行に目を通すことができ、なおかつPorting Linuxで紹介されているようにとてもきれいで読みやすいです。なのでx86やarmなどの巨大なアーキテクチャと違い、目を通さないまま丸々コピペする心配があまりありません。この方法で始めると最初からビルドが通るので、作業中に誤りを埋め込んでもすぐに発見できてとても楽になりました。

先ほどのブートまでできるexampleアークテクチャには及びませんが、x86再実装とは別にscoreをもとにしたstubアーキテクチャ

github.com

にて作成中です。もし新しく移植を始める際にはお役に立つかもしれません。まだまだscore依存のコードが(特にヘッダファイルに)残っていますが、x86再実装の進行とともに洗練させていくつもりです。

make ARCH=stubs tinyconfig
make ARCH=stubs -j4

にてビルド可能です。出来上がるELFはx86用になっています。

ところでツールチェインの節で紹介したようにこれはclangでもビルド可能です。その場合、

make ARCH=stubs CC="clang -no-integrated-as"

となります。-no-integrated-asって何やねんというとclangの統合インラインアセンブリによる最適化を無効にするオプションです。clangのバグと機能不足によるもので、現時点ではこれを付けるのがLinuxビルドのデファクトになっています。

中身を書く

スタブができたら後はひたすら中身を実際に実装していく作業になります。スタブを理解してほかのアーキテクチャを流し読みしていると、だいたいやるべきことが分かってきます。流れとしてはだいたい以下のようになります。

  1. head.Sにブートシークエンスを書いてstart_kernelまで走らせる

  2. メモリ初期化や割り込み初期化など初期化処理を書いてstart_kernelを完遂させる

  3. 割り込みハンドラやswitch_toなど、カーネルの動作に必要なarch依存部を書いてinitを起動させる

なお、これらはkernel/*.cだけを埋めていけばいいというわけではなく、動作に必要なヘッダファイルも並行して埋めていくことになります。例えばメモリのレイアウトに関する定数など。またLinuxはヘッダファイルにも.cファイル並みに関数の実装を書くので、それも書いていく必要があります。書かなければいけない主要な関数などは、

Porting Linux to a new processor architecture, part 2: The early code [LWN.net]

にまとまっているので雰囲気を知りたい方はぜひ参考にしてください。

x86再実装においては、僕らはまだまだ2の途中の段階です。1のブートシークエンスはmulti boot headerを書いてgrubqemuにハードウェアの初期化を任せ簡潔に済ませました。

実際にコードを書いていく作業は当たり前なんですが大変です。数としては書くべき.cは意外と多くないのですが、カーネルの理解が必要とされるので、カーネルの勉強を並行しての作業だとあまり速くは書けません。また、include/以下の.hの方は多いと100個ほど書かなければならないそうです。もっとも現在はasm-genericをincludeするだけで大半のものが済ませてしまいます。感謝感謝。

自分もまだまだ作業中なので、この部分に関して簡潔にまとめることは残念ながらまだまだできません。実際に作業をしての雑感としては、コードを書いている時間よりもカーネルのコアのコードや資料を読んでいる時間の方が何十倍もあるね!という感じです。(カーネルnewbieなので当然ですね。)新年ではカーネルの移植を完了しての雑感を書きたいところです。

まとめ

Linuxを新しいCPUへ移植する作業は、ツールチェインを移植したのち、archディレクトリ以下をほかのアーキテクチャを参考にしつつ実装していく作業でした。最近はPorting Linux to a new processor architecture, part 1: The basics [LWN.net]などのいい感じの資料も出てきて作業がしやすくなっています。カーネルの勉強にももしかしたらちょうどいいかもしれません[要出典]

Linuxを移植したいけど余った新しいCPUがない?そんな場合は学科の後輩たちが書いている CPU実験 Advent Calendar 2015 - Adventar にいい感じのCPUの作り方の記事があるかもしれませんし、ないかもしれません。(雑な宣伝)

ところで文中何度も出てきたx86のミニマムな再実装ですが、レポジトリこちらになります。ただし本格的な作業がまだなので、今はどなたかの参考になる段階にはないと思います。。後々の成長を見守ってくださいませ。

それでは皆様、メリークリスマス! & よいお年を!

*1:移植が終わったらこういう資料を作ろうと思っていた僕たちからすると少し複雑