プロセスの源流を見に行く
MacBookが起動している間、実に様々なアプリケーションが稼働して、快適な作業環境を提供してくれる。Dockやメニューバーには、自分の意志で明示的に起動されたアプリケーションが並ぶ。しかし、そこに見えているのはほんの一部で、その裏ではさらに多くのアプリケーションが稼働している。それらは常にバックグラウンドで稼働し、UI*1を持たない。だから、厳密にはアプリケーションとは言えないかもしれない。
普段はその存在をほとんど感じることはないが、アクティビティモニタ*2を起動すると簡単に確認できる。ツールバーで「すべてのプロセス」にしてみると、自分の環境ではその数およそ100プロセス*3。結構な数だ。
それらの暗黙的に稼働している(はるかに多い)プロセスは、一体いつ起動されたのだろう?それを調べるには、やはりアクティビティモニタのツールバーで「すべてのプロセス(階層表示)」にしてみると、ある程度想像がつく。
プロセスには親子関係があって、あるプロセスBには、それを起動した別のプロセスAが必ず存在する。(BはAの子プロセス、逆にAはBの親プロセス、という関係になる)また、プロセスには起動した順番にプロセスID(PID)が付番されている。PIDが小さいほど早いタイミングで起動されたプロセス、大きければ遅いタイミングで起動されたプロセスになる。
PID昇順に並べ替えて、先頭を見てみる。すると、あらゆるOSX 10.6は以下のように始まっていると思う。
- PID 0 = kernel_task
- PID 1 = launchd
kernel_taskは子を持たず単独のプロセスだが、launchdは子を持っている。驚いたことに、launchd以降に起動したプロセスは、すべてlaunchdの子プロセスだったのである。また、よく見るとlaunchdの子プロセスの中にも、さらに別のlaunchdが見つかる。PID 1の方はroot権限のlaunchdだ。その子プロセスのlaunchdは、ユーザー権限のlaunchdだ。おそらく、OS共通のプロセス管理か、ログインしたユーザーのためのプロセス管理かで分担しているように見える。
- root権限のlaunchdは、すべてのユーザー共通の環境を作るプロセスを管理する役割。
- ユーザー権限のlaunchdは、ログイン後のそのユーザー環境を作るプロセスを管理する役割。
launchd
launchdは、すべてのプロセスの母だったのである。では、launchdとは何か?その概要については、以前の日記が参考になるかもしれない。
launchdの使い方
設定ファイルは、以下のように明示的にloadする必要がある。
- plist形式(xml)の設定ファイルを作る。
- 例1:~/Library/LaunchAgents/com.apple.MobileMeSyncClientAgent.plist(MobileMeと同期する設定)
- 例2:/System/Library/LaunchAgents/com.apple.Finder.plist(ログインしたユーザーごとにFinderを常時起動する設定)
- ロードする
$ sudo launchctl load FILE_PATH
- アンロードする
$ sudo launchctl unload FILE_PATH
- ロードされているリスト
$ launchctl list
自動ロード
OS起動時・ログイン時には、以下の設定ファイルは自動的にロードされる。
- /Users/zari/Library/LaunchAgents
- zariユーザーがログインした時だけ、ロードされる。
- /Library/LaunchAgents
- 各ユーザーがログインした時に、それぞれロードされる。(サードパーティの設定)
- /Library/LaunchDaemons
- OS起動時にロードされる。(サードパーティの設定)
- /System/Library/LaunchAgents
- 各ユーザーがログインした時に、それぞれロードされる。(OSXの設定)
- /System/Library/LaunchDaemons
- OS起動時にロードされる。(OSXの設定)
LaunchAgentsとLaunchDaemonsの違い
LaunchAgents とは...
- ログイン時にロードされる。
- 各ユーザーごとのlaunchdの子プロセスとなる。
- GUI環境を利用したプロセスも、起動できる。
LaunchDaemons とは...
- OS起動時にロードされる。
- PID 1 のlaunchdの子プロセスとなる。
- GUI環境を利用しないプロセスのみ、起動できる。
例:
- /System/Library/LaunchAgents/com.apple.Finder.plist によって、Finderは、各ユーザーがログインした時に、それぞれ起動される。
- LaunchAgentsに設定されるのは、FinderがGUIアプリケーションであるため。
- 仮に、/Users/zari/Library/LaunchAgents/com.apple.Finder.plist では、zariユーザーがログインした時しか起動しなくなってしまう。
kernel_task
それでは、launchdよりも先に起動している、PID 0 のkernel_taskとは何だろうか?実は、これこそがOS本体(カーネルと呼ばれている部分)のプロセスである。
- kernel_taskは、それ以降に起動されるすべてのプロセス(PID 1 のlaunchdも含む)に対して、作業する場所と時間を分け与える。
- 作業する場所とは、メモリ領域である。時間とは、CPUを利用する時間である。
- 起動するタイミングを決めるのはlaunchdだが、起動したプロセスはkernel_taskによって、完璧にコントロールされているのだ。
特権モードとユーザーモード
- kernel_taskは特権モードで実行される唯一のプロセスである。
- 一方、それ以外のプロセスは、すべてユーザーモードで実行される。
- 特権モードはユーザーモードより優先されるので、kernel_taskはその他のプロセスがマナー違反を犯そうとしても、対抗できる。
- また、ユーザーモードでは実行できない命令、アクセスできないメモリ領域がある。
-
-
- ちなみに、ここで言う特権モード・ユーザーモードとは、ファイルのアクセス権とは全く別の権限である。
- CPUモード - Wikipedia
-
制限と代理実行・最適分配
- プロセスが分け与えたメモリ領域以外にアクセスしようとすると、kernel_taskはそのプロセスに処理を中止させる。
- その代わりkernel_taskはプロセスより依頼を受ければ、ユーザーモードでアクセスできないメモリ領域に代理でアクセスしてあげる。(許可できるアクセスならば)
- プロセスが分け与えた時間以上にCPUの利用を独り占めしようとしても、kernel_taskはタイマーを見て、そのプロセスにCPUの使用を中止させる。
- しかし、kernel_taskはすべてのプロセスにCPUの利用時間を均等に分配している訳ではない。
- 忙しいプロセスにはより多くの時間を、暇そうなプロセスにはより少ない時間を、それぞれの状況を察する気遣いもしている。
外界とのやり取り
ドライバ
- CPUが外部機器にアクセスする場合は、IO領域と呼ばれる特殊なメモリ領域へアクセスすることで実現される。あるいは、IO領域へアクセスする専用の命令が用意されている。
- どちらにしても、IO領域へのアクセスでは非常に低レベルな操作しかできず、しかも外部機器ごとに固有のルールがあるので、そのままではものすごく面倒なことになる。
- そんな状況を解決すべく、ドライバと呼ばれる、外部機器をもっと簡潔に操作するためのソフトウェアが用意されるようになった。
ドライバが実行される場所
- ドライバは、外部機器をコントロールするために、どこかで必ずIO領域へアクセスする必要がある。
- IO領域は、特権モードのCPUしかアクセスできない領域になっている。
- ところで、OSXのカーネルは、machというマイクロカーネルがベースとなっている。
- マイクロカーネルとは、プロセスに場所と時間を分け与えることのみに特化して、できる限り小さくシンプルなカーネルを目指した設計思想である。*5
- そのため、マイクロカーネルでは、ドライバもカーネル外部でユーザーモードの一般的なプロセスとして稼働する。
- ドライバがユーザーモードのプロセスとなったことで、プロセス同士(プロセスとドライバ間)のデータ通信手段が必要になった。
- また、ドライバがIO領域へアクセスする時に特権モードが必要になるので、カーネルに処理を依頼する必要もある。(2回の手間)
- 仮に、ドライバがカーネルに組み込まれていたら、プロセスはカーネルにドライバの処理を依頼するだけで完了する。(1回で完了)
- マイクロカーネルよりも、ドライバが組み込まれたカーネルの方が、処理効率としては良いのである。
- そこでOSXでは処理効率を考えて、マイクロカーネルに再びドライバを組み込んでしまった*6。
- 但し、何の工夫もなくカーネルにドライバを組み込んでしまうと、ドライバを追加・変更する度にカーネルの再構築が必要になってしまう。
- そのためOSXでは、稼働中のカーネルであっても、カーネル機能拡張としてドライバを自由に追加・削除できる仕組みになっている。
このカーネル機能拡張のおかげで、以下の恩恵を受けられるのだ。
カーネル機能拡張の使い方
カーネル機能拡張は、以下のように明示的にカーネルにloadする必要がある。
- カーネル機能拡張の作り方。
$ sudo cp -R MyDriver.kext /tmp $ sudo chown -R root:wheel /tmp/MyDriver.kext $ sudo kextload -v /tmp/MyDriver.kext Requesting load of /tmp/MyDriver.kext. /tmp/MyDriver.kext loaded successfully (or already loaded).
- アンロードして、カーネルから取り除く。
- vオプションでメッセージを出力している例。
$ sudo kextunload -v /tmp/MyDriver.kext
com.MyCompany.driver.MyDriver unloaded and personalities removed.
$ kextstat | grep -v com.apple Index Refs Address Size Wired Name (Version)108 0 0x13bb000 0xe000 0xd000 com.iospirit.driver.rbiokithelper (1.5.4) <68 35 29 5 4 3 1> 127 0 0x95e7a000 0x2000 0x1000 com.bresink.driver.BRESINKx86Monitoring (2.0) <12 11 10> 128 3 0x9603b000 0x25000 0x24000 org.virtualbox.kext.VBoxDrv (3.2.8) <7 5 4 3 1> 129 0 0x95f10000 0x7000 0x6000 org.virtualbox.kext.VBoxUSB (3.2.8) <128 49 35 7 5 4 3 1> 130 0 0x95ee0000 0x4000 0x3000 org.virtualbox.kext.VBoxNetFlt (3.2.8) <128 7 5 4 3 1> 131 0 0x95eb3000 0x5000 0x4000 com.google.driver.Gild (1.0.0) <5 4 3 1> 132 0 0x95eae000 0x3000 0x2000 org.virtualbox.kext.VBoxNetAdp (3.2.8) <128 5 4 1> 133 0 0x7f50b000 0x18000 0x17000 org.pqrs.driver.KeyRemap4MacBook (7.3.0) <29 5 4 3 1> 144 0 0xb75000 0x2000 0x1000 com.bebekoubou.driver.ClamshellWake (1) <4 3>
自動ロード
OS起動時に、以下のフォルダ内のカーネル機能拡張は、自動的にロードされる。
- /System/Library/Extensions/
/Library/Extensions/-
- 自作の ClamshellWake.kext を /Library/Extensions/ に入れても起動時にロードされなかった。(/System/Library/Extensions/ ならロードされるのだけど)
- ところが、/Library/Extensions/VBox*.kext 関連は、ちゃんと起動時にロードされている。なぜだろう?
-
kernel_taskのメモリ
- アクティビティモニタのシステムメモリで 固定中(Wired)は、kernel_taskが確保している分である。
- kernel_taskは、常に物理メモリ上に展開されて、実行される。
- 処理効率を低下させないことを目指しているので、スワップしない。
kernelのキャッシュ
- 起動時にカーネル機能拡張を一つずつロードしていては、時間がかかってしまう。
- そのため、終了時のkernel_taskのメモリに展開された状態をファイルに保存(キャッシュ)しておき、
- 次回起動時には、キャッシュされたkernel_taskを素早く読み込んで、メモリに展開している。
- OSインストールやアップデート後、2回目以降の起動時間が短縮されるのは、この仕組みによる。
...と、ここまで書いたが、全然源流にたどり着けない...。launchdとkernel_taskのことを調べるだけで手一杯になってしまった。
kernel_task以前はどうなっているのか?また、kernel_task以降についても、時系列にはまだ何も分かっていない。まだまだ疑問は山積みである。プロセスの源流を辿る旅は、まだまだ続く。
-
-
- 以上のことは現状の自分の理解なので、不足しているところ、勘違いしている部分があるかもしれない。(教えて頂けると嬉しいです)
-
*1:ユーザー・インターフェース。ユーザーがマウス・キーボードで直接操作する手段。あるいは、その処理結果を確認する手段。
*2:/アプリケーション/ユーティリティ/アクティビティモニタ。/Applications/Utilities/Activity Monitor.app。
*3:OSは稼働中のプログラムをプロセスという単位で管理している。同じメモリ領域を分け与えられたプログラムの実行単位と考えられるだろうか。例:Safari 5.1は1つのアプリケーションだが、プロセスとしてはSafari 本体、Safari Webコンテンツ、Flash Player プラグイン、と3つのプロセスに分かれて稼働しているのだ。Chromeは、タブ1つひとつが独立した1プロセスとして稼働している。
*4:さらに、今時のパソコンには描画専用のCPU(GPU)が用意されていて、メインのCPUはGPUに描画を依頼している。VRAMにアクセスするのはGPUの役割になっていたりする。