https://kanonji.hatenadiary.com/entry/20100621/1277075926
Mac OSはUNIXベースですが、デーモンの起動や管理にはRunCommandを使わなくなっています。
OS X 10.4(Tiger)から、UNIXのPID 1のプロセスはinitでしたが、それに代わって launchd が導入されました。
OS X 10.5(Leopard)では、一応残っていた /etc/rc が無くなりました。
起動時にデーモンを立ち上げたり、デーモンのコントロールに /etc/rc が使えないので、launchd の仕組みを調べてみました。
PIDって何?
全てのプロセスに先立って実行されるプロセス。
まず、Linuxが起動するまでの大まかな流れを概観しておきましょう。
マシンの電源をオンにすると、BIOSが起動して制御をハードディスクのMBRなどに移管します。Linuxの場合、MBRに書き込まれているブートローダ(LILOやGRUB)を起動し、このブートローダからカーネルを呼び出すのが一般的です。今回、BIOSからカーネルの起動までには深入りしません。とにかく、何らかの方法でカーネルが動き出した後からを追うことにします。
BIOSって何?OSの前に起動して、ハードウェアの管理を行う。
BIOS(バイオス)とはBasic Input Output Systemの略称で、マザーボード上のROMに搭載されているプログラムです。 Windows等のOSが起動する前に動作し、パソコン本体に搭載されているハードウェア(キーボード・マウス・メモリ・CPU・ハードディスク等)の管理と制御を行います。
MBRって何?マスターブートレコード。どのOSを起動するか?」や「どんな流れでOSを起動するか?が書いてある。
カーネルが起動された後の流れを挙げると、
- 各種デバイスなどの初期化
- initプログラムなどの起動
- ブート時の処理
- rcスクリプトの実行
といった処理が行われています。
デバイスなどの初期化
これは、カーネルに組み込まれた、あるいはモジュールとしてロードしたデバイスドライバを使って行います。各種デバイスの中には、電源投入直後の動作が不定と決まっているものがあります。いずれにしても、実際に利用する方法に合うように設定を変えなければなりません。例えば、シリアルポートなら通信速度、画面ならば表示モードといった部分です。
initプログラムなどの起動
Linux上のプログラムとして最初に実行されるのは、initプログラムです(編注)。initプロセスは、psコマンドで必ずPIDが「1」と表示されます。
$ ps ax PID TTY STAT TIME COMMAND 1 ? S 0:04 init 2 ? SW 0:00 [keventd] (中略) 493 ? S 0:00 /sbin/dhcpcd -n eth0 580 ? S 0:00 syslogd -m 0 (中略) 890 ? S 0:00 crond 954 ? S 0:00 xfs -droppriv -daemon 1103 tty2 S 0:00 /sbin/mingetty tty2 (中略) 21157 ? S 0:00 smbd -D 21162 ? S 0:01 nmbd -D (中略) 32610 ? S 0:00 /usr/sbin/sshd 32611 pts/0 S 0:00 -bash 32646 pts/0 R 0:00 ps ax
Linux上で動くすべてのプログラムは、このinitプログラムから実行されます。ユーザーが実行するプログラムはシェルから実行されますが、そのシェルも元をたどればinitから実行されたプログラムです。そのため、親子関係になぞらえて「initはすべてのプロセスの親である」と表現したりします。
initに続いて、キャッシュマネージャやスワップを制御するプログラム、ハードディスクへのデータ書き込みを制御するプログラムなどが実行されます。こうした、縁の下の力持ち的に各種のサービスを提供するプログラムを「デーモン」と呼びます。
initは何をしているのか?
では、initがプログラムを実行する方法を見ていきましょう。
initの動作を定義するinittab
initがどのような処理をしているのかは、/etc/inittabを見れば分かります。このファイルは、initが行うべき処理を定義しているもので、各種confファイルのようなものだと考えればよいでしょう。
/etc/inittabの各行は、
id:runlevel:action:process
という書式になっています。各部の意味は次のとおりです。
id
エントリの識別子。ユニークな文字列(1~4文字)でなければならない。
runlevel
ランレベルの指定で、1から6までの数字が使える。「2345」など、複数を同時に指定できる。省略するとデフォルトランレベルとなる。
action
プロセスの起動あるいは終了時の動作。actionの内容は表を参照。
action | 意味 |
---|---|
respawn | processで指定したプロセスを起動し、終了したら再起動する |
wait | processで指定したプロセスを起動し、終了を待つ |
once | 指定したランレベルへの移行後に1度だけ実行 |
initdefault | デフォルトランレベルの指定 |
sysinit | ブート時に実行するプロセス |
powerfail | UPSが電源切断を検出したときに実行するプロセス |
powerokwait | UPSが電源オンを検出したときに実行するプロセス |
ctrlaltdel | [Ctrl]+[Alt]+[Delete]キーが押された場合 |
表 指定可能なactionの一部 |
process
起動するプログラム。
構文が分かると、/etc/inittabの各行の意味も理解できるでしょう。
# デフォルトランレベル(ランレベル3を指定) id:3:initdefault: # ブート時の処理(/etc/rc.d/rc.sysinitを実行) si::sysinit:/etc/rc.d/rc.sysinit # ランレベルごとの処理(各ランレベル用のrcスクリプトを実行し、その終了を待つ) l0:0:wait:/etc/rc.d/rc 0 l1:1:wait:/etc/rc.d/rc 1 l2:2:wait:/etc/rc.d/rc 2 l3:3:wait:/etc/rc.d/rc 3 l4:4:wait:/etc/rc.d/rc 4 l5:5:wait:/etc/rc.d/rc 5 l6:6:wait:/etc/rc.d/rc 6 # 1度だけ実行される処理(/sbin/updateを実行) ud::once:/sbin/update # [Ctrl]+[Alt]+[Delete]キーを押したときの処理 ca::ctrlaltdel:/sbin/shutdown -t3 -r now # 電源オフ時の処理 pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down" # 電源オン時の処理 pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled" # 端末制御(ランレベル2~5で/sbin/mingettyを実行。終了されると再実行) 1:2345:respawn:/sbin/mingetty tty1 2:2345:respawn:/sbin/mingetty tty2 3:2345:respawn:/sbin/mingetty tty3 4:2345:respawn:/sbin/mingetty tty4 5:2345:respawn:/sbin/mingetty tty5 6:2345:respawn:/sbin/mingetty tty6 # ランレベル5時のログイン処理(/etc/X11/prefdmを実行。終了されると再実行) x:5:respawn:/etc/X11/prefdm -nodaemon
/etc/inittabには、initの実行から各種デーモンの起動を経て、ログインプロンプトが表示されるまでの処理が記述されているのが分かります。詳細は後述するとして、大まかな流れを挙げると、
- ブート時の処理を行うスクリプトの実行
- ランレベルに応じたスクリプトの実行
actionにwaitが指定されているので、スクリプトの実行が終了するまで待つ - 端末制御
ログインプロンプトを出す。actionにrespawnが指定されているので、終了(ログオフ)された場合は再実行(ログインプロンプトを出力)する
といったことを行っています。
ブート時の処理
Red Hat Linux 7.2では、ブート時に/etc/rc.d/rc.sysinitというスクリプトを実行するようになっています。/etc/inittabの、
# System initialization. si::sysinit:/etc/rc.d/rc.sysinit
という行で定義されています。ざっとその内容を見ると、
- ネットワークの初期化
- ホスト名の設定
- Welcomeバナーの表示
- /procファイルシステムのマウント
- カーネルパラメータの設定
- クロックの設定
- keymapの読み込み
- システムフォントの読み込み
- スワップの有効化
- USBの初期化
- 必要に応じてfsckの実行
- quotaの有効化
- ハードディスクパラメータの設定
- カーネルモジュールの読み込み
- RAIDデバイスの組み込み
- ファイルシステムのマウント
といった処理が行われています(編注)。これらの処理を行った後、指定されたランレベルに対応したrcスクリプト群を実行して、ログインプロンプトを出すことになります。
起動後の仕事
ブート完了後にinitが行うことは、親プロセスを持たなくなったプロセスと端末の制御があります。
プロセスは自分自身が_exitシステムコールを実行し、親プロセスがwait系システムコールを実行することで初めて終了します。ところが、何かの拍子に親プロセスが止まってしまったりすると、_exitしたまま永遠にwaitを待つことになります。こうしたプロセスを探して、本来の親プロセスに代わってwait系システムコールを実行するわけです。
端末制御は、/etc/inittabの記述に従って、各端末(仮想端末を含む)に標準入出力やエラー出力を割り当て、gettyと総称されるプログラムを起動します。このgetty(Red Hat Linux 7.2ではmingetty)がログインプロンプトを出し、ここでようやくユーザーがログインできるようになるわけです。
https://atmarkit.itmedia.co.jp/ait/articles/0204/02/news002.html
すべてのUnixシステムで、特別なプロセス識別子である1を与えられる1つのプロセスが存在する。 これは他のすべてのプロセスよりも前にカーネルによって起動され、他に親となるプロセスを持たない他のプロセスすべての親となるものである。 そのため、このプロセスは他のプロセスにはできないようなたくさんのことをすることができる。 そして、ブート時にユーザースペースを立ち上げ、維持するといった、他のプロセスが責任を負えないようなことに対しても責任を負うのである。
歴史的に、LinuxではPID 1として振る舞うソフトウェアは由緒あるsysvinitパッケージであった。一方で、sysvinitはかなりの時間、衰えを感じさせていた。 多くの代替案が提案されてきたが、そのうちのたった一つだけが実際に成功した。それはUpstartであり、これは今のところすべてのメジャーなディストリビューションにまで到達することができた。
すでに述べたとおり、initシステムの主たる責任はユーザースペースの立ち上げにある。 そして、優れたinitシステムはそれを速くおこなうものである。 不幸なことに、伝統的なSysV initシステムは特に速いというわけではない。
素早く、そして、効果的なブートアップには2つのことが重要になる
- 小さくスタート
- よりパラレルにスタート
これは何を意味するのか?小さくスタートは、より少ないサービスを起動させる、あるいは、サービスが実際に必要とされるまではその起動を遅らせることを意味する。 サービスの中には、すでに知られているように、早かれ遅かれ必要とされるものもある(syslog, D-Busシステムなどである)が、その他の多くはこのケースには当てはまらない。 例えば、bluetoothdはBluetoothドングルが実際にプラグインされるか、アプリケーションがD-Busインターフェイスとトークしたいと思わない限りは、実行されている必要はない。 同じことはプリンティングシステムにも言える。つまり、マシンに物理的にプリンターが接続されているか、アプリケーションが何かを印刷したいと思うまでは、CUPSといったプリンティングデーモンが起動している必要はないのである。 Avahiもそうである。もしマシンがネットワークに繋がれていない場合、アプリケーションがAPIを使いたいと思わない限りは、Avahiが起動している必要がない。 SSHにだって同じことが言える。誰もマシンに接続しようとしなければ、SSHが起動している必要はなく、最初に接続が来たときに起動させればよい。(そして、sshdがリスンしているかもしれないマシンの殆どは誰かが数カ月にたった1度といった感じで接続していると認めよう。)
よりパラレルにスタートは何かを起動させなければならないなら、スタートアップを(sysvinitがするように)シリアルでおこなうべきではなく、すべてを同時に実行すべきであるということを意味する。こうすることで、使用可能なCPUやディスクIOの帯域を織り交ぜて、全体としてのスタートアップ時間を最小化できる。
UNIXとMAC OSって違うの?MAC OS =UNIXと考えて良さそう。Linuxは別そう。
macOSがUNIX系と呼ばれる際には4通りの意味があると考えて良いと思います。
- POSIX準拠
- BSD由来カーネル
- NeXTStep由来GUI
- UNIX標準ツールが入っていること。
1.はUNIXであると公式に名乗ってよい公認であります。これは商用UNIXが乱立した時代にUNIXの最大公約数的な部分を集めた規格と呼んで良いでしょう。アメリカ政府は以前はPOSIX準拠を調達要件にしていました。(そのため、古いWindows NTも準拠しています。) これは、LinuxやFreeBSDなどのオープンソースOSに対するアドバンテージです。
2. macOSのカーネルはdarwinですが、Mach OS 3.0+BSDが由来になっています。Mach OSはマイクロカーネルであり、モノリシックカーネルのOSよりも安定性が高いと言われています。このMach OSはスティーブ・ジョブズがアップル復帰前に作っていたNeXTのコアでもあります。
3. UNIXの標準GUIはXウィンドウであり、その上のウインドウマネージャーは規定されていません。それに対して、macOSはNeXTStep由来のGUIを標準UIとして使用しています。Xウィンドウに関しては独自のアプリケーションを使用しています。これはWindows+Xサーバと同じ構成と言えます。
NeXTStepのGUIはObjectiveCで構築され、設計された時点では最も洗練されたGUIとも言われていました。見た目はだいぶ変わったのですが、現在のmacOSでもライブラリの接頭文字がNSである等、痕跡は残っています。
4. UNIX標準ツールが入っていることはUNIXをUNIXらしく使う人たちにはとても重要です。現在はコンパイラ等は後からインストールになっていますが、それさえ入れてしまえば、通常のUNIXでやりたいことはだいたい問題なくこなせます。
さて、これらのUNIXとしての特徴・アドバンテージを持ちつつ、他の商用/オープンソースUNIXと違う点が一つあります。それは、クライアントOSとして普及しているというところから来るのですが、Microsoft OfficeやAdobe Creative Cloudなどのアプリケーションがネイティブで動作する点です。Windowsに比べれば少ないのですが、中心的なアプリケーションは移植されているのです。
つまり、日常的にこのようなアプリケーションを使いつつ、UNIXとしても使用できるというのがmacOSのアドバンテージと言っても良いと思います。これは3.のNeXTStep由来のGUIと、旧MacOS時代のライブラリのサブセットであるCarbonのおかげとも言えます。
概要
間違いがあるかもというか、誤解を招くかもしれないけど、ざっくりと。
- launchd は init の代わりにPID 1で最初に起動して、初期化やシェルの起動を行う。
- /etc/rc は無くなったけど一部残ってる /etc/rc.common などのrcスクリプトを実行する。
- /etc/rc のrcスクリプトの代わりに launchd.plist を使う。
- cron の代わりに時刻をトリガーにしたプロセスの起動をする。
- inetd/xinetd の代わりにネットワークのポートを監視して、プロセスの起動を行う。
- ファイルやフォルダを監視し、ファイルの追加や更新をトリガーにして、プロセスの起動を行う。
デーモンをサービスとして登録する
rcスクリプトの代わりにXMLで記述する launchd.plist を作ります。
$ ls -al /Library/LaunchDaemons/ total 40 drwxr-xr-x 7 root wheel 238 6 21 07:07 . drwxrwxr-t+ 57 root admin 1938 12 8 2009 .. -rw-r--r-- 1 root wheel 694 5 29 2009 de.jinx.SmartSleepDaemon.plist -rw-r--r-- 1 root wheel 412 6 21 03:13 kanonji.gnump3d.plist lrwxr-xr-x 1 root wheel 66 10 22 2009 org.freedesktop.dbus-system.plist -> /opt/local/Library/LaunchDaemons/org.freedesktop.dbus-system.plist -rw-r--r-- 1 root wheel 474 5 26 2008 org.pqrs.KeyRemap4MacBook.load.plist -rw-r--r-- 1 root wheel 470 5 25 2008 org.pqrs.PCKeyboardHack.load.plist
置き場所はここです。
/Library/LaunchDaemons/ は root:wheel 所有なので、ここにファイルを作る時は sudo します。
$ less kanonji.gnump3d.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>KeepAlive</key> <true/> <key>Label</key> <string>kanonji.gnump3d</string> <key>ProgramArguments</key> <array> <string>/usr/bin/gnump3d</string> <string>--fast</string> </array> <key>RunAtLoad</key> <true/> </dict> </plist>
最近インストールした GNUMP3d 用の launchd.plist を作ったので、例にしてみます。
key | 説明 | 必須 |
Label | launchd のジョブの名前。ユニークな名前を付ける。必須か慣例か分からないけど、ファイル名もこのLabelに.plistという拡張子を付けたものにする。 | 必須 |
ProgramArguments | 実行するプログラムとオプションや引数を、例の通りarrayノードに入れ子で書く。 | 必須 |
KeepAlive | trueなら常に起動するようにlaunchdががんばる。起動したプロセスをkillしてもすぐ立ち上げなおされる。bool型以外にdictionary型で細かい指定が可能らしい。 | falseがデフォルト値 |
RunAtLoad | true なら launchd に launchd.plist がロードされたタイミングで起動する。 | 不明 |
この設定で、OSを起動したら /usr/bin/gnump3d –fast が呼ばれて起動します。
KeepAlive true なら、プロセスが落ちても立ち上げなおしてくれるみたいです。
$ launchctl load /Library/LaunchDaemons/kanonji.gnump3d.plist
launchd.plist を書いたら、launchd のCUIツールである launchctl でロードします。
RunAtLoad があるので、ロードしたら gnump3d が起動しました。
ちなみに、RunAtLoad true は一応書いたけど、もしかすると KeepAlive true なら不要かもしれない。
KeepAlive true だけで launchd.plist をロードしても、gnump3d は起動してました。
OS起動時はどうなるかはまだ未確認です。
launchd.plistを書くGUIツール
launchd.plist はXMLで、更にkeyノードの次にそのノードに対応した値ノードが来るという、ちょっと変わった文法なので、あまり手で書きたくはありません。
オープンソースで Lingon.app というツールがあるので、これを使います。
ただ、更新が2008年で止まってるので、Lingonで作った launchd.plist を一部修正して使います。
具体的には、指定して無いのに WatchPaths というキーと QueueDirectories というキーが指定されているってだけなんですけど。
値ノードが空なので、たぶん問題は無いと思うけど一応その2つは vi で消しました。
上から順に、(1)で Label を書いて、(2)で ProgramArguments を書いて、(3)で KeepAlive と RunAtLoad を含むいくつかのキーが設定できるだけって感じです。
新しい設定を作る時、最初にタイプを選ぶダイアログがでます。
この説明は次で。
Daemon と Agent
ざっくりと言えばこんな感じです。
~/Library/LaunchAgents/ | My Agents | ホームディレクトリ内なので、自分用のエージェント |
/Library/LaunchAgents/ | Users Agents | ユーザで共有のエージェント |
/Library/LaunchDaemons/ | Users Daemons | デーモン |
Lingon のダイアログで、3つのタイプがありましたが launchd.plist の置き場所によって変わります。
gnump3d は名前の最後に d がある様に、デーモンなので /Library/LaunchDaemons/ に設置しました。
/System/Library/LaunchAgents/ と /System/Library/LaunchDaemons/
Lingon の左側にも SYSTEM AGENTS と SYSTEM DAEMONS とあります。
これはたぶん、OS X のシステムの設定と思われるので、ユーザーが追加したり変更したりというのは、基本的に必要ないと思います。
ここにあるという事を覚えておけば、トラブル時に解決の糸口を探れるかもしれません。
launchctlの使い方
$ launchctl list PID Status Label 3440 - 0x112230.launchctl 3388 - [0x0-0x166166].org.lingon.Lingon 2597 - 0x112950.bash 2596 - 0x112860.login 2533 - 0x111390.bash 2532 - 0x111ae0.login 592 - [0x0-0x42042].org.mozilla.thunderbird 574 - [0x0-0x3f03f].com.apple.AppleSpell 中略 - 1 kanonji.gnump3d 以下略
ロード済みの設定を一覧に表示します。
PID に数字が書いてあればプロセスが起動してます。
Status の意味はちょっと調べきれてません。
$ launchctl load /Library/LaunchDaemons/kanonji.gnump3d.plist $ vi /Library/LaunchDaemons/kanonji.gnump3d.plist $ launchctl unload /Library/LaunchDaemons/kanonji.gnump3d.plist $ launchctl load /Library/LaunchDaemons/kanonji.gnump3d.plist
ロードとアンロードは launchd.plist を指定します。
launchd.plist を変更した場合、一旦 unload して再度 load が必要です。
$ launchctl start kanonji.gnump3d $ launchctl stop kanonji.gnump3d
start や stop*1 はジョブラベルを指定します。
より詳しい使い方はDocumentation Archive。
非推奨な手段
起動項目(SystemStarter)
/Library/StartupItemsディレクトリ入れるもので、Windowsで言うところのスタートアップの様なものだったと思います。
StartupItems も LaunchDaemons 知らなかったので、最初はこれを混同しちゃってましたが、launchd とは別の仕組みみたいです。
OS X 10.4からは起動項目に代わり、launchd の使用が推奨されています。
inetdおよびxinetdデーモン
OS X 10.4 からは launchd の使用が推奨されてます。
cron
cronについてはドキュメントが見つかりませんでした。
参考
*1:KeepAlive true な設定をしたプロセスは stop しても直ぐ立ち上げ直されます。止めるには unload です。