違う。単なる名前のせいだ。ただし、紛らわしい要素は十分にあると思う。そこで今回はユーザープログラムとカーネルプログラムの違い、そしてユーザープロセスとカーネルスレッドの違いについて整理してみたい。
/proc:実体のないファイルシステム
Linuxカーネルは/procという仮想ファイルシステムを提供している。「仮想」である理由は、ディスクに実際に保存されているファイルではなく、カーネルが自身の内部データ構造をファイルのように見せる仕組みだからだ。
/procはよく知られている通りディレクトリの形をしている。しかしその下にはファイルデータが保存されているわけではなく、その内容を生成するカーネル関数が紐づけられている。そのため/proc/[pid]/statのようなファイルを開いて読み込むと、その瞬間に対応するカーネル関数が呼び出され、その場で現在の状態を作り出して返す。
psやtopは、この/proc以下のファイルを読み取ってパースし、人が見やすい形に整形して出力するだけの、ごく普通のユーザースペースプログラムである。
systemd、sshd、cronが紛らわしい本当の理由:デーモン
では、systemd、sshd、cronなどはどうだろうか。これらも同じくユーザースペースプログラムだ。ただし名前以外にも紛らわしい理由がもう一つある。それが**デーモン(daemon)**であるという点だ。
psやtopは一度実行すれば結果を出力して終わる。一方、こうしたデーモンはずっと動き続けている。そのため「あれ?これは自動で動くカーネルプロセスなのでは?」と勘違いしやすい。
最も紛らわしいのはsystemdだろう。PIDが常に1番であるため何か特別に見えるし、すべてのユーザープロセスの祖先でもある。しかしこれは「カーネルプロセス=システムに関わるプロセス」という思い込みから来る誤解にすぎず、systemdも結局はユーザースペースプログラムである。
では「カーネルプロセス」とは何なのか
用語から確認しよう。Linuxにおけるプロセスとは、独立したメモリ空間(アドレス空間)を持つ実行単位を指す。では、カーネルは独立したメモリ空間を持っているのだろうか?答えはノーだ。カーネルは一つのアドレス空間を共有している。そのため実務では「カーネルプロセス」という表現の代わりにカーネルスレッドという用語を使う。複数の実行フローが一つのアドレス空間を共有するという点で、一つのプロセス内の複数のスレッドがアドレス空間を共有するのと性質が同じだからだ。
では「カーネルプログラム」という表現はどうだろうか。これも適切ではない。通常プログラムは実行時にメモリにロードされてプロセスとして起動するが、カーネルは起動(ブート)時点ですでにメモリに乗っている。(システムの土台となる部分であり、スワップの対象にもなり得ないからだ。)つまりカーネルは「実行されるプログラム」というより、カーネルスレッドたちが動くすでに常駐している環境に近い。
まとめると、カーネルの中で実行フローの単位として存在するのは、プロセスではなくカーネルスレッドである。
カーネルスレッドとユーザープロセス、どう見分けるか
一番簡単な方法は、実際にマッピングされたメモリ空間があるかどうかを見ることだ。カーネルスレッドは上で説明した理由により、独立したユーザーアドレス空間を持たない。
あるいは、プロセスの祖先をたどっていく方法もある。
カーネル(ブート時点ですでにメモリに常駐、プロセスではない)
├─ kthreadd (PID 2) ← すべてのカーネルスレッドがここから派生
│ ├─ ksoftirqd/0
│ ├─ kworker/0:1
│ └─ ...
│
└─ systemd (PID 1) ← すべてのユーザープロセスがここから派生
├─ sshd
├─ bash
│ └─ ps (今実行しているこのコマンド)
├─ NetworkManager
└─ ...
厳密に言うと、上のツリーの本当のルートはPID 0(swapper/idle)であり、kthreadd・systemdはどちらもその子である。ただしPID 0は
ps/pstreeの出力には通常表示されないため、この図では便宜上「カーネル」としてまとめて省略している。
おわりに
ユーザープロセスとカーネルプロセスは教科書でよく比較されるテーマだが、実際の環境(ps、top、systemdのような名前たち)ではかえって紛らわしいので、一度整理してみた。参考になれば嬉しい。