Windows サービスとは?

「Windows サービス」 と 「NT サービス」 はどう違うの?

結局、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 コマンドは上記のレジストリを構成します。

sc コマンドは Windows XP 以降で利用可能です。Windows 2000 など、sc コマンドが使えない環境で、 サービスを簡単にインストールするコマンドを以前作ったものがありますので置いておきます。 (万が一、そんな環境で何かするときはお使いください)
サービスインストール用コマンド 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
...

>

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説