絵で見てわかるLinuxカーネルの仕組み:読書メモ

読んで理解したことや感想などを載せる
1章 Linuxカーネルの基本
概要
カーネルはハードウェアの操作を行ったり、アプリケーションのためのインターフェースを用意したりしているOSの主要なコンポーネントである。
Linuxのカーネルにはプロセススケジューラ、メモリ管理、ファイルシステム、ブロックI/O、ネットワークなどの構成要素がある。
バージョン、LTS
LinuxカーネルにはLTSという長期サポートがされるバージョンがある。2~6年メンテナンスがされる。
メンテナンスというのは最新バージョンでのバグ修正の内容のバックポートである。バックポートというのは新機能などは追加せずに修正だけを既存のバージョンに反映するという感じである。
(LTSについてよく知らなかった。。なるほど。)
学習方法
カーネルの学習方法としてはソースコードを読むのがよい。いきなり全体をみるのは難しいので、パッチの内容から関連箇所をたどるのが良いとのこと。
カーネルモジュール
カーネルの機能を拡張するためにメモリにロードして使うモジュールのこと。カーネルモジュールをつかうことで、カーネル本体部分だけをカーネルイメージに含めてイメージサイズを押さえることができる。モジュールはカーネル起動後にメモリにロードされる。
ユーザ空間とカーネル空間
ユーザが操作するアプリケーションなどが動いているメモリ領域がユーザ空間、カーネルが動作するメモリ領域がカーネル空間。ユーザ空間とカーネル空間のやりとりにはシステムコールをもちいる。

2章 プロセススケジューラ
プロセススケジューラとは
プログラムを実行するとプロセスが作られる。プロセスを処理するにはCPUのリソースが必要である。
そこでプロセスにCPUのリソースを割り当てるのがプロセススケジューラである。
プロセススケジューリング
CPUのコア数には限りがあるので、プロセス数がそれより大きくなると上手く捌く必要が生じる。実際には1つのプロセスが処理を続けられる時間には制限があって、制限時間になると他のプロセスにリソースが割り当てられる。こうして処理するプロセスを短時間で切り替えすることで、同時に処理しているかのように見えている。
プロセスの状態
- Running(実行中ないしはリソース割り当て待ち)
- Interuptible(ユーザが中断可能な待ち状態)
- Uninteruptible(ユーザが中断不可能な待ち状態)
- Dead(終了)
- Stopped(シグナルによって停止されている状態)
スケジューリングポリシー
スケジューリングポリシーはスケジューリングをおこなうときに、どのようにCPUリソースを割り当てるかを決めるルール。Deadline, Realtime, Fairでおおきく3つに分類できる。CFS(Completely Fair Scheduler)はRunning状態のプロセスに均等にCPU時間を与える(nice値という値で優先順位をきめられうらしい)。リアルタイムスケジューラは静的にきまった優先度順にCPU時間の割り当てをおこなうので、CFSのようにプロセス数が増えたときにCPU割り当てをされるまでの時間が変化するということはない。デッドラインスケジューラは締切が早いプロセスから順に、締切に間に合うようにCPU時間を割り当てる。

