Windows サービスとは?
「Windows サービス」 と 「NT サービス」 はどう違うの?
この資料では、「Windows サービス」について説明します。
「Windows サービス」 は、以前は 「NT サービス」 などと呼ばれていました。 このため、いろんな解説書を読んでいると「NT サービス」と書いてあったり、「Windows サービス」 と書いてあったりしますが、 結局、どっちも同じものです。
この資料では、新しい呼び方である 「Windows サービス」 という名称を使います。 あるいは省略して、「サービス」 と呼びます。
ちなみに、マイクロソフトが NT サービスから Windows サービスへと名称を変更した理由は私も知りません(苦笑)
たぶん、Windows NT という呼び方自体が消えつつあるので、 NT (ニュー・テクノロジ) という名称を消して行きたいのでしょうね。そもそも、Windows NT ベースでない Windows (つまり、Windows 95/98/Me です) が無くなりつつありますしね。
Windows サービスって何?いつ使うの?
さて、この Windows サービス。これはどんなものでしょうか。
Windows サービスはバックグラウンドで実行されるプログラムで、システム起動と同時に起動させたりできます。 これは通常のフォアグラウンドで動くプログラムが、ログインしたユーザーの権限で実行するのと大きく異なる点です。
どういう場合に使うかというと、例えば Web サーバーなどのサーバーは、通常 サービスとして実装されます。
サービス・プログラムと対話的なプログラムとの大きな違い~セキュリティの面から~
サービスにすると、誰も Windows にログオンをしなくてもプログラムを起動できます。
さらっと書きましたが、これは非常に重要なポイントです。
そもそも、Windows のセキュリティシステムは、「誰がそのプログラムを実行させているか」 という点に依存しています。
対話的にログインするときは、ユーザー名とパスワードを入力してログインします。通常はこのユーザー名とパスワードから、 トークンが生成されて、そのトークンの元、様々なプログラムが実行され、セキュリティレベルはそのトークンによって制御される仕組みになってます。
具体的に言うと、あなたが Windows にログインしたときは、あなたのトークンをプライマリトークンとして、シェルであるエクスプローラのプロセスが起動されます。 他のプログラム(例えば電卓やメモ帳、あるいはフォトショップなど)を起動したときは、そのトークンがそれぞれのプロセスに受け継がれます。
このトークンによって、できること、できないことが制御されます。
例えば、アクセス権が無くて、フォトショップから開けないファイルが、メモ帳からは開けた・・・なんてことは普通ありませんよね? フォトショップから開こうとしてアクセス拒否されたファイルは、普通メモ帳から開こうとしたって拒否されますよね。
こういう一貫性を確保できるのは、上に書いたように、ログインしたユーザーのトークンがしっかり引き継がれるからなんです。
これが、Windows のセキュリティシステムの基礎となっています。
しかし、サービスとすれば誰もログインしなくても、システムが起動できるようになります。
このため、Web サーバーなどサーバー・プログラムのように、対話的にログインを必要としないプログラムを実装する場合に向いているのです。
Windows サービスって、どんな風に出来てるの?
Windows サービスは、サービスを制御するサブシステムである Service Control Manager (SCM) と通信するインターフェイスを持ちます。 SCM とのインターフェイスを持つので、サービス スナップインまたは SCM と通信可能なプログラムから開始・停止等の操作が行えます。
下に詳しく書きましたが、簡単に言うと、プログラムが開始したら、プログラムから StartServiceCtrlDispatcher という API を呼び出して、Windows に ServiceMain 関数を登録します。Windows は登録された ServiceMain を呼び出します。
サービス固有の作業は、この ServiceMain で実行します。例えば、Web サーバーならば、ServiceMain でクライアントからの要求を受け付け、 それに対して応答を返せばよいです。
さらに、ServiceMain では SCM からの一時停止要求などを受け付ける制御ハンドラを登録します。 管理画面のサービス・スナップインなどからの命令は、この制御ハンドラに送られます。 これによって、サービスを停止したり、開始したりすればよいのです。
要はこれだけです。
具体的なコード例は 簡単な Windows サービスを作成する をご覧ください。
サービスの登録 ~ SCM データベース
システム (=OS, Windows) には、どのプログラムがサービスとして実行されるべきか教えておかなければいけません。 この情報を格納しておくのが、SCM データベース です。実質はレジストリです。
SCMデータベースはレジストリの以下のキーの下にあります:
HKLM\System\CurrentControlSet\Services
サービスの起動と制御
- main:ServiceMain 関数を登録 (起動時)
- StartServiceCtrlDispatcher を用いる
- StartServiceCtrlDispatcher はサービス終了時まで制御を返さない
- ServiceMain: 制御ハンドラを登録 (起動時)
- RegisterServiceCtrlHandlerEx を用いる
- ServiceMain: サービスの開始 (起動時)
- サービス固有のコード
- HandlerEx: 制御の受付 (必要に応じて)
マニア向け・・・実際にスレッドがシステムによって起動されているところを見てみよう
上で、「システムが ServiceMain を呼ぶ」 などとしれっと書きましたが、実際にそうなっているのでしょうか?
このことを確かめるべく、デバッガをデモ用のサービスプログラムにアタッチして、動作確認したのが次のログです。
デバッガでスレッドを見ると、メインスレッドの他、以下のようにサービス用のスレッドが ADVAPI32!ScSvcctrlThreadA によって起動されていることが確認できます。 (これは MSDN にドキュメントされていないようです) また、その開始アドレスは demo_service!ServiceMain です。
0:000> ~*kbn 100 . 0 Id: 11c4.10a0 Suspend: 1 Teb: 7ffde000 Unfrozen # ChildEBP RetAddr Args to Child 00 0012fba4 772e8c74 7745046b 00000020 00000000 ntdll!KiFastSystemCallRet 01 0012fba8 7745046b 00000020 00000000 00000000 ntdll!ZwReadFile+0xc 02 0012fc20 76afd260 00000000 0012fce8 0000022a kernel32!ReadFile+0x210 03 0012fc4c 76afd306 00000020 0012fce8 0000022a ADVAPI32!ScGetPipeInput+0x2a 04 0012fcb8 76afd949 00000020 00000000 0000022a ADVAPI32!ScDispatcherLoop+0x65 05 0012ff30 0040100f 00420000 00000001 0012ff88 ADVAPI32!StartServiceCtrlDispatcherW+0xce 06 0012ff40 00401ef4 00000001 00921b58 00921bc0 demo_service!main+0xf 07 0012ff88 77454911 7ffdf000 0012ffd4 772ce4b6 demo_service!__tmainCRTStartup+0xfb 08 0012ff94 772ce4b6 7ffdf000 6c8032b2 00000000 kernel32!BaseThreadInitThunk+0xe 09 0012ffd4 772ce489 00401f4b 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x23 0a 0012ffec 00000000 00401f4b 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b 1 Id: 11c4.10ec Suspend: 1 Teb: 7ffdd000 Unfrozen # ChildEBP RetAddr Args to Child 00 008afecc 772e83b4 7745c350 00000000 008aff14 ntdll!KiFastSystemCallRet 01 008afed0 7745c350 00000000 008aff14 cd9352f0 ntdll!NtDelayExecution+0xc 02 008aff38 77411c7a 000007d0 00000000 008aff74 kernel32!SleepEx+0x62 03 008aff48 004013c7 000007d0 00000010 00000004 kernel32!Sleep+0xf 04 008aff74 76afd1da 00000001 002b5004 00000000 demo_service!ServiceMain+0x137 05 008aff88 77454911 002b4ff8 008affd4 772ce4b6 ADVAPI32!ScSvcctrlThreadA+0x21 06 008aff94 772ce4b6 002b4ff8 6c1832b2 00000000 kernel32!BaseThreadInitThunk+0xe 07 008affd4 772ce489 76afd1b9 002b4ff8 00000000 ntdll!__RtlUserThreadStart+0x23 08 008affec 00000000 76afd1b9 002b4ff8 00000000 ntdll!_RtlUserThreadStart+0x1b ... 0:000>
SetServiceStatus を用いて状態遷移を Service Control Manager に対してレポートします。
尚、サービスを停止すると以下のように、SERVICE_CONTROL_STOP を出力するコードパスを通ることが確認できます。
...
Demo_Service is running.
Demo_Service is running.
SERVICE_CONTROL_STOP
END OF ServiceMain
eax=00000000 ebx=00000001 ecx=00000000 edx=000000fe esi=00922d1c edi=00922d20
eip=772e9a94 esp=0012fec0 ebp=0012fed0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
772e9a94 c3 ret
サービスのインストール
- レジストリにエントリを作成する
HKLM\SYSTEM \CurrentControlSet\Services - インストールに利用可能なAPI
- OpenSCManager
- CreateService
- CloseServiceHandle
sc create コマンドでサービスをインストールすることが可能です。つまり、 sc create コマンドは上記のレジストリを構成します。
サービスインストール用コマンド si
/? オプションで使い方が表示されます。
サービスをインストールすると、コントロールパネルに表示されます。
tasklist コマンドの /svc オプションで、どのプロセスがどのサービスをホストしているか確認できます。 尚、下記コマンドの結果で表示されている DEMO_SERVICE は当サイトのデモで作成したサービスです。
> tasklist /svc
Image Name PID Services
========================= ======== ============================================
System Idle Process 0 N/A
System 4 N/A
...
mmc.exe 8116 N/A
demo_service.exe 5536 DEMO_SERVICE
tasklist.exe 7796 N/A
...
>