はじめに多くの問題はバックグラウンドで実行するプログラムで解決できます。これらのプログラムにはグラフィカルユーザインターフェイス(GUI)がなく、ユーザとは間接的にのみ対話します。たとえば、次のようになります。
このテクニカルノートでは、バックグラウンドで実行するプログラムの作成に関する問題について説明します。まず、作成できるバックグラウンドプログラムのタイプに関する形式的な定義から始めます(Daemonomicon)。次に、バックグラウンドプログラムが遭遇する異常な実行状況(実行コンテキスト)と、Mac OS Xが階層化フレームワークを使ってそのような状況が引き起こす問題を管理する方法(階層化フレームワーク)について説明します。また、設計に関するいくつかの一般的な推奨事項(設計の考慮事項)を皮切りに、バックグラウンドプログラムを作成する方法についてのアドバイス、さらにバックグラウンドプログラムの起動(起動)およびプログラムのさまざまなコンポーネント間で通信する方法(デーモンIPCに関する推奨事項)についてのアドバイスも提供します。最後に、コーディングに関していくつかの具体的な推奨事項(コーディングに関する推奨事項)や種々雑多なヒントとこつ(ヒントとこつ)について述べます。 このテクニカルノートを読む前に、『System Startup Programming』および『Multiple User Environments』の正式な文書に目を通してください。 重要:このテクニカルノートでは、複雑なトピックを明確に説明するよう努力しています。多くの場合、あることを理解してもらうための最善の方法は、具体的な例を示すことです。これらの例は、システムを実装する方法に関して詳細な情報を示します。重要なのは、将来変更されるかもしれない特定の実装の詳細ではなく、例の目的に集中することです。 実装が旧システムで異なっていたり、将来変更される可能性がある場合は、文中にそのような変更について警告します。 このテクニカルノートに掲載した例はMac OS X 10.4以降に対応したものです。 Daemonomiconバックグラウンドで動作するプログラムを表すために、さまざまな用語が使用されています。多くの場合、同じ用語でも、いろいろな人がそれぞれに異なる意味で使っています。このセクションでは、以降のセクションで使用する用語を定義します。 バックグラウンドプログラムは、特にGUIを表示することなくバックグラウンドで動作するプログラムです。このカテゴリは、デーモン(システム全体を対象とするバックグラウンドプログラム)とエージェント(特定ユーザのために動作)に分けられます。次の2つのセクションでは、これらの二次分類について詳しく説明します。 デーモンデーモンは、全体システムの一部としてバックグラウンドで動作する(つまり、特定のユーザとは結びついていない)プログラムです。デーモンではGUIを表示できません。もっと明確にいえば、ウインドウサーバに接続することが許可されていません。Webサーバはデーモンの好例です。 さまざまなデーモンがありますが、それぞれについては以降のセクションで説明します。 注:従来のUNIXに関する予備知識がある場合は、Mac OS Xでデーモンを起動する方法としては、 起動項目起動項目は、起動時にSystemStarterプログラムに起動されるデーモンです。サードパーティの起動項目は、 Mac OS X 10.4からは、起動項目に代わり、launchdデーモンの利用が推奨されています。 重要:互換性の理由から、Mac OS X 10.4以降では、ネットワークが起動して稼働するまで、システムは起動項目を起動しません。ネットワークが立ち上がるまで少し時間が(数十秒)かかることもよくあるので、起動項目が以前のシステムよりも起動が遅いことに気付くかもしれません。この問題を回避する最善の方法は、launchdデーモンを使用するように切り替えることです。 注:Mac OS X 10.3.xの「アカウント」環境設定パネルでは、ログイン項目を指して「起動項目」という用語を使用していました。この紛らわしい用語の使い方はMac OS X 10.4以降では修正されました。 mach_initデーモンmach_initデーモンは、Machの初期化プロセス(Mac OS X 10.3.x以前では inetdおよびxinetdデーモンinetdデーモンおよびxinetdデーモンは、インターネットスーパーサーバ(もともとは
Mac OS X 10.4からは、inetdデーモン群とxinetdデーモン群の代わりにlaunchdデーモンの使用が推奨されています。 inetdおよびxinetdデーモンの詳細については、『UNIX Network Programming』およびxinetd Webサイトを参照してください。 launchdデーモンMac OS X 10.4で、アップルは新しいデーモン起動プログラムとしてlaunchdを導入しました。このプログラムが、システムのほとんどのデーモンを起動する責任を引き受けます。厳密にいうと、launchdはmach_initデーモンと起動項目を起動する責任を負います。 しかし、launchdデーモンは高度なプロパティリストファイルで設定する新しいタイプのデーモンです。このファイルを使用することで、さまざまな基準(リッスンしているソケットへの接続、ファイルシステム内の項目の変更、定期的になど)に基づいてデーモンを起動できます。詳細については、launchd.plistのmanページを参照してください。 サードパーティのlaunchdデーモンをインストールするには、 launchdデーモンは他のタイプのデーモンとは多少異なります。たとえば、launchdデーモンは自身をデーモン化してはなりません。詳細については、launchdのmanページを参照してください。 エージェントエージェントは、特定ユーザのためにバックグラウンドで動作するプロセスです。エージェントが役に立つのは、ウインドウサーバへの接続など、デーモンにはできないことができるからです。カレンダーモニタリングプログラムは、次の理由からエージェントの好例です。
エージェントとデーモンの違いは、エージェントでは必要に応じてGUIを表示できるのに対し、デーモンではそれができないことです。エージェントと通常のアプリケーションの違いは、エージェントでは通常はGUIを表示しない(または非常に限られたGUIを表示する)ことです。 エージェントは長年にわたって、さまざまな名前を付けられてきました。たとえば、BOA (background-only application)、FBA (faceless background-only application)、UI要素(エージェントは多少のGUIを表示するけれども、メニューバーを備えた本格的なアプリケーションではないというニュアンス)などがあります。これらの名前は、実際のところ、ここでの説明とは無関係です。何が重要で、何が各種のエージェントの違いになるのかと言えば、エージェントを起動する方法です。 注:従来のMac OSに慣れているユーザは、FBAやBOAをデーモンと言い表すかもしれませんが、Mac OS Xではこれは正しくありません。Mac OS XではFBAやBOAはエージェントであり、エージェントはデーモンとはまったく異なります。 注:洞察力の鋭い読者は、システムの ログイン項目ログイン項目は、ユーザがGUIを使ってログインしたときに起動されます。ログイン項目としては、開くことが可能な任意の項目を指定できますが、一般的にはアプリケーションかエージェントです。 ログイン項目をインストールするための唯一のサポートされている方法は、「システムイベント」プロセスにアップルイベントを送信することです。サンプルコードプロジェクト「LoginItemsAE」に、その方法の1つを示します。 グローバルログイン項目グローバルログイン項目は、ユーザがログインしたときに起動されるログイン項目です。グローバルログイン項目をインストールするということは、システムの全ユーザに対して当該ログイン項目をインストールすることとほぼ同じです。ユーザがログインするたびに、loginwindowがそのユーザのログイン項目とすべてのグローバルログイン項目を起動します。 グローバルログイン項目をインストールするためのサポートされている方法はありません。この機能が必要な場合は、Developer Technical Support (DTS)にお問い合わせください。 システムログイン項目システムログイン項目は、ユーザがログインする前に、それぞれのGUIログインセッションの中で起動されるグローバルログイン項目です。ログイン画面が表示されているときにウインドウサーバを使用する必要がある場合は、システムログイン項目が役立ちます。たとえば、リモートユーザがログインウインドウにログインできるようにするスクリーン共有プログラムなどです。 システムログイン項目をインストールするためのサポートされている方法はありません。この機能が必要な場合は、Developer Technical Support (DTS)にお問い合わせください。 mach_initエージェントmach_initエージェントは、特定ユーザとの関連で実行される点を除けば、mach_initデーモンに似ています。GUIユーザにログインするプロセスの一部として、loginwindowに( 重要:mach_initエージェントは、非GUIログインセッションでは起動されません。 launchdエージェントlaunchdエージェントは、特定ユーザのログインセッションのために実行される点を除けば、launchdデーモンに似ています。ユーザのログインプロセスの一部として、loginwindowに( サードパーティのlaunchdエージェントをインストールするには、 現在のシステムソフトウェアにおける制限(r. 4255854)に起因し、launchdエージェントは思うほどには有用ではありません。具体的にいうと、システムはログインセッションごとではなく、システムのユーザごとに 実行コンテキスト従来のBSDプログラミングに慣れていれば、プロセスに対応するユーザID(UID。しばしば「プロセスの所有者」という)については知っているでしょう。従来のBSDシステムでは、これらのUIDに応じて当該プロセスの能力が決まります。UIDが一致する2つのプロセスの能力は多かれ少なかれ同じであると想定できます。 これはMac OS Xには当てはまりません。プロセスのコンテキストには、その能力を大きく左右する要素が他にもあります。そのため、たとえば、UIDがコンソールにログインしたユーザのUIDに設定されるデーモンは、そのユーザが起動したアプリケーションと同じではありません。 次のセクションでは、プロセスのコンテキスト要素と、それらがバックグラウンドプログラムに与える影響について説明します。 UIDプロセスのUID(その実効UID (EUID)、実UID (RUID)、および保存UID (SUID))は、プロセスのコンテキスト要素として最もよく知られています。これらのUIDは、プロセスのさまざまな能力を制御しますが、大部分がシステムのBSD部分(ファイルシステム、ネットワーク、BSDプロセス制御)に集中しています。たとえば、プロセスのファイルを開く能力はプロセスのEUIDに応じて決まり、別のプロセスにシグナルを送る能力は送信元のEUIDおよび送信先のEUIDとRUIDに応じて決まります。 Machブートストラップの基礎多くのMac OS Xサブシステムは、中央サービスとMachメッセージを交換することで機能します。このようなサブシステムが機能するには、サービスを見つけられる必要があります。これは通常、Machブートストラップサービスを使用して実行されます。これによって、プロセスはサービスを名前で検索できます。プロセスはすべて、その親からブートストラップサービスの参照を継承します。 この動作を把握するには、 リスト1:「ターミナル」からのBootstrapDump $ BootstrapDump "com.apple.KernelExtensionServer" "com.apple.FontObjectsServer" by "/System/Library/Frameworks/Ap[...] "com.apple.SystemConfiguration.configd" by "/usr/sbin/configd" "com.apple.audio.coreaudiod" by "/usr/sbin/coreaudiod" is inact[...] "com.apple.CoreServices.coreservicesd" by "/System/Library/Core[...] "com.apple.DirectoryService" by "/usr/sbin/DirectoryService" "com.apple.DiskArbitration.diskarbitrationd" by "/usr/sbin/disk[...] "com.apple.distributed_notifications.2" by "/usr/sbin/distnoted "com.apple.system.hdiejectd" by "/System/Library/PrivateFramewo[...] "com.apple.IIDCAssistant" by "/Library/Audio/Plug-Ins/HAL/iSigh[...] "com.apple.KerberosAutoConfig" by "/sbin/kerberosautoconfig -x"[...] "com.apple.system.Kernel[UNC]Notifications" by "/usr/libexec/ku[...] [...] "com.apple.dock.server" [...] "com.apple.iChatAgent" [...] ご覧のように、ブートストラップサービスを通じて多数のサービスが公開されています。これらはほぼすべて外部に対して閉じています。たとえば、“com.apple.dock.server”サービスにメッセージを直接送信することは期待されていません。むしろ、フレームワークを通じてエクスポートされているルーチン( ブートストラップ名前空間前の例には興味深い点があります。 このような問題の解決策は、ブートストラップサービスが複数のブートストラップ名前空間を作成できることです。ログインセッションはそれぞれにブートストラップ名前空間を持っており、当該セッション内で実行されているプロセスはすべて当該空間への参照を継承します。そのため、Dockがそのサービスを登録すると、その登録はログインセッションの名前空間に入ります。そのセッション内にある他のプロセスはすべて同じ名前空間への参照を継承するため、Dockのサービスを参照することができます。他のログインセッションにあるプロセスは別の名前空間を参照するので、このサービスを参照できません。 注:ただし、このようなプロセスでも、その名前空間に登録されているDockの別のインスタンスを参照できます。 GUIおよび非GUIセッション単位ブートストラップ名前空間の違いは注目に値します。GUIセッション単位ブートストラップ名前空間は、ユーザがGUI経由でログインするときに、GUIインフラ(loginwindowおよびウインドウサーバ)によって作成されます。非GUIセッション単位ブートストラップ名前空間は、ユーザが 注:Mac OS X 10.3より前は、非GUIログインセッションがグローバルブートストラップ名前空間で実行されていました。 注: 名前空間階層ブートストラップ名前空間は階層的に配置されています。グローバルブートストラップ名前空間は1つしかありません。システムがログインセッションの新しいブートストラップ名前空間を作成すると、このセッション単位ブートストラップ名前空間はグローバル名前空間を親として扱います。当該セッションで実行されているプロセスがサービスを登録すると、サービスはセッション単位名前空間のみに出現するので、当該セッション内の他のプロセスにのみ認識されます。しかし、グローバル名前空間に登録されたサービスは、すべてのプロセスに認識されます。 これは実質的には、次のことを意味します。
リスト2:グローバルブートストラップ名前空間のダンプ $ sudo BootstrapDump 1 Password:******** "com.apple.KernelExtensionServer" "com.apple.FontObjectsServer" by "/System/Library/Frameworks/Ap[...] "com.apple.SystemConfiguration.configd" by "/usr/sbin/configd" "com.apple.audio.coreaudiod" by "/usr/sbin/coreaudiod" is inact[...] "com.apple.CoreServices.coreservicesd" by "/System/Library/Core[...] "com.apple.DirectoryService" by "/usr/sbin/DirectoryService" "com.apple.DiskArbitration.diskarbitrationd" by "/usr/sbin/disk[...] "com.apple.distributed_notifications.2" by "/usr/sbin/distnoted "com.apple.system.hdiejectd" by "/System/Library/PrivateFramewo[...] "com.apple.IIDCAssistant" by "/Library/Audio/Plug-Ins/HAL/iSigh[...] "com.apple.KerberosAutoConfig" by "/sbin/kerberosautoconfig -x"[...] "com.apple.system.Kernel[UNC]Notifications" by "/usr/libexec/ku[...] [...] $ sudo BootstrapDump 1 | grep dock 重要:プログラムでは、グローバルブートストラップ名前空間をダンプする機能に依存しないでください。先の例で利用しているテクニックは、アップルが長期的にサポートする予定のものではありません。デバッグと説明にはふさわしいものですが、これに依存したプログラムは出さないでください。 これを見ると、“com.apple.KernelExtensionServer”サービスがグローバルブートストラップ名前空間に登録されており、システムのすべてのプロセスから認識できることが分かります。他方、(リスト1の)“com.apple.dock.server”サービスはログインセッション単位名前空間に登録されており、その名前空間を使用しているプロセスのみが認識できます。 したがって、次のルールを覚えておきましょう。
これらのルールに従わないと、正常に機能しているように見えても、いずれ分かりにくい問題に出くわすことになります。次のセクションでは、そのような問題の一例を示します。これは一例にすぎませんが、さらに多くの問題が潜んでいます。このような問題を避ける唯一の保証された方法は、上記のルールを順守することです。 名前空間の探索これまでの説明は理論の上でのことのように思えるかもしれません。また、これがどのように影響するのか思われていることでしょう。ここでは、ブートストラップ名前空間がアプリケーション機能にどのように作用するかについて、具体的な例を検討することではっきり分かるように説明します。
重要:sshセッションから実行されているDockBrowserがGUIを表示できていること自体、実に意外なことです。これが可能なのは、DockBrowserがグローバルウインドウサーバサービスに接続されているためです。この件については、次のセクションで詳しく説明します。 図1:DockにおけるDockBrowser
図2:sshを通じて起動されたDockBrowser
ウインドウサーバウインドウサーバ(10.4では 重要:ウインドウサーバは単にウインドウを管理するだけではありません。ユーザインターフェイスのないアプリケーション(バックグラウンド専用アプリケーションなど)でもウインドウサーバに依存しています。 ウインドウサーバが提供するサービスの大部分は、Machメッセージを使用して実装されています。そのため、ウインドウサーバを確実に使用するには、有効なセッション単位ブートストラップ名前空間の参照を継承する必要があります。これは前述のルールから予期される結果です。 しかし、意外なのは、アプリケーションをGUIログインセッション以外(つまり、アプリケーションがグローバルブートストラップ名前空間、または非GUIセッション単位ブートストラップ名前空間の参照を継承している場合)から実行しても(多少なりとも)動作することです。これは、ウインドウサーバがグローバルブートストラップ名前空間でそのサービスを公開しているからです。これはグローバルウインドウサーバサービスと呼ばれています。 この不明瞭な動作の理由は歴史の奥底に失われています。しかし、いくらかでも動作するという事実は、ほとんど無意味です。というのも、真に有益であることを妨げる重要な問題点があるからです。以下のセクションでは、これらの問題点について詳しく説明します。 警告:アップルはMac OS Xの今後のリリースで、グローバルウインドウサーバサービスを使用不可にする予定です。グローバルウインドウサーバサービスを利用する新しいコードは書かないようにしてください。このサービスを利用する既存のコードがある場合は、長期的な互換性を確保するために、そのような依存関係を排除する必要があります。この問題を回避するためにコードを構造化する方法の具体的なアドバイスについては、「モノリスの危険」を参照してください。 ウインドウサーバを超えてアプリケーションを正しく機能させるのに必要なサービスは、ウインドウサーバだけではありません。前述したように、Dockサービスも必要ですが、これはGUIセッション単位ブートストラップ名前空間にのみ登録されます。 接続許可コンソールユーザは、GUIログインセッションにコンソールを使用しているユーザです。コンソールデバイス、 プロセスがグローバルウインドウサーバサービスを利用できるのは、そのEUIDが0(rootとして実行)であるか、コンソールユーザのUIDと同じ場合だけです。それ以外のユーザはこのサービスを利用できません。 これを確認するには、ローカルホストにssh接続し、openツールを使って「アドレスブック」を開いてみます。このツールはLaunchサービスを利用します。つまり、ウインドウサーバに依存しているということです。リスト5に、「ターミナル」からこれを実行する例を示します。最初の リスト5:コンソールユーザおよび非コンソールユーザからウインドウサーバへのアクセス
$ ssh ${USER}@localhost
Password:********
Last login:Fri Sep 30 11:50:11 2005
Welcome to Darwin!
$ id
uid=2000(quinn) gid=2000(quinn) groups=2000(quinn), 81(appserveradm), 79(appserverusr), 80(admin)
$ ls -l /dev/console
crw------- 1 quinn quinn 0, 0 Oct 3 21:27 /dev/console
$ open /Applications/Address\ Book.app/
$ logout
Connection to localhost closed.
$ ssh mrgumby@localhost
Password:********
Welcome to Darwin!
$ id
uid=502(mrgumby) gid=502(mrgumby) groups=502(mrgumby)
$ ls -l /dev/console
crw------- 1 quinn quinn 0, 0 Oct 3 21:31 /dev/console
$ open /Applications/Address\ Book.app/
kCGErrorRangeCheck :Window Server communications from outside of \
session allowed for root and console user only
INIT_Processeses(), could not establish the default connection to \
the WindowServer.Abort trap
$ logout
Connection to localhost closed.
この制限によって、グローバルウインドウサービスを確実に使用するのが非常に難しくなります。その理由は次のとおりです。
ウインドウサーバのライフサイクルグローバルウインドウサーバサービスの棺に打つ最後の釘は、ウインドウサーバのライフサイクルに関わります。予想とは裏腹に、ウインドウサーバは常に実行されているわけではありません。むしろ、場合によって(下記の注を参照)、ウインドウサーバは終了されたり、 注:ウインドウサーバが終了する正確な条件は実装固有のものなので、それに依存しないでください。現在(Mac OS X 10.4)、ウインドウサーバは最後のGUIログインセッションが終わると終了しますが、これは必ずしも最後のGUIユーザがログアウトするときではありません。 リスト6のプログラムを使用すると、この動作を見ることができます。このプログラムはプロセスマネージャのルーチン リスト6:ウインドウサーバに接続する小プログラム
#include <Carbon/Carbon.h>
#include <stdio.h>
int main (int argc, const char * argv[])
{
ProcessSerialNumber psn;
fprintf(stderr, "Hello Cruel World!\n");
(void) GetCurrentProcess(&psn);
fprintf(
stderr,
"psn = %#08lx:%#08lx\n",
psn.highLongOfPSN,
psn.lowLongOfPSN
);
fprintf(stderr, "Running runloop\n");
CFRunLoopRun();
return 0;
}
このプログラムをテストするには、別のマシンからsshを使用してログインする必要があります(ウインドウの1つで実行されているプログラムがあると、「ターミナル」はGUIログアウトを保留するからです)。リスト7に、このセッションの様子を示します。 リスト7:GUIログアウトによる死 $ ssh tragic.local. Password:******** Last login:Fri Sep 30 12:07:18 2005 Welcome to Darwin! tragic$ ./WindowServerTest Hello Cruel World! psn = 00000000:0x0e0001 Running runloop Killed Tragic:~ quinn$ echo $? 137 これを自分でやってみるには、次の手順を実行します。
このプログラムが強制終了されたのは、ウインドウサーバがそのサービスを使用しているプロセスを追跡・管理しているからです。ログアウトすると、システム(実際にはloginwindow)がそれらのプロセスを終了しようとします。GUIプロセスごとに、プロセスに対して 非GUIプロセスの場合は、若干事情が異なります。loginwindowはまず、 その結果、ウインドウサーバに接続しているプロセスは、通常のログアウトで存続できません。 この他にも、ウインドウサーバのライフサイクルに関連してデーモンにとって問題となる問題点があります。ウインドウサーバに実際に接続していない場合でも、デーモンがログインセッション単位ブートストラップ名前空間にサービスを登録すると、トラブルが生じる可能性があります。ウインドウサーバが終了すると、ウインドウサーバによって作成されたログインセッション単位ブートストラップ名前空間は無効になります。この詳細については、「ブートストラップ名前空間:補足」を参照してください。 セキュリティコンテキストセキュリティコンテキストは、プロセスと関連するもう1つの実行コンテキストです。セキュリティコンテキストは、Mac OS Xセキュリティサーバ( 注:セキュリティコンテキストとブートストラップ名前空間が互いに結び付いていない唯一のケースは、独自にブートストラップ名前空間を作成した場合だけです。 ほとんどの場合、セキュリティコンテキストはプログラムと直接的な関係はありません。たいてい、足元をすくわれる原因はブートストラップ名前空間です。 他方、セキュリティコンテキストには素晴らしい特性が1つあります。つまり、(
重要:Mac OS X 10.4以降、非GUIログインセッションでは 実行コンテキストの要約表1に、デーモンまたはエージェントの実行コンテキストが起動に使用されるメカニズムによって受ける影響を示します。 表1:コンテキスト相互参照
注記:
図3はその情報を図解したものです。それぞれの四角はプロセスを表します。プロセス名の後に続くテキストは、当該プロセスのUID(丸括弧内のテキスト)であるか、後述の注記の番号(角括弧内のテキスト)です。矢印付きの直線は、2つのプロセス間の親子関係を示します。それぞれのグレーの四角はセキュリティコンテキストの範囲(および関連するブートストラップ名前空間)の範囲を示します。3つのセッション単位セキュリティコンテキストがあり、2つはGUIログインセッション用で(ユーザAおよびB)、1つはsshログインセッション用です(ユーザC)。グレーの四角に入っていない項目は、グローバルセキュリティコンテキストに含まれます。 図3:プロセスの関係
注記:
図3を見ながら、そこに示されているさまざまなプロセスを検討してください。
警告:この例はMac OS X 10.4に基づいています。説明のためだけに掲載しています。システムで実行される各種プロセス間の正確な関係はこれまでにも変更されており、今後も変更される可能性があります。 階層化フレームワークMac OS Xのほとんどの機能は大きなシステムフレームワークによって実装されています。これらのフレームワークの多くは、ブートストラップサービスを利用してルックアップするMachベースのサービスを使用しています。そのため、誤ったブートストラップ名前空間を参照するプログラムからこれらのサービスを呼び出すと、あらゆる問題が引き起こされるおそれがあります。 この問題に対するアップルの解決策は階層化です。フレームワークをレイヤに分割し、レイヤごとに、グローバルブートストラップ名前空間での操作をサポートするかどうかを決めています。基本的なルールとして、CoreService以下のもの(System、IOKit、System Configuration、Foundation)はすべて任意のブートストラップ名前空間で機能し(これらはデーモンセーフフレームワークです)、CoreServicesより上位のもの(ApplicationServices、Carbon、AppKitなど)はすべてGUIセッション単位ブートストラップ名前空間を必要とします。 唯一解決されずに残っている問題は、一部のフレームワークが適切に階層化されていないことです。QuickTimeがその好例です。従来のMac OSから引き継いでいる遺産に起因して、QuickTimeは「コア」と「アプリケーション」フレームワークに明確に階層化されていません。むしろ、1つの大きなフレームワークがあり、どの部分はデーモンから機能するのか文書化されていません。100%の安全を確保する唯一の方法は、これらのフレームワークを使用しないことですが、それは多くの開発者にとって選択肢になりません。ただし、呼び出すルーチンのセットを制限することで、リスクを最小限に抑えられます。この提案については、「危ない橋を渡る」で詳しく説明します。 要約すると、具体的な提案は次のとおりです。
危ない橋を渡るデーモンセーフでないフレームワークをデーモンが使用する場合は、さまざまな問題に遭遇する可能性があります。
そのため、デーモンセーフでないフレームワークとデーモンがリンクしている場合、その一般動作は予測できません。ご使用のマシンでは機能するかもしれませんが、他のユーザのマシン、今後のシステムリリース、または異なる入力データで機能しない可能性もあります。このように、危ない橋を渡ることになります。 デーモンセーフでないフレームワークを呼び出す必要がある場合は、バグを登録して、行おうとしていることとその理由を説明するところから始めてください。アップルでは、今後のシステムソフトウェアを開発するときに、ユーザから得た情報を検討します。 次に、潜在的に安全でないルーチンの呼び出しは最小限に抑えるようにしてください。これによって互換性リスクが軽減されます(排除されるわけではありません)。 フレームワーク相互参照表2に、デーモンセーフなフレームワークを要約しています。 表2:デーモンセーフなフレームワーク
注記:
この表には、Mac OS X 10.4現在のフレームワークをリストアップしています。フレームワークがここに掲載されていない場合は、デーモンセーフでないと見なすのが最善です。 サブフレームワークの状態に関心があり、そのサブフレームワークがここに掲載されていない場合は、当該フレームワークのアンブレラ(包括)フレームワークの状態を調べてください。たとえば、アンブレラフレームワークのCoreServicesがセーフなので、そのサブフレームワークのOSServicesはセーフだといえます。 フレームワークがセーフとして記載されている場合は、今後のシステムリリースでも引き続きセーフです。フレームワークがセーフでないと記載されていても、今後のリリースでセーフになる可能性があります。 設計上の考慮事項このセクションでは、バックグラウンドプログラムを設計するときに考慮すべき重要なポイントを示します。 必要なのか?バックグラウンドプログラムを検討する際にまず考慮すべきことは、そのプログラムがそもそも必要かどうかということです。バックグラウンドプログラムは絶えずリソースを消費するので(もっとも、オンデマンド起動することで消費されるリソースを最小限に抑えることができます)、使用を回避できるのであれば、ユーザのためになります。また、バックグラウンドプログラムは共通障害点を持ち込むので、システムを弱体化させる可能性があります(この点で、デーモンはエージェントに劣り、バックグラウンドプログラムがないほうがまだましです)。他方、バックグラウンドプログラムを使用するのは多くの場合、分散状態管理の複雑なメカニズムを使用するのよりはましです。 バックグラウンドプログラムを実装すると決めたら、エージェントかデーモンのどちらが必要なのか決める必要があります。デーモンを使用する主な理由は、さまざまなログインセッションの複数のプロセス間で何らかの状態を共有する必要があるからです。そのような必要がない場合は、エージェントの使用を検討します。 モノリスの危険DTSに届くよくある質問は、「どうすればデーモンからGUIアプリケーションを起動できるのか」というものです。その答えは、「できない」ということになります。これはMac OS Xの階層化アーキテクチャの直接的な結果です。デーモンは、適切でないコンテキストで実行されるので、GUIアプリケーションを起動できません。たとえGUIアプリケーションを実行できるコンテキストを選べたとしても、どれを選ぶでしょうか。また、コンピュータにloginwindowが表示されている場合、有効なGUIコンテキストがありません。何が起こるのでしょうか。 この問題に対する正しい解決策は、プログラムを複数のコンポーネントに分割し、それぞれを特定の役割に分化させることです。たとえば、全体的な状態を管理するデーモンと、ログインする各ユーザのために動作するエージェントを用意することが考えられます。ユーザに特定の質問に答えてもらう必要がデーモンに生じた場合、デーモンからすべてのエージェントにシグナルを送り、エージェントはローカル情報を使用して、実行すべきことを決定します。たとえば、エージェントはGUIまたは非GUIログインセッションのどちらで実行されるかに応じて、あるいはGUIセッションがアクティブである(ファストユーザスイッチ環境でコンソールを使用している)かどうかに応じて動作が異なります。 このアプローチの優れた面は、デーモンの状態が簡素化されることです。デーモンはただ単に、いずれかのエージェントが質問の答を提供することを知っているだけです。いくつのエージェントがあるのか、エージェントがどのタイプのログインコンテキストで実行されているのか、ユーザとどのように対話しているのかについては関心がありません。 クライアント/サーバモデルプログラムを複数のコンポーネントに分割する場合は、クライアント/サーバモデルを利用するようにしてください。厳密にいえば、クライアントがサーバに接続するのであって、逆ではありません。これは分かりきったことに思われるかもしれませんが、このポイントは忘れやすく、いつの間にか苦境に陥ることがあります。 この文脈で言えば、サーバは共有の全体状態を含むバックグラウンドプログラムであり、クライアントはその状態にアクセスするプログラムです。サーバはエージェントの場合もあれば(すべてのクライアントが同じログインセッションで実行される場合)、デーモンの場合もあります(複数のログインセッションのクライアントに対応する場合、または他のデーモンをクライアントとして扱う場合)。重要なポイントは、サーバとそのクライアントの間に1対多の関係があることです。 このような設計では、サーバがクライアントを検出しようとしたり、クライアントに接続しようとしたりしてはなりません。むしろ、クライアントがサーバに接続しなければなりません。この方式は一般的なプロセス間通信(IPC)のAPIにぴったりです。たとえば、UNIXドメインソケットを使用すれば、デーモンを1つのUNIXドメインソケットをリッスンするようにし、クライアントをそのソケットに接続するようにするのは簡単です。また、サーバは(終了または強制終了による)クライアントの接続解除を簡単に処理できます。逆のことをすると、事態が複雑になります。 起動基本設計が落ち着いたら、各コンポーネントを起動する方法を決める必要があります。図4および図5に、ニーズを評価して最善のアプローチを考える方法を示します。 図4:デーモンの起動
図5:エージェントの起動
このアルゴリズムが示すように、アップルでは可能であれば、launchdを使用してバックグラウンドプログラムを実装することを推奨しています。その理由は次のとおりです。
デーモンIPCに関する推奨事項ほとんどのデーモンはデーモンとそのクライアント間で通信するために、何らかのプロセス間通信(IPC)を使用しています。デーモンを開発する場合は、設計上の最初の判断事項の1つとして、使用するIPCメカニズムを決定することになります。このセクションでは、デーモンでMachメッセージベースのIPCを使用する場合の落とし穴と、代わりにUNIXドメインソケットを考慮すべき理由を説明します。 重要:このセクションの推奨事項は、異なるブートストラップ名前空間のクライアントと通信するデーモンを作成するときに最も重要です。すべてのプロセスが同じブートストラップ名前空間で実行される場合は、これらの推奨事項を無視できます。たとえば、エージェントを開発する場合に、すべてのクライアントが同じログインセッションのGUIアプリケーションであれば、アップルイベントを使って通信してもまったく差し支えありません。 Machに関する考慮事項Mach APIはカーネルに対する最低レベルのインターフェイスです。そのため、システムが進化すると、Mach APIは変更される可能性が最も高いと考えられます。アップルでは一貫して、サードパーティにMach APIを使わないよう推奨しています。このことは他のすべてと同様に、デーモンとエージェントにも当てはまります。 ただし、デーモンとエージェントの場合、ブートストラップ名前空間のことを配慮する必要があるため、Mach APIはさらに厄介なものになります。ブートストラップ名前空間を正しく管理することは可能ですが(簡単だという意見もあります)、Mach APIを避けることで問題を完全に回避するほうが無難です。 Mach APIの使用を避けるためにアップルが提案している一般的な推奨事項には、高レベルのラッパーを使用するほうがよいというアドバイスも含まれています。たとえば、Mach APIを使用してMachメッセージを送受信するのではなく、CFMessagePortを使用して同等のことを実行します。これは一般的には良いアドバイスですが、デーモンまたはエージェントを開発する場合、これでもブートストラップ名前空間の問題に突き当たります。水面下では、 足元をすくわれる可能性が高い高レベルのIPC APIは次のとおりです。
全体的に見れば、Machメッセージを完全に避けるほうが簡単です。Mac OS Xには、代わりに使用できるIPCメカニズムがいろいろ用意されています。個人的には、UNIXドメインソケットが気に入っています。 有用なUNIXドメインソケットUNIXドメインソケットは、通信が必ずコンピュータにローカルなものである点を除けば、TCP/IPソケットに多少似ています。UNIXドメインソケットにアクセスするには、TCP/IPソケットに使用するのと同じBSDソケットAPIを使用します。主要な違いはアドレス形式です。TCP/IPソケットの場合、( サーバがUNIXドメインソケットにバインドすると、ソケットを表すファイルシステムオブジェクトがシステムによって作成されます。たとえば、PPPデーモンのUNIXドメインソケットは リスト8:PPPのUNIXドメインソケットの表示 $ ls -l /var/run/pppconfd srwxrwxrwx 1 root daemon 0 9 May 12:57 /var/run/pppconfd サーバが実行されると、クライアントはそのパスを UNIXドメインソケットの詳細について、一般的な任意のUNIXリファレンスを参照してください。特に、『UNIX Network Programming』(Stevensほか)をお勧めします。 クライアント/サーバ環境におけるUNIXドメインソケットの使用例については、サンプルコードプロジェクト「CFLocalServer」を参照してください。 UNIXドメインソケットの利点デーモンを実装する場合、UNIXドメインソケットAPIは他のIPCメカニズムに勝るいくつかの利点を備えています。
ベストプラクティスUNIXドメインソケットを使用する場合は、次の点に留意してください。
コーディングに関する推奨事項次のセクションでは、バックグラウンドプログラムのコーディングに関するさまざまな推奨事項を示します。 オンデマンド起動システムリソースの使用を最小限に抑えるには、バックグラウンドプログラム(デーモンまたはエージェント)をオンデマンドで起動するのが一番です。つまり、いつも起動時(またはエージェントの場合は、ログイン時)に起動するのではなく、サービスが必要なときにだけプログラムを起動します。 Mac OS Xでは、この目的を実現するためのさまざまな方法が用意されています。これらの方法はすべて、共通のアプローチを採用しています。
起動基準に応じて、多種多様なスーパーサーバを利用できます。
デーモン化デーモンを開発する場合、起動時にデーモン化が必要になることもあります。これが必要な理由については本書の範囲を超えています。詳細については、『Advanced Programming In The UNIX Environment』を参照してください。 デーモン化が必要かどうかは、起動した方法に応じて決まります。表3に、デーモンのタイプごとにデーモン化の必要性を示します。 表3:デーモン化の必要性
デーモン化する必要がある場合は、daemonルーチンを使って実行できます。 ロギングほとんどの場合、バックグラウンドプログラムにアクティビティ情報を記録させたいと考えることでしょう。このようなログ記録は初期の立ち上げ時に役立ちますし、ユーザが問題をトラブルシューティングを行う場合にも使えます。Mac OS Xでは、いくつかのロギング機能をサポートしています。 Apple System LogApple System Log (ASL)はMac OS X 10.4で導入された新しいロギング機能です。これによって、柔軟で構造化されたログエントリを作成できます。また、これらのログを照会する管理ツールを開発することもできます。 ASLの詳細については、そのmanページを参照してください。その使用例については、サンプルコードプロジェクト「SampleD」を参照してください。 syslogsyslogはBSDの伝統的なロギング機能です。syslogを使用して(比較的構造化されていない)メッセージを記録でき、そのようなメッセージを処理する方法を設定できます。 syslogの詳細については、syslogd、syslog、およびloggerプログラム、syslog.conf環境設定ファイル、ならびにsyslog APIに関するmanページを参照してください。 コンソールログほとんどのデーモンは、ASLまたはsyslogを使ってログを記録します。一般的なデーモンでは、 対照的に、ほとんどのエージェントでは コンソールへのロギングについては、テクニカルノートTN2124「Mac OS Xにおけるデバッグの魔法」を参照してください。 セキュリティの考慮事項本質的に、デーモンはシステム全体を対象とするサービスです。そのため、デーモンを開発するときには、セキュリティについて配慮する必要があります。セキュアなプログラミングに関する詳細な議論は本書の対象外ですが、考慮すべき事項の簡単なリストを以下に示します。
セキュアなソフトウェアの開発の詳細については、このトピックに関して入手可能な多数のテキストのいずれかを参考にしてください。『Building Secure Software』を強くお勧めします。 エージェントとファストユーザスイッチエージェントを開発する場合は、ファストユーザスイッチにかかわる微妙な問題に気を付けてください。たとえば、エージェントは次のことを認識しなければならない場合もあります。
詳細については、『Multiple User Environments』を参照してください。 ヒントとこつこのセクションでは、バックグラウンドプログラムを開発するときに役立つさまざまなヒントとこつについて説明します。 デーモンの起動デーモンを開発する場合、インストール直後にデーモンを起動すると問題に遭遇することがあります。2つの本質的に矛盾する要求があります。
この難問を解決する方法は3つあります。
注: 起動のデバッグデーモンとエージェントは通常、間接的に実行されるので(つまり、
リスト9:起動の一時停止
if (1) {
fprintf(
stderr,
"Process %ld waiting for debugger.\n",
(long) getpid()
);
pause();
}
ウインドウサーバの想定外使用のデバッグリスト5に示すようなメッセージを出してデーモンが機能停止し、ウインドウサーバに接続した理由が考えられない場合は、この問題をデバッグするために実行できることがいろいろあります。まず、 ログの監視バックグラウンドプログラムを開発するときには、ログファイルを常に監視することをお勧めします。予期しないログメッセージによって、不明瞭な問題の切り分けが迅速にできることがどれだけ多いか驚くかもしれません。これはsyslogを使用してログを記録し、バックグラウンドで「コンソール」プログラムの実行するという簡単な方法で実現できます。 同様に次の方法が考えられます。
リスト10:冗長ブートの有効化 $ sudo nvram boot-args="-v" Password:******** ブートストラップ名前空間:補足ブートストラップ名前空間に関する先の議論(「ブートストラップ名前空間」を参照)では、その作成方法について広く述べましたが、それらを破棄する方法に関する問題を積み残していました。最終的には、ブートストラップ名前空間は、それを参照する最後のプロセスが終了するまで存続します。そのため、プロセスが実行されているかぎりは、ブートストラップ名前空間が有効であることが保証されています。 しかし、落とし穴が1つあります。システムがブートストラップ名前空間を作成すると、名前空間はそれを作成したプロセスと関連付けられます。そのプロセスが終了すると、名前空間は非アクティブになります。たとえば、sshログインセッションからデーモンを起動して、その後ログアウトすると、ブートストラップ名前空間を作成したプロセス(事実上、これは 非アクティブになったブートストラップ名前空間でもサービスをルックアップできますが、新しいサービスを登録することはできません。非アクティブ化な名前空間にサービスを登録しようとすると、 初期バージョンのMac OS X(Mac OS X 10.2よりも前)では、ブートストラップ名前空間は非アクティブになりませんでした。名前空間を作成したプロセスが終了すると、ブートストラップサービスがすぐに名前空間を破棄していました。その名前空間を参照するプロセスはすべて、ブートストラップ名前空間のない状況になりました(厳密には、それらのブートストラップポートはMachデッド名になります)。 アップルは間違った名前空間で不適切に実行されているプログラム(一般的に、プログラム作成者のブートストラップ名前空間に対する理解不足が原因)の互換性を改善するために、一時しのぎの手段としてブートストラップ名前空間をそのように破棄するのではなく、非アクティブにすることにしたのです。将来のシステムでは、非アクティブなブートストラップ名前空間の考え方をなくし、10.2以前の動作に戻る可能性があります。プログラムが正しい名前空間で実行されているかぎり、これは互換性に関していかなる問題も引き起こしません。 参考資料
ドキュメント改訂履歴
掲載日: 2006-07-13 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|