3章 メモリ管理
メモリ確保
Cのメモリを確保する関数malloc()は確保容量が小さいときはプロセスアドレス空間のヒープ領域からメモリを確保していて、確保する容量が大きいときはメモリ管理によってメモリを確保する。(このへんはプロセスアドレス空間のスタック、ヒープ、データ領域とかを思い出すとよい)
メモリを確保したとしてもすぐに確保分を使うとは限らないので、メモリ確保のときは仮想アドレス空間を返却している。実際につかわれたタイミングで物理メモリを用意している。
オーバーコミット
メモリの使用状況は刻々と変化するので、ある瞬間で確保したいメモリ量の空きが無いとしても、時間をおけば開放されて確保できるかもしれない。ということでLinuxは物理メモリの大きさ以上のメモリ割り当てができるようになっている。
Buddyアロケータ
ページ単位でメモリを管理してくれる機能。物理メモリの単位は1バイトだけど、Buddyアロケータは1ページ(4KB)単位でメモリを扱う。huge pageという機能で4KB以上のサイズのページを扱うことも可能。逆にそれより小さいサイズのメモリ管理をするときはスラブアロケータやvmallocを使用する。
スラブアロケータ
スラブアロータはBuddyアロケータに用意してもらったページをスラブオブジェクトに分割して管理する。スラブオブジェクトの集合をスラブと呼ぶ。
(ユーザ空間ではmalloc()によってメモリの取得と解放ができるが、カーネル空間ではできないのでスラブアロケータがその代わりをしてくれる)
現在のLinuxのデフォルトのSLUBアロケータではスラブオブジェクトをサイズごとに管理している。
もし異なるサイズのスラブオブジェクトを混ぜて管理すると、フラグメンテーション(小さい未使用スラブオブジェクトを合計すると必要なメモリ量に足りるが、連続領域でないので確保できない)ということが起こる。
未使用のスラブオブジェクトは次に使うことができる未使用のスラブオブジェクトのポインタ情報をもっている。これによって未使用のスラブオブジェクトは連結リストになっていてfreelistと呼ばれる。
CPUにはper-CPU variablesというCPU固有の記憶の保管場所がある。そこでCPUにひも付けされたページの情報やfreelistの情報をもっている。
vmalloc
vmallocでは確保する仮想メモリアドレスのページが物理メモリアドレス上で連続していることを保証しない。
(前触れもなくkmalloc()が登場するが、カーネル空間専用のメモリ確保の関数としてkmalloc()という関数があるらしい。)
ヒープオーバーフローバグをつかって本来のサイズ以上の書き込みをする攻撃の対策としてガードページがある。ガードページは本来につかうはずのメモリ領域の直後に仮想アドレス上で確保しておいて、本来の使うはずのメモリ領域をこえて書き込みをしたときに、ガードページ部分に到達してページフォルトが発生することで攻撃を検知できる(らしい)。

4章 ファイルシステム
以前調べた内容と被る箇所もあるので追加で知ったことを書く。
FHS関連の概要はここで書いた。
イニシャルRAMディスク(initrd/initramfs)
イニシャルRAMディスクはブートのためにメモリ上に作られる小さなファイルシステムである。
これを使うことでブート時のファイルシステムと実際のファイルシステムを独立させることができるので様々な種類のディスクのためのドライバをカーネルイメージに含めずに済むというメリットがある。
☆従来のブート
- 電源をいれる
- EFI
- ブートローダ起動
- メモリ上にカーネルイメージ(一般的に使われているディスクのドライバを含む)をロード
- カーネルを起動する(カーネルは自身の持つドライバでディスクを読み取り、ファイルシステムをマウントする)
☆イニシャルRAMディスクを用いたブート
- 電源をいれる
- EFI
- ブートローダ起動
- メモリ上にカーネルイメージとイニシャルRAMディスクをロードする
- カーネルを起動する
- メモリ上にイニシャルRAMディスクをマウントする(ファイルシステムとして使える状態にする)
- イニシャルRAMディスク内のinitを実行する(ここで本来のフィアルシステムを使えるようにする)
- 本来のルートファイルシステムに切り替える
モダンなファイルシステム
慣習的に/
ファイルシステムとしてマウントされるパーティションと/usr
ファイルシステムとしてマウントされるパーティションは別にされるのが一般的であった。そうなるとブート時にルートファイルシステムで使うパーティションをマウントした時点では別パーティションの/usr
はマウントされていない。もし/usr
の中にブートで使うためのファイルが入ってたとしたらブート時にそれを認識してないので使うことができない。というわけで/bin
,/sbin
,/lib
はルートファイルシステムに属していた。
しかし最近はイニシャルRAMディスク上で起動時用のファイルシステムを用いて起動後に本来のファイルシステムをマウントするので、/bin
や/sbin
は/usr
にあっても良い。という経緯で/bin
や/sbin
は/usr/bin
や/usr/sbin
に統合された。
VFS
VFS(Virtual Filesystem)はシステムコールと各ファイルシステムの間に存在して共通のインターフェースを提供している。Linuxのファイルシステムには様々な種類がある。ext4やBtrfsなどの通常ファイルシステムに加えてディスクでなくメモリ上に記憶するメモリファイルシステムや、カーネルの情報をファイルとして扱う疑似ファイルシステムがある。
またキャッシュをVFSで管理しているので、同じファイルに続けてアクセスしたとき2度目はファイルシステムを読み取りに行くことなくVFSからキャッシュを返却する。
(memo)
"ロード"はメモリ上にデータを置くという意味で、"マウント"はメモリ上のデータをファイルシステムとして認識することで論理的に扱うという意味